Mercurial > hg > orthanc
comparison OrthancServer/DicomProtocol/DicomUserConnection.cpp @ 947:c2c28dd17e87 query-retrieve
integration mainline -> query-retrieve
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 25 Jun 2014 12:09:38 +0200 |
parents | 67e6400fca03 b3f6fb1130cd |
children | 111e23bb4904 |
comparison
equal
deleted
inserted
replaced
758:67e6400fca03 | 947:c2c28dd17e87 |
---|---|
28 * You should have received a copy of the GNU General Public License | 28 * You should have received a copy of the GNU General Public License |
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. | 29 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
30 **/ | 30 **/ |
31 | 31 |
32 | 32 |
33 | |
34 /*========================================================================= | |
35 | |
36 This file is based on portions of the following project: | |
37 | |
38 Program: DCMTK 3.6.0 | |
39 Module: http://dicom.offis.de/dcmtk.php.en | |
40 | |
41 Copyright (C) 1994-2011, OFFIS e.V. | |
42 All rights reserved. | |
43 | |
44 This software and supporting documentation were developed by | |
45 | |
46 OFFIS e.V. | |
47 R&D Division Health | |
48 Escherweg 2 | |
49 26121 Oldenburg, Germany | |
50 | |
51 Redistribution and use in source and binary forms, with or without | |
52 modification, are permitted provided that the following conditions | |
53 are met: | |
54 | |
55 - Redistributions of source code must retain the above copyright | |
56 notice, this list of conditions and the following disclaimer. | |
57 | |
58 - Redistributions in binary form must reproduce the above copyright | |
59 notice, this list of conditions and the following disclaimer in the | |
60 documentation and/or other materials provided with the distribution. | |
61 | |
62 - Neither the name of OFFIS nor the names of its contributors may be | |
63 used to endorse or promote products derived from this software | |
64 without specific prior written permission. | |
65 | |
66 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
67 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
68 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
69 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
70 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
71 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
72 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
73 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
74 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
75 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
76 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
77 | |
78 =========================================================================*/ | |
79 | |
80 | |
81 #include "../PrecompiledHeadersServer.h" | |
33 #include "DicomUserConnection.h" | 82 #include "DicomUserConnection.h" |
34 | 83 |
35 #include "../../Core/OrthancException.h" | 84 #include "../../Core/OrthancException.h" |
36 #include "../ToDcmtkBridge.h" | 85 #include "../ToDcmtkBridge.h" |
37 #include "../FromDcmtkBridge.h" | 86 #include "../FromDcmtkBridge.h" |
56 **/ | 105 **/ |
57 #define HOST_NAME_MAX 256 | 106 #define HOST_NAME_MAX 256 |
58 #endif | 107 #endif |
59 | 108 |
60 | 109 |
110 #if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) | |
111 /** | |
112 * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that | |
113 * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an | |
114 * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect | |
115 * that the result will fit." | |
116 * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html | |
117 **/ | |
118 #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX | |
119 #endif | |
120 | |
121 | |
61 static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; | 122 static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; |
123 | |
124 /** | |
125 * "If we have more than 64 storage SOP classes, tools such as | |
126 * storescu will fail because they attempt to negotiate two | |
127 * presentation contexts for each SOP class, and there is a total | |
128 * limit of 128 contexts for one association." | |
129 **/ | |
130 static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; | |
131 | |
62 | 132 |
63 namespace Orthanc | 133 namespace Orthanc |
64 { | 134 { |
65 struct DicomUserConnection::PImpl | 135 struct DicomUserConnection::PImpl |
66 { | 136 { |
101 { | 171 { |
102 pimpl_->CheckIsOpen(); | 172 pimpl_->CheckIsOpen(); |
103 } | 173 } |
104 | 174 |
105 | 175 |
106 void DicomUserConnection::CopyParameters(const DicomUserConnection& other) | 176 static void RegisterStorageSOPClass(T_ASC_Parameters* params, |
107 { | 177 unsigned int& presentationContextId, |
108 Close(); | 178 const std::string& sopClass, |
109 localAet_ = other.localAet_; | 179 const char* asPreferred[], |
110 distantAet_ = other.distantAet_; | 180 std::vector<const char*>& asFallback) |
111 distantHost_ = other.distantHost_; | 181 { |
112 distantPort_ = other.distantPort_; | 182 Check(ASC_addPresentationContext(params, presentationContextId, |
113 manufacturer_ = other.manufacturer_; | 183 sopClass.c_str(), asPreferred, 1)); |
114 preferredTransferSyntax_ = other.preferredTransferSyntax_; | 184 presentationContextId += 2; |
115 } | 185 |
116 | 186 if (asFallback.size() > 0) |
117 | 187 { |
188 Check(ASC_addPresentationContext(params, presentationContextId, | |
189 sopClass.c_str(), &asFallback[0], asFallback.size())); | |
190 presentationContextId += 2; | |
191 } | |
192 } | |
193 | |
194 | |
118 void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) | 195 void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) |
119 { | 196 { |
120 // Fallback transfer syntaxes | 197 // Flatten an array with the preferred transfer syntax |
198 const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; | |
199 | |
200 // Setup the fallback transfer syntaxes | |
121 std::set<std::string> fallbackSyntaxes; | 201 std::set<std::string> fallbackSyntaxes; |
122 fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); | 202 fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); |
123 fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); | 203 fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); |
124 fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); | 204 fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); |
125 | |
126 // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE | |
127 std::vector<std::string> transferSyntaxes; | |
128 transferSyntaxes.push_back(UID_VerificationSOPClass); | |
129 transferSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); | |
130 transferSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); | |
131 transferSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); | |
132 | |
133 // TODO: Allow the set below to be configured | |
134 std::set<std::string> uselessSyntaxes; | |
135 uselessSyntaxes.insert(UID_BlendingSoftcopyPresentationStateStorage); | |
136 uselessSyntaxes.insert(UID_GrayscaleSoftcopyPresentationStateStorage); | |
137 uselessSyntaxes.insert(UID_ColorSoftcopyPresentationStateStorage); | |
138 uselessSyntaxes.insert(UID_PseudoColorSoftcopyPresentationStateStorage); | |
139 | |
140 // Add the transfer syntaxes for C-STORE | |
141 for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) | |
142 { | |
143 // Test to make some room to allow the ECHO and FIND requests | |
144 if (uselessSyntaxes.find(dcmShortSCUStorageSOPClassUIDs[i]) == uselessSyntaxes.end()) | |
145 { | |
146 transferSyntaxes.push_back(dcmShortSCUStorageSOPClassUIDs[i]); | |
147 } | |
148 } | |
149 | |
150 // Flatten the fallback transfer syntaxes array | |
151 const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; | |
152 | |
153 fallbackSyntaxes.erase(preferredTransferSyntax); | 205 fallbackSyntaxes.erase(preferredTransferSyntax); |
154 | 206 |
207 // Flatten an array with the fallback transfer syntaxes | |
155 std::vector<const char*> asFallback; | 208 std::vector<const char*> asFallback; |
156 asFallback.reserve(fallbackSyntaxes.size()); | 209 asFallback.reserve(fallbackSyntaxes.size()); |
157 for (std::set<std::string>::const_iterator | 210 for (std::set<std::string>::const_iterator |
158 it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) | 211 it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) |
159 { | 212 { |
160 asFallback.push_back(it->c_str()); | 213 asFallback.push_back(it->c_str()); |
161 } | 214 } |
162 | 215 |
216 CheckStorageSOPClassesInvariant(); | |
163 unsigned int presentationContextId = 1; | 217 unsigned int presentationContextId = 1; |
164 for (size_t i = 0; i < transferSyntaxes.size(); i++) | 218 |
165 { | 219 for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); |
166 Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, | 220 it != reservedStorageSOPClasses_.end(); ++it) |
167 transferSyntaxes[i].c_str(), asPreferred, 1)); | 221 { |
168 presentationContextId += 2; | 222 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, |
169 | 223 *it, asPreferred, asFallback); |
170 if (asFallback.size() > 0) | 224 } |
171 { | 225 |
172 Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, | 226 for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); |
173 transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size())); | 227 it != storageSOPClasses_.end(); ++it) |
174 presentationContextId += 2; | 228 { |
175 } | 229 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, |
230 *it, asPreferred, asFallback); | |
231 } | |
232 | |
233 for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); | |
234 it != defaultStorageSOPClasses_.end(); ++it) | |
235 { | |
236 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, | |
237 *it, asPreferred, asFallback); | |
176 } | 238 } |
177 } | 239 } |
178 | 240 |
179 | 241 |
180 static bool IsGenericTransferSyntax(const std::string& syntax) | 242 static bool IsGenericTransferSyntax(const std::string& syntax) |
190 CheckIsOpen(); | 252 CheckIsOpen(); |
191 | 253 |
192 DcmFileFormat dcmff; | 254 DcmFileFormat dcmff; |
193 Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); | 255 Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); |
194 | 256 |
257 // Determine the storage SOP class UID for this instance | |
258 static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016); | |
259 OFString sopClassUid; | |
260 if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good()) | |
261 { | |
262 connection.AddStorageSOPClass(sopClassUid.c_str()); | |
263 } | |
264 | |
195 // Determine whether a new presentation context must be | 265 // Determine whether a new presentation context must be |
196 // negociated, depending on the transfer syntax of this instance | 266 // negotiated, depending on the transfer syntax of this instance |
197 DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); | 267 DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); |
198 const std::string syntax(xfer.getXferID()); | 268 const std::string syntax(xfer.getXferID()); |
199 bool isGeneric = IsGenericTransferSyntax(syntax); | 269 bool isGeneric = IsGenericTransferSyntax(syntax); |
200 | 270 |
201 if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) | 271 if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) |
202 { | 272 { |
203 // Making a generic-to-specific or specific-to-generic change of | 273 // Making a generic-to-specific or specific-to-generic change of |
204 // the transfer syntax. Renegociate the connection. | 274 // the transfer syntax. Renegotiate the connection. |
205 LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax"; | 275 LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; |
206 | 276 |
207 if (isGeneric) | 277 if (isGeneric) |
208 { | 278 { |
209 connection.ResetPreferredTransferSyntax(); | 279 connection.ResetPreferredTransferSyntax(); |
210 } | 280 } |
211 else | 281 else |
212 { | 282 { |
213 connection.SetPreferredTransferSyntax(syntax); | 283 connection.SetPreferredTransferSyntax(syntax); |
214 } | 284 } |
215 | 285 } |
286 | |
287 if (!connection.IsOpen()) | |
288 { | |
289 LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; | |
216 connection.Open(); | 290 connection.Open(); |
217 } | 291 } |
218 | 292 |
219 // Figure out which SOP class and SOP instance is encapsulated in the file | 293 // Figure out which SOP class and SOP instance is encapsulated in the file |
220 DIC_UI sopClass; | 294 DIC_UI sopClass; |
230 { | 304 { |
231 const char *modalityName = dcmSOPClassUIDToModality(sopClass); | 305 const char *modalityName = dcmSOPClassUIDToModality(sopClass); |
232 if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); | 306 if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); |
233 if (!modalityName) modalityName = "unknown SOP class"; | 307 if (!modalityName) modalityName = "unknown SOP class"; |
234 throw OrthancException("DicomUserConnection: No presentation context for modality " + | 308 throw OrthancException("DicomUserConnection: No presentation context for modality " + |
235 std::string(modalityName)); | 309 std::string(modalityName)); |
236 } | 310 } |
237 | 311 |
238 // Prepare the transmission of data | 312 // Prepare the transmission of data |
239 T_DIMSE_C_StoreRQ req; | 313 T_DIMSE_C_StoreRQ req; |
240 memset(&req, 0, sizeof(req)); | 314 memset(&req, 0, sizeof(req)); |
286 | 360 |
287 const char* sopClass; | 361 const char* sopClass; |
288 std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); | 362 std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); |
289 switch (model) | 363 switch (model) |
290 { | 364 { |
291 case FindRootModel_Patient: | 365 case FindRootModel_Patient: |
292 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); | 366 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); |
293 sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; | 367 sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; |
294 | 368 |
295 // Accession number | 369 // Accession number |
296 if (!fields.HasTag(0x0008, 0x0050)) | 370 if (!fields.HasTag(0x0008, 0x0050)) |
297 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 371 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
298 | 372 |
299 // Patient ID | 373 // Patient ID |
300 if (!fields.HasTag(0x0010, 0x0020)) | 374 if (!fields.HasTag(0x0010, 0x0020)) |
301 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); | 375 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); |
302 | 376 |
303 break; | 377 break; |
304 | 378 |
305 case FindRootModel_Study: | 379 case FindRootModel_Study: |
306 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); | 380 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); |
307 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | 381 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; |
308 | 382 |
309 // Accession number | 383 // Accession number |
310 if (!fields.HasTag(0x0008, 0x0050)) | 384 if (!fields.HasTag(0x0008, 0x0050)) |
311 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 385 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
312 | 386 |
313 // Study instance UID | 387 // Study instance UID |
314 if (!fields.HasTag(0x0020, 0x000d)) | 388 if (!fields.HasTag(0x0020, 0x000d)) |
315 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); | 389 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); |
316 | 390 |
317 break; | 391 break; |
318 | 392 |
319 case FindRootModel_Series: | 393 case FindRootModel_Series: |
320 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); | 394 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); |
321 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | 395 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; |
322 | 396 |
323 // Accession number | 397 // Accession number |
324 if (!fields.HasTag(0x0008, 0x0050)) | 398 if (!fields.HasTag(0x0008, 0x0050)) |
325 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 399 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
326 | 400 |
327 // Study instance UID | 401 // Study instance UID |
328 if (!fields.HasTag(0x0020, 0x000d)) | 402 if (!fields.HasTag(0x0020, 0x000d)) |
329 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); | 403 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); |
330 | 404 |
331 // Series instance UID | 405 // Series instance UID |
332 if (!fields.HasTag(0x0020, 0x000e)) | 406 if (!fields.HasTag(0x0020, 0x000e)) |
333 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); | 407 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); |
334 | 408 |
335 break; | 409 break; |
336 | 410 |
337 case FindRootModel_Instance: | 411 case FindRootModel_Instance: |
338 if (manufacturer_ == ModalityManufacturer_ClearCanvas || | 412 if (manufacturer_ == ModalityManufacturer_ClearCanvas || |
339 manufacturer_ == ModalityManufacturer_Dcm4Chee) | 413 manufacturer_ == ModalityManufacturer_Dcm4Chee) |
340 { | 414 { |
341 // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. | 415 // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. |
342 // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J | 416 // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J |
343 // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx | 417 // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx |
344 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); | 418 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); |
345 } | 419 } |
346 else | 420 else |
347 { | 421 { |
348 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); | 422 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); |
349 } | 423 } |
350 | 424 |
351 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | 425 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; |
352 | 426 |
353 // Accession number | 427 // Accession number |
354 if (!fields.HasTag(0x0008, 0x0050)) | 428 if (!fields.HasTag(0x0008, 0x0050)) |
355 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 429 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
356 | 430 |
357 // Study instance UID | 431 // Study instance UID |
358 if (!fields.HasTag(0x0020, 0x000d)) | 432 if (!fields.HasTag(0x0020, 0x000d)) |
359 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); | 433 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); |
360 | 434 |
361 // Series instance UID | 435 // Series instance UID |
362 if (!fields.HasTag(0x0020, 0x000e)) | 436 if (!fields.HasTag(0x0020, 0x000e)) |
363 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); | 437 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); |
364 | 438 |
365 // SOP Instance UID | 439 // SOP Instance UID |
366 if (!fields.HasTag(0x0008, 0x0018)) | 440 if (!fields.HasTag(0x0008, 0x0018)) |
367 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); | 441 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); |
368 | 442 |
369 break; | 443 break; |
370 | 444 |
371 default: | 445 default: |
372 throw OrthancException(ErrorCode_ParameterOutOfRange); | 446 throw OrthancException(ErrorCode_ParameterOutOfRange); |
373 } | 447 } |
374 | 448 |
375 // Figure out which of the accepted presentation contexts should be used | 449 // Figure out which of the accepted presentation contexts should be used |
376 int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); | 450 int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); |
377 if (presID == 0) | 451 if (presID == 0) |
499 | 573 |
500 Check(cond); | 574 Check(cond); |
501 } | 575 } |
502 | 576 |
503 | 577 |
578 void DicomUserConnection::ResetStorageSOPClasses() | |
579 { | |
580 CheckStorageSOPClassesInvariant(); | |
581 | |
582 storageSOPClasses_.clear(); | |
583 defaultStorageSOPClasses_.clear(); | |
584 | |
585 // Copy the short list of storage SOP classes from DCMTK, making | |
586 // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE. | |
587 | |
588 std::set<std::string> uncommon; | |
589 uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); | |
590 uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); | |
591 uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); | |
592 uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); | |
593 | |
594 // Add the storage syntaxes for C-STORE | |
595 for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) | |
596 { | |
597 if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) | |
598 { | |
599 defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); | |
600 } | |
601 } | |
602 | |
603 CheckStorageSOPClassesInvariant(); | |
604 } | |
605 | |
606 | |
504 DicomUserConnection::DicomUserConnection() : | 607 DicomUserConnection::DicomUserConnection() : |
505 pimpl_(new PImpl), | 608 pimpl_(new PImpl), |
506 preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), | 609 preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), |
507 localAet_("STORESCU"), | 610 localAet_("STORESCU"), |
508 distantAet_("ANY-SCP"), | 611 distantAet_("ANY-SCP"), |
512 manufacturer_ = ModalityManufacturer_Generic; | 615 manufacturer_ = ModalityManufacturer_Generic; |
513 | 616 |
514 pimpl_->net_ = NULL; | 617 pimpl_->net_ = NULL; |
515 pimpl_->params_ = NULL; | 618 pimpl_->params_ = NULL; |
516 pimpl_->assoc_ = NULL; | 619 pimpl_->assoc_ = NULL; |
620 | |
621 // SOP classes for C-ECHO, C-FIND and C-MOVE | |
622 reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); | |
623 reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); | |
624 reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); | |
625 reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); | |
626 | |
627 ResetStorageSOPClasses(); | |
517 } | 628 } |
518 | 629 |
519 DicomUserConnection::~DicomUserConnection() | 630 DicomUserConnection::~DicomUserConnection() |
520 { | 631 { |
521 Close(); | 632 Close(); |
522 } | 633 } |
634 | |
635 | |
636 void DicomUserConnection::Connect(const RemoteModalityParameters& parameters) | |
637 { | |
638 SetDistantApplicationEntityTitle(parameters.GetApplicationEntityTitle()); | |
639 SetDistantHost(parameters.GetHost()); | |
640 SetDistantPort(parameters.GetPort()); | |
641 SetDistantManufacturer(parameters.GetManufacturer()); | |
642 } | |
643 | |
523 | 644 |
524 void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) | 645 void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) |
525 { | 646 { |
526 if (localAet_ != aet) | 647 if (localAet_ != aet) |
527 { | 648 { |
592 { | 713 { |
593 // Don't reopen the connection | 714 // Don't reopen the connection |
594 return; | 715 return; |
595 } | 716 } |
596 | 717 |
718 LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() | |
719 << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host " | |
720 << GetDistantHost() << ":" << GetDistantPort() | |
721 << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")"; | |
722 | |
597 Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_)); | 723 Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_)); |
598 Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); | 724 Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); |
599 | 725 |
600 // Set this application's title and the called application's title in the params | 726 // Set this application's title and the called application's title in the params |
601 Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), distantAet_.c_str(), NULL)); | 727 Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), distantAet_.c_str(), NULL)); |
605 gethostname(localHost, HOST_NAME_MAX - 1); | 731 gethostname(localHost, HOST_NAME_MAX - 1); |
606 | 732 |
607 char distantHostAndPort[HOST_NAME_MAX]; | 733 char distantHostAndPort[HOST_NAME_MAX]; |
608 | 734 |
609 #ifdef _MSC_VER | 735 #ifdef _MSC_VER |
610 _snprintf | 736 _snprintf |
611 #else | 737 #else |
612 snprintf | 738 snprintf |
613 #endif | 739 #endif |
614 (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); | 740 (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); |
615 | 741 |
616 Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort)); | 742 Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort)); |
617 | 743 |
618 // Set various options | 744 // Set various options |
619 Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); | 745 Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); |
740 void DicomUserConnection::SetConnectionTimeout(uint32_t seconds) | 866 void DicomUserConnection::SetConnectionTimeout(uint32_t seconds) |
741 { | 867 { |
742 dcmConnectionTimeout.set(seconds); | 868 dcmConnectionTimeout.set(seconds); |
743 } | 869 } |
744 | 870 |
871 | |
872 void DicomUserConnection::CheckStorageSOPClassesInvariant() const | |
873 { | |
874 assert(storageSOPClasses_.size() + | |
875 defaultStorageSOPClasses_.size() + | |
876 reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); | |
877 } | |
878 | |
879 void DicomUserConnection::AddStorageSOPClass(const char* sop) | |
880 { | |
881 CheckStorageSOPClassesInvariant(); | |
882 | |
883 if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) | |
884 { | |
885 // This storage SOP class is already explicitly registered. Do | |
886 // nothing. | |
887 return; | |
888 } | |
889 | |
890 if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) | |
891 { | |
892 // This storage SOP class is not explicitly registered, but is | |
893 // used by default. Just register it explicitly. | |
894 defaultStorageSOPClasses_.erase(sop); | |
895 storageSOPClasses_.insert(sop); | |
896 | |
897 CheckStorageSOPClassesInvariant(); | |
898 return; | |
899 } | |
900 | |
901 // This storage SOP class is neither explicitly, nor implicitly | |
902 // registered. Close the connection and register it explicitly. | |
903 | |
904 Close(); | |
905 | |
906 if (reservedStorageSOPClasses_.size() + | |
907 storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) | |
908 { | |
909 // The maximum number of SOP classes is reached | |
910 ResetStorageSOPClasses(); | |
911 defaultStorageSOPClasses_.erase(sop); | |
912 } | |
913 else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + | |
914 defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) | |
915 { | |
916 // Make room in the default storage syntaxes | |
917 assert(defaultStorageSOPClasses_.size() > 0); // Necessarily true because condition (*) is false | |
918 defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); | |
919 } | |
920 | |
921 // Explicitly register the new storage syntax | |
922 storageSOPClasses_.insert(sop); | |
923 | |
924 CheckStorageSOPClassesInvariant(); | |
925 } | |
926 | |
745 } | 927 } |