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 }