Mercurial > hg > orthanc
comparison OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp @ 4465:fe774d8e904b
New configuration option: "DicomScuPreferredTransferSyntax" to control transcoding in C-STORE SCU
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 21 Jan 2021 17:08:32 +0100 |
parents | d9473bd5ed43 |
children | 2243f1bb909b |
comparison
equal
deleted
inserted
replaced
4464:e8c7be7a02a9 | 4465:fe774d8e904b |
---|---|
29 #include "../OrthancException.h" | 29 #include "../OrthancException.h" |
30 #include "DicomAssociation.h" | 30 #include "DicomAssociation.h" |
31 | 31 |
32 #include <dcmtk/dcmdata/dcdeftag.h> | 32 #include <dcmtk/dcmdata/dcdeftag.h> |
33 | 33 |
34 #include <list> | |
35 | |
34 | 36 |
35 namespace Orthanc | 37 namespace Orthanc |
36 { | 38 { |
37 static void ProgressCallback(void * /*callbackData*/, | 39 static void ProgressCallback(void * /*callbackData*/, |
38 T_DIMSE_StoreProgress *progress, | 40 T_DIMSE_StoreProgress *progress, |
47 } | 49 } |
48 } | 50 } |
49 | 51 |
50 | 52 |
51 bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid, | 53 bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid, |
52 const std::set<DicomTransferSyntax>& syntaxes) | 54 const std::set<DicomTransferSyntax>& sourceSyntaxes, |
53 { | 55 bool hasPreferred, |
54 // Default transfer syntax for DICOM | 56 DicomTransferSyntax preferred) |
55 const bool addLittleEndianImplicit = ( | 57 { |
56 proposeUncompressedSyntaxes_ && | 58 typedef std::list< std::set<DicomTransferSyntax> > GroupsOfSyntaxes; |
57 syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end()); | 59 |
58 | 60 GroupsOfSyntaxes groups; |
59 const bool addLittleEndianExplicit = ( | 61 |
60 proposeUncompressedSyntaxes_ && | 62 // Firstly, add one group for each individual transfer syntax |
61 syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end()); | 63 for (std::set<DicomTransferSyntax>::const_iterator |
62 | 64 it = sourceSyntaxes.begin(); it != sourceSyntaxes.end(); ++it) |
63 const bool addBigEndianExplicit = ( | 65 { |
64 proposeUncompressedSyntaxes_ && | 66 std::set<DicomTransferSyntax> group; |
65 proposeRetiredBigEndian_ && | 67 group.insert(*it); |
66 syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end()); | 68 groups.push_back(group); |
67 | 69 } |
68 size_t requiredCount = syntaxes.size(); | 70 |
69 if (addLittleEndianImplicit) | 71 // Secondly, add one group with the preferred transfer syntax |
70 { | 72 if (hasPreferred && |
71 requiredCount += 1; | 73 sourceSyntaxes.find(preferred) == sourceSyntaxes.end()) |
72 } | 74 { |
73 | 75 std::set<DicomTransferSyntax> group; |
74 if (addLittleEndianExplicit || | 76 group.insert(preferred); |
75 addBigEndianExplicit) | 77 groups.push_back(group); |
76 { | 78 } |
77 requiredCount += 1; | 79 |
78 } | 80 // Thirdly, add all the uncompressed transfer syntaxes as one single group |
79 | 81 if (proposeUncompressedSyntaxes_) |
80 if (association_->GetRemainingPropositions() <= requiredCount) | 82 { |
83 static const size_t N = 3; | |
84 static const DicomTransferSyntax UNCOMPRESSED_SYNTAXES[N] = { | |
85 DicomTransferSyntax_LittleEndianImplicit, | |
86 DicomTransferSyntax_LittleEndianExplicit, | |
87 DicomTransferSyntax_BigEndianExplicit | |
88 }; | |
89 | |
90 std::set<DicomTransferSyntax> group; | |
91 | |
92 for (size_t i = 0; i < N; i++) | |
93 { | |
94 DicomTransferSyntax syntax = UNCOMPRESSED_SYNTAXES[i]; | |
95 if (sourceSyntaxes.find(syntax) == sourceSyntaxes.end() && | |
96 (!hasPreferred || preferred != syntax)) | |
97 { | |
98 group.insert(syntax); | |
99 } | |
100 } | |
101 | |
102 if (!group.empty()) | |
103 { | |
104 groups.push_back(group); | |
105 } | |
106 } | |
107 | |
108 // Now, propose each of these groups of transfer syntaxes | |
109 if (association_->GetRemainingPropositions() <= groups.size()) | |
81 { | 110 { |
82 return false; // Not enough room | 111 return false; // Not enough room |
83 } | 112 } |
84 else | 113 else |
85 { | 114 { |
86 for (std::set<DicomTransferSyntax>::const_iterator | 115 for (GroupsOfSyntaxes::const_iterator it = groups.begin(); it != groups.end(); ++it) |
87 it = syntaxes.begin(); it != syntaxes.end(); ++it) | |
88 { | 116 { |
89 association_->ProposePresentationContext(sopClassUid, *it); | 117 association_->ProposePresentationContext(sopClassUid, *it); |
90 proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it)); | 118 |
91 } | 119 // Remember the syntaxes that were individually proposed, in |
92 | 120 // order to avoid renegociation if they are seen again (**) |
93 if (addLittleEndianImplicit) | 121 if (it->size() == 1) |
94 { | 122 { |
95 association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit); | 123 DicomTransferSyntax syntax = *it->begin(); |
96 proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit)); | 124 proposedOriginalClasses_.insert(std::make_pair(sopClassUid, syntax)); |
97 } | |
98 | |
99 if (addLittleEndianExplicit || | |
100 addBigEndianExplicit) | |
101 { | |
102 std::set<DicomTransferSyntax> uncompressed; | |
103 | |
104 if (addLittleEndianExplicit) | |
105 { | |
106 uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit); | |
107 } | |
108 | |
109 if (addBigEndianExplicit) | |
110 { | |
111 uncompressed.insert(DicomTransferSyntax_BigEndianExplicit); | |
112 } | |
113 | |
114 association_->ProposePresentationContext(sopClassUid, uncompressed); | |
115 | |
116 assert(!uncompressed.empty()); | |
117 if (addLittleEndianExplicit ^ addBigEndianExplicit) | |
118 { | |
119 // Only one transfer syntax was proposed for this presentation context | |
120 assert(uncompressed.size() == 1); | |
121 proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *uncompressed.begin())); | |
122 } | 125 } |
123 } | 126 } |
124 | 127 |
125 return true; | 128 return true; |
126 } | 129 } |
245 | 248 |
246 | 249 |
247 bool DicomStoreUserConnection::NegotiatePresentationContext( | 250 bool DicomStoreUserConnection::NegotiatePresentationContext( |
248 uint8_t& presentationContextId, | 251 uint8_t& presentationContextId, |
249 const std::string& sopClassUid, | 252 const std::string& sopClassUid, |
250 DicomTransferSyntax transferSyntax) | 253 DicomTransferSyntax transferSyntax, |
254 bool hasPreferred, | |
255 DicomTransferSyntax preferred) | |
251 { | 256 { |
252 /** | 257 /** |
253 * Step 1: Check whether this presentation context is already | 258 * Step 1: Check whether this presentation context is already |
254 * available in the previously negotiated assocation. | 259 * available in the previously negotiated assocation. |
255 **/ | 260 **/ |
263 if (association_->IsOpen()) | 268 if (association_->IsOpen()) |
264 { | 269 { |
265 CLOG(INFO, DICOM) << "Re-negotiating DICOM association with " | 270 CLOG(INFO, DICOM) << "Re-negotiating DICOM association with " |
266 << parameters_.GetRemoteModality().GetApplicationEntityTitle(); | 271 << parameters_.GetRemoteModality().GetApplicationEntityTitle(); |
267 | 272 |
273 // Don't renegociate if we know that the remote modality was | |
274 // already proposed this individual transfer syntax (**) | |
268 if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) != | 275 if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) != |
269 proposedOriginalClasses_.end()) | 276 proposedOriginalClasses_.end()) |
270 { | 277 { |
271 CLOG(INFO, DICOM) << "The remote modality has already rejected SOP class UID \"" | 278 CLOG(INFO, DICOM) << "The remote modality has already rejected SOP class UID \"" |
272 << sopClassUid << "\" with transfer syntax \"" | 279 << sopClassUid << "\" with transfer syntax \"" |
292 { | 299 { |
293 // Should never fail because of (*) | 300 // Should never fail because of (*) |
294 throw OrthancException(ErrorCode_InternalError); | 301 throw OrthancException(ErrorCode_InternalError); |
295 } | 302 } |
296 | 303 |
297 if (!ProposeStorageClass(sopClassUid, mandatory->second)) | 304 if (!ProposeStorageClass(sopClassUid, mandatory->second, hasPreferred, preferred)) |
298 { | 305 { |
299 // Should never happen in real life: There are no more than | 306 // Should never happen in real life: There are no more than |
300 // 128 transfer syntaxes in DICOM! | 307 // 128 transfer syntaxes in DICOM! |
301 throw OrthancException(ErrorCode_InternalError, | 308 throw OrthancException(ErrorCode_InternalError, |
302 "Too many transfer syntaxes for SOP class UID: " + sopClassUid); | 309 "Too many transfer syntaxes for SOP class UID: " + sopClassUid); |
312 for (RegisteredClasses::const_iterator it = registeredClasses_.begin(); | 319 for (RegisteredClasses::const_iterator it = registeredClasses_.begin(); |
313 it != registeredClasses_.end(); ++it) | 320 it != registeredClasses_.end(); ++it) |
314 { | 321 { |
315 if (it->first != sopClassUid) | 322 if (it->first != sopClassUid) |
316 { | 323 { |
317 ProposeStorageClass(it->first, it->second); | 324 ProposeStorageClass(it->first, it->second, hasPreferred, preferred); |
318 } | 325 } |
319 } | 326 } |
320 | 327 |
321 | 328 |
322 /** | 329 /** |
338 std::string c(dcmShortSCUStorageSOPClassUIDs[i]); | 345 std::string c(dcmShortSCUStorageSOPClassUIDs[i]); |
339 | 346 |
340 if (c != sopClassUid && | 347 if (c != sopClassUid && |
341 registeredClasses_.find(c) == registeredClasses_.end()) | 348 registeredClasses_.find(c) == registeredClasses_.end()) |
342 { | 349 { |
343 ProposeStorageClass(c, ts); | 350 ProposeStorageClass(c, ts, hasPreferred, preferred); |
344 } | 351 } |
345 } | 352 } |
346 } | 353 } |
347 | 354 |
348 | 355 |
365 { | 372 { |
366 DicomTransferSyntax transferSyntax; | 373 DicomTransferSyntax transferSyntax; |
367 LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom); | 374 LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom); |
368 | 375 |
369 uint8_t presID; | 376 uint8_t presID; |
370 if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax)) | 377 if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax, proposeUncompressedSyntaxes_, |
378 DicomTransferSyntax_LittleEndianExplicit)) | |
371 { | 379 { |
372 throw OrthancException(ErrorCode_NetworkProtocol, | 380 throw OrthancException(ErrorCode_NetworkProtocol, |
373 "No valid presentation context was negotiated for " | 381 "No valid presentation context was negotiated for " |
374 "SOP class UID [" + sopClassUid + "] and transfer " | 382 "SOP class UID [" + sopClassUid + "] and transfer " |
375 "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] " | 383 "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] " |
463 } | 471 } |
464 | 472 |
465 | 473 |
466 void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes, | 474 void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes, |
467 const std::string& sopClassUid, | 475 const std::string& sopClassUid, |
468 DicomTransferSyntax sourceSyntax) | 476 DicomTransferSyntax sourceSyntax, |
477 bool hasPreferred, | |
478 DicomTransferSyntax preferred) | |
469 { | 479 { |
470 acceptedSyntaxes.clear(); | 480 acceptedSyntaxes.clear(); |
471 | 481 |
472 // Make sure a negotiation has already occurred for this transfer | 482 // Make sure a negotiation has already occurred for this transfer |
473 // syntax. We don't use the return code: Transcoding is possible | 483 // syntax. We don't use the return code: Transcoding is possible |
474 // even if the "sourceSyntax" is not supported. | 484 // even if the "sourceSyntax" is not supported. |
475 uint8_t presID; | 485 uint8_t presID; |
476 NegotiatePresentationContext(presID, sopClassUid, sourceSyntax); | 486 NegotiatePresentationContext(presID, sopClassUid, sourceSyntax, hasPreferred, preferred); |
477 | 487 |
478 std::map<DicomTransferSyntax, uint8_t> contexts; | 488 std::map<DicomTransferSyntax, uint8_t> contexts; |
479 if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid)) | 489 if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid)) |
480 { | 490 { |
481 for (std::map<DicomTransferSyntax, uint8_t>::const_iterator | 491 for (std::map<DicomTransferSyntax, uint8_t>::const_iterator |
490 void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */, | 500 void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */, |
491 std::string& sopInstanceUid /* out */, | 501 std::string& sopInstanceUid /* out */, |
492 IDicomTranscoder& transcoder, | 502 IDicomTranscoder& transcoder, |
493 const void* buffer, | 503 const void* buffer, |
494 size_t size, | 504 size_t size, |
505 DicomTransferSyntax preferredTransferSyntax, | |
495 bool hasMoveOriginator, | 506 bool hasMoveOriginator, |
496 const std::string& moveOriginatorAET, | 507 const std::string& moveOriginatorAET, |
497 uint16_t moveOriginatorID) | 508 uint16_t moveOriginatorID) |
498 { | 509 { |
499 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); | 510 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); |
501 dicom->getDataset() == NULL) | 512 dicom->getDataset() == NULL) |
502 { | 513 { |
503 throw OrthancException(ErrorCode_NullPointer); | 514 throw OrthancException(ErrorCode_NullPointer); |
504 } | 515 } |
505 | 516 |
506 DicomTransferSyntax inputSyntax; | 517 DicomTransferSyntax sourceSyntax; |
507 LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom); | 518 LookupParameters(sopClassUid, sopInstanceUid, sourceSyntax, *dicom); |
508 | 519 |
509 std::set<DicomTransferSyntax> accepted; | 520 std::set<DicomTransferSyntax> accepted; |
510 LookupTranscoding(accepted, sopClassUid, inputSyntax); | 521 LookupTranscoding(accepted, sopClassUid, sourceSyntax, true, preferredTransferSyntax); |
511 | 522 |
512 if (accepted.find(inputSyntax) != accepted.end()) | 523 if (accepted.find(sourceSyntax) != accepted.end()) |
513 { | 524 { |
514 // No need for transcoding | 525 // No need for transcoding |
515 Store(sopClassUid, sopInstanceUid, *dicom, | 526 Store(sopClassUid, sopInstanceUid, *dicom, |
516 hasMoveOriginator, moveOriginatorAET, moveOriginatorID); | 527 hasMoveOriginator, moveOriginatorAET, moveOriginatorID); |
517 } | 528 } |
518 else | 529 else |
519 { | 530 { |
520 // Transcoding is needed | 531 // Transcoding is needed |
521 std::set<DicomTransferSyntax> uncompressedSyntaxes; | |
522 | |
523 if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end()) | |
524 { | |
525 uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); | |
526 } | |
527 | |
528 if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end()) | |
529 { | |
530 uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); | |
531 } | |
532 | |
533 if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end()) | |
534 { | |
535 uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); | |
536 } | |
537 | |
538 IDicomTranscoder::DicomImage source; | 532 IDicomTranscoder::DicomImage source; |
539 source.AcquireParsed(dicom.release()); | 533 source.AcquireParsed(dicom.release()); |
540 source.SetExternalBuffer(buffer, size); | 534 source.SetExternalBuffer(buffer, size); |
541 | 535 |
542 const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed()); | 536 const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed()); |
543 | 537 |
544 IDicomTranscoder::DicomImage transcoded; | 538 IDicomTranscoder::DicomImage transcoded; |
545 if (transcoder.Transcode(transcoded, source, uncompressedSyntaxes, false)) | 539 bool success = false; |
546 { | 540 bool isDestructiveCompressionAllowed = false; |
547 if (sourceUid != IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed())) | 541 std::set<DicomTransferSyntax> attemptedSyntaxes; |
548 { | 542 |
549 throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP " | 543 if (accepted.find(preferredTransferSyntax) != accepted.end()) |
550 "instance UID while transcoding to an uncompressed transfer syntax"); | 544 { |
551 } | 545 // New in Orthanc 1.9.0: The preferred transfer syntax is |
552 else | 546 // accepted by the remote modality => transcode to this syntax |
553 { | 547 std::set<DicomTransferSyntax> targetSyntaxes; |
554 DicomTransferSyntax transcodedSyntax; | 548 targetSyntaxes.insert(preferredTransferSyntax); |
555 | 549 attemptedSyntaxes.insert(preferredTransferSyntax); |
556 // Sanity check | 550 |
557 if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) || | 551 success = transcoder.Transcode(transcoded, source, targetSyntaxes, true); |
558 accepted.find(transcodedSyntax) == accepted.end()) | 552 isDestructiveCompressionAllowed = true; |
553 } | |
554 | |
555 if (!success) | |
556 { | |
557 // Transcode to either one of the uncompressed transfer | |
558 // syntaxes that are accepted by the remote modality | |
559 | |
560 std::set<DicomTransferSyntax> targetSyntaxes; | |
561 | |
562 if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end()) | |
563 { | |
564 targetSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); | |
565 attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); | |
566 } | |
567 | |
568 if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end()) | |
569 { | |
570 targetSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); | |
571 attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); | |
572 } | |
573 | |
574 if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end()) | |
575 { | |
576 targetSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); | |
577 attemptedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); | |
578 } | |
579 | |
580 if (!targetSyntaxes.empty()) | |
581 { | |
582 success = transcoder.Transcode(transcoded, source, targetSyntaxes, false); | |
583 isDestructiveCompressionAllowed = false; | |
584 } | |
585 } | |
586 | |
587 if (success) | |
588 { | |
589 std::string targetUid = IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed()); | |
590 if (sourceUid != targetUid) | |
591 { | |
592 if (isDestructiveCompressionAllowed) | |
559 { | 593 { |
560 throw OrthancException(ErrorCode_InternalError); | 594 LOG(WARNING) << "Because of the use of a preferred transfer syntax that corresponds to " |
595 << "a destructive compression, C-STORE SCU has hanged the SOP Instance UID " | |
596 << "of a DICOM instance from \"" << sourceUid << "\" to \"" << targetUid << "\""; | |
561 } | 597 } |
562 else | 598 else |
563 { | 599 { |
564 Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(), | 600 throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP " |
565 hasMoveOriginator, moveOriginatorAET, moveOriginatorID); | 601 "Instance UID while transcoding to an uncompressed transfer syntax"); |
566 } | 602 } |
567 } | 603 } |
604 | |
605 DicomTransferSyntax transcodedSyntax; | |
606 | |
607 // Sanity check | |
608 if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) || | |
609 accepted.find(transcodedSyntax) == accepted.end()) | |
610 { | |
611 throw OrthancException(ErrorCode_InternalError); | |
612 } | |
613 else | |
614 { | |
615 Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(), | |
616 hasMoveOriginator, moveOriginatorAET, moveOriginatorID); | |
617 } | |
618 } | |
619 else | |
620 { | |
621 std::string s; | |
622 for (std::set<DicomTransferSyntax>::const_iterator | |
623 it = attemptedSyntaxes.begin(); it != attemptedSyntaxes.end(); ++it) | |
624 { | |
625 s += " " + std::string(GetTransferSyntaxUid(*it)); | |
626 } | |
627 | |
628 throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode from " + | |
629 std::string(GetTransferSyntaxUid(sourceSyntax)) + | |
630 " to one of [" + s + " ]"); | |
568 } | 631 } |
569 } | 632 } |
570 } | 633 } |
571 } | 634 } |