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 }