Mercurial > hg > orthanc
comparison OrthancServer/DicomProtocol/DicomUserConnection.cpp @ 930:27d256e0b458 mac
integration mainline -> mac
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 24 Jun 2014 16:47:18 +0200 |
parents | 55b945749bed 816dccaeb7cf |
children | b3f6fb1130cd |
comparison
equal
deleted
inserted
replaced
928:882833632b1f | 930:27d256e0b458 |
---|---|
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" |
57 #define HOST_NAME_MAX 256 | 106 #define HOST_NAME_MAX 256 |
58 #endif | 107 #endif |
59 | 108 |
60 | 109 |
61 #if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) | 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 **/ | |
62 #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX | 118 #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX |
63 #endif | 119 #endif |
64 | 120 |
65 | 121 |
66 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 | |
67 | 132 |
68 namespace Orthanc | 133 namespace Orthanc |
69 { | 134 { |
70 struct DicomUserConnection::PImpl | 135 struct DicomUserConnection::PImpl |
71 { | 136 { |
106 { | 171 { |
107 pimpl_->CheckIsOpen(); | 172 pimpl_->CheckIsOpen(); |
108 } | 173 } |
109 | 174 |
110 | 175 |
111 void DicomUserConnection::CopyParameters(const DicomUserConnection& other) | 176 static void RegisterStorageSOPClass(T_ASC_Parameters* params, |
112 { | 177 unsigned int& presentationContextId, |
113 Close(); | 178 const std::string& sopClass, |
114 localAet_ = other.localAet_; | 179 const char* asPreferred[], |
115 distantAet_ = other.distantAet_; | 180 std::vector<const char*>& asFallback) |
116 distantHost_ = other.distantHost_; | 181 { |
117 distantPort_ = other.distantPort_; | 182 Check(ASC_addPresentationContext(params, presentationContextId, |
118 manufacturer_ = other.manufacturer_; | 183 sopClass.c_str(), asPreferred, 1)); |
119 preferredTransferSyntax_ = other.preferredTransferSyntax_; | 184 presentationContextId += 2; |
120 } | 185 |
121 | 186 if (asFallback.size() > 0) |
122 | 187 { |
188 Check(ASC_addPresentationContext(params, presentationContextId, | |
189 sopClass.c_str(), &asFallback[0], asFallback.size())); | |
190 presentationContextId += 2; | |
191 } | |
192 } | |
193 | |
194 | |
123 void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) | 195 void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) |
124 { | 196 { |
125 // 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 | |
126 std::set<std::string> fallbackSyntaxes; | 201 std::set<std::string> fallbackSyntaxes; |
127 fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); | 202 fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); |
128 fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); | 203 fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); |
129 fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); | 204 fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); |
130 | |
131 // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE | |
132 std::vector<std::string> transferSyntaxes; | |
133 transferSyntaxes.push_back(UID_VerificationSOPClass); | |
134 transferSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); | |
135 transferSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); | |
136 transferSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); | |
137 | |
138 // TODO: Allow the set below to be configured | |
139 std::set<std::string> uselessSyntaxes; | |
140 uselessSyntaxes.insert(UID_BlendingSoftcopyPresentationStateStorage); | |
141 uselessSyntaxes.insert(UID_GrayscaleSoftcopyPresentationStateStorage); | |
142 uselessSyntaxes.insert(UID_ColorSoftcopyPresentationStateStorage); | |
143 uselessSyntaxes.insert(UID_PseudoColorSoftcopyPresentationStateStorage); | |
144 | |
145 // Add the transfer syntaxes for C-STORE | |
146 for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) | |
147 { | |
148 // Test to make some room to allow the ECHO and FIND requests | |
149 if (uselessSyntaxes.find(dcmShortSCUStorageSOPClassUIDs[i]) == uselessSyntaxes.end()) | |
150 { | |
151 transferSyntaxes.push_back(dcmShortSCUStorageSOPClassUIDs[i]); | |
152 } | |
153 } | |
154 | |
155 // Flatten the fallback transfer syntaxes array | |
156 const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; | |
157 | |
158 fallbackSyntaxes.erase(preferredTransferSyntax); | 205 fallbackSyntaxes.erase(preferredTransferSyntax); |
159 | 206 |
207 // Flatten an array with the fallback transfer syntaxes | |
160 std::vector<const char*> asFallback; | 208 std::vector<const char*> asFallback; |
161 asFallback.reserve(fallbackSyntaxes.size()); | 209 asFallback.reserve(fallbackSyntaxes.size()); |
162 for (std::set<std::string>::const_iterator | 210 for (std::set<std::string>::const_iterator |
163 it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) | 211 it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) |
164 { | 212 { |
165 asFallback.push_back(it->c_str()); | 213 asFallback.push_back(it->c_str()); |
166 } | 214 } |
167 | 215 |
216 CheckStorageSOPClassesInvariant(); | |
168 unsigned int presentationContextId = 1; | 217 unsigned int presentationContextId = 1; |
169 for (size_t i = 0; i < transferSyntaxes.size(); i++) | 218 |
170 { | 219 for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); |
171 Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, | 220 it != reservedStorageSOPClasses_.end(); it++) |
172 transferSyntaxes[i].c_str(), asPreferred, 1)); | 221 { |
173 presentationContextId += 2; | 222 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, |
174 | 223 *it, asPreferred, asFallback); |
175 if (asFallback.size() > 0) | 224 } |
176 { | 225 |
177 Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, | 226 for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); |
178 transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size())); | 227 it != storageSOPClasses_.end(); it++) |
179 presentationContextId += 2; | 228 { |
180 } | 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); | |
181 } | 238 } |
182 } | 239 } |
183 | 240 |
184 | 241 |
185 static bool IsGenericTransferSyntax(const std::string& syntax) | 242 static bool IsGenericTransferSyntax(const std::string& syntax) |
195 CheckIsOpen(); | 252 CheckIsOpen(); |
196 | 253 |
197 DcmFileFormat dcmff; | 254 DcmFileFormat dcmff; |
198 Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); | 255 Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); |
199 | 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 | |
200 // Determine whether a new presentation context must be | 265 // Determine whether a new presentation context must be |
201 // negociated, depending on the transfer syntax of this instance | 266 // negotiated, depending on the transfer syntax of this instance |
202 DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); | 267 DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); |
203 const std::string syntax(xfer.getXferID()); | 268 const std::string syntax(xfer.getXferID()); |
204 bool isGeneric = IsGenericTransferSyntax(syntax); | 269 bool isGeneric = IsGenericTransferSyntax(syntax); |
205 | 270 |
206 if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) | 271 if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) |
207 { | 272 { |
208 // Making a generic-to-specific or specific-to-generic change of | 273 // Making a generic-to-specific or specific-to-generic change of |
209 // the transfer syntax. Renegociate the connection. | 274 // the transfer syntax. Renegotiate the connection. |
210 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"; |
211 | 276 |
212 if (isGeneric) | 277 if (isGeneric) |
213 { | 278 { |
214 connection.ResetPreferredTransferSyntax(); | 279 connection.ResetPreferredTransferSyntax(); |
215 } | 280 } |
216 else | 281 else |
217 { | 282 { |
218 connection.SetPreferredTransferSyntax(syntax); | 283 connection.SetPreferredTransferSyntax(syntax); |
219 } | 284 } |
220 | 285 } |
286 | |
287 if (!connection.IsOpen()) | |
288 { | |
289 LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; | |
221 connection.Open(); | 290 connection.Open(); |
222 } | 291 } |
223 | 292 |
224 // 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 |
225 DIC_UI sopClass; | 294 DIC_UI sopClass; |
235 { | 304 { |
236 const char *modalityName = dcmSOPClassUIDToModality(sopClass); | 305 const char *modalityName = dcmSOPClassUIDToModality(sopClass); |
237 if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); | 306 if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); |
238 if (!modalityName) modalityName = "unknown SOP class"; | 307 if (!modalityName) modalityName = "unknown SOP class"; |
239 throw OrthancException("DicomUserConnection: No presentation context for modality " + | 308 throw OrthancException("DicomUserConnection: No presentation context for modality " + |
240 std::string(modalityName)); | 309 std::string(modalityName)); |
241 } | 310 } |
242 | 311 |
243 // Prepare the transmission of data | 312 // Prepare the transmission of data |
244 T_DIMSE_C_StoreRQ req; | 313 T_DIMSE_C_StoreRQ req; |
245 memset(&req, 0, sizeof(req)); | 314 memset(&req, 0, sizeof(req)); |
291 | 360 |
292 const char* sopClass; | 361 const char* sopClass; |
293 std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); | 362 std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); |
294 switch (model) | 363 switch (model) |
295 { | 364 { |
296 case FindRootModel_Patient: | 365 case FindRootModel_Patient: |
297 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); | 366 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); |
298 sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; | 367 sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; |
299 | 368 |
300 // Accession number | 369 // Accession number |
301 if (!fields.HasTag(0x0008, 0x0050)) | 370 if (!fields.HasTag(0x0008, 0x0050)) |
302 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 371 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
303 | 372 |
304 // Patient ID | 373 // Patient ID |
305 if (!fields.HasTag(0x0010, 0x0020)) | 374 if (!fields.HasTag(0x0010, 0x0020)) |
306 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); | 375 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); |
307 | 376 |
308 break; | 377 break; |
309 | 378 |
310 case FindRootModel_Study: | 379 case FindRootModel_Study: |
311 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); | 380 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); |
312 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | 381 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; |
313 | 382 |
314 // Accession number | 383 // Accession number |
315 if (!fields.HasTag(0x0008, 0x0050)) | 384 if (!fields.HasTag(0x0008, 0x0050)) |
316 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 385 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
317 | 386 |
318 // Study instance UID | 387 // Study instance UID |
319 if (!fields.HasTag(0x0020, 0x000d)) | 388 if (!fields.HasTag(0x0020, 0x000d)) |
320 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); | 389 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); |
321 | 390 |
322 break; | 391 break; |
323 | 392 |
324 case FindRootModel_Series: | 393 case FindRootModel_Series: |
325 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); | 394 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); |
326 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | 395 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; |
327 | 396 |
328 // Accession number | 397 // Accession number |
329 if (!fields.HasTag(0x0008, 0x0050)) | 398 if (!fields.HasTag(0x0008, 0x0050)) |
330 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 399 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
331 | 400 |
332 // Study instance UID | 401 // Study instance UID |
333 if (!fields.HasTag(0x0020, 0x000d)) | 402 if (!fields.HasTag(0x0020, 0x000d)) |
334 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); | 403 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); |
335 | 404 |
336 // Series instance UID | 405 // Series instance UID |
337 if (!fields.HasTag(0x0020, 0x000e)) | 406 if (!fields.HasTag(0x0020, 0x000e)) |
338 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); | 407 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); |
339 | 408 |
340 break; | 409 break; |
341 | 410 |
342 case FindRootModel_Instance: | 411 case FindRootModel_Instance: |
343 if (manufacturer_ == ModalityManufacturer_ClearCanvas || | 412 if (manufacturer_ == ModalityManufacturer_ClearCanvas || |
344 manufacturer_ == ModalityManufacturer_Dcm4Chee) | 413 manufacturer_ == ModalityManufacturer_Dcm4Chee) |
345 { | 414 { |
346 // 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>. |
347 // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J | 416 // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J |
348 // 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 |
349 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); | 418 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); |
350 } | 419 } |
351 else | 420 else |
352 { | 421 { |
353 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); | 422 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); |
354 } | 423 } |
355 | 424 |
356 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | 425 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; |
357 | 426 |
358 // Accession number | 427 // Accession number |
359 if (!fields.HasTag(0x0008, 0x0050)) | 428 if (!fields.HasTag(0x0008, 0x0050)) |
360 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); | 429 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); |
361 | 430 |
362 // Study instance UID | 431 // Study instance UID |
363 if (!fields.HasTag(0x0020, 0x000d)) | 432 if (!fields.HasTag(0x0020, 0x000d)) |
364 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); | 433 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); |
365 | 434 |
366 // Series instance UID | 435 // Series instance UID |
367 if (!fields.HasTag(0x0020, 0x000e)) | 436 if (!fields.HasTag(0x0020, 0x000e)) |
368 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); | 437 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); |
369 | 438 |
370 // SOP Instance UID | 439 // SOP Instance UID |
371 if (!fields.HasTag(0x0008, 0x0018)) | 440 if (!fields.HasTag(0x0008, 0x0018)) |
372 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); | 441 DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); |
373 | 442 |
374 break; | 443 break; |
375 | 444 |
376 default: | 445 default: |
377 throw OrthancException(ErrorCode_ParameterOutOfRange); | 446 throw OrthancException(ErrorCode_ParameterOutOfRange); |
378 } | 447 } |
379 | 448 |
380 // Figure out which of the accepted presentation contexts should be used | 449 // Figure out which of the accepted presentation contexts should be used |
381 int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); | 450 int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); |
382 if (presID == 0) | 451 if (presID == 0) |
504 | 573 |
505 Check(cond); | 574 Check(cond); |
506 } | 575 } |
507 | 576 |
508 | 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 | |
509 DicomUserConnection::DicomUserConnection() : | 607 DicomUserConnection::DicomUserConnection() : |
510 pimpl_(new PImpl), | 608 pimpl_(new PImpl), |
511 preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), | 609 preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), |
512 localAet_("STORESCU"), | 610 localAet_("STORESCU"), |
513 distantAet_("ANY-SCP"), | 611 distantAet_("ANY-SCP"), |
517 manufacturer_ = ModalityManufacturer_Generic; | 615 manufacturer_ = ModalityManufacturer_Generic; |
518 | 616 |
519 pimpl_->net_ = NULL; | 617 pimpl_->net_ = NULL; |
520 pimpl_->params_ = NULL; | 618 pimpl_->params_ = NULL; |
521 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(); | |
522 } | 628 } |
523 | 629 |
524 DicomUserConnection::~DicomUserConnection() | 630 DicomUserConnection::~DicomUserConnection() |
525 { | 631 { |
526 Close(); | 632 Close(); |
527 } | 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 | |
528 | 644 |
529 void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) | 645 void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) |
530 { | 646 { |
531 if (localAet_ != aet) | 647 if (localAet_ != aet) |
532 { | 648 { |
597 { | 713 { |
598 // Don't reopen the connection | 714 // Don't reopen the connection |
599 return; | 715 return; |
600 } | 716 } |
601 | 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 | |
602 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_)); |
603 Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); | 724 Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); |
604 | 725 |
605 // 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 |
606 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)); |
610 gethostname(localHost, HOST_NAME_MAX - 1); | 731 gethostname(localHost, HOST_NAME_MAX - 1); |
611 | 732 |
612 char distantHostAndPort[HOST_NAME_MAX]; | 733 char distantHostAndPort[HOST_NAME_MAX]; |
613 | 734 |
614 #ifdef _MSC_VER | 735 #ifdef _MSC_VER |
615 _snprintf | 736 _snprintf |
616 #else | 737 #else |
617 snprintf | 738 snprintf |
618 #endif | 739 #endif |
619 (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); | 740 (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); |
620 | 741 |
621 Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort)); | 742 Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort)); |
622 | 743 |
623 // Set various options | 744 // Set various options |
624 Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); | 745 Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); |
745 void DicomUserConnection::SetConnectionTimeout(uint32_t seconds) | 866 void DicomUserConnection::SetConnectionTimeout(uint32_t seconds) |
746 { | 867 { |
747 dcmConnectionTimeout.set(seconds); | 868 dcmConnectionTimeout.set(seconds); |
748 } | 869 } |
749 | 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 | |
750 } | 927 } |