diff 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
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Wed Jan 20 17:43:15 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Thu Jan 21 17:08:32 2021 +0100
@@ -31,6 +31,8 @@
 
 #include <dcmtk/dcmdata/dcdeftag.h>
 
+#include <list>
+
 
 namespace Orthanc
 {
@@ -49,76 +51,77 @@
 
 
   bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid,
-                                                     const std::set<DicomTransferSyntax>& syntaxes)
+                                                     const std::set<DicomTransferSyntax>& sourceSyntaxes,
+                                                     bool hasPreferred,
+                                                     DicomTransferSyntax preferred)
   {
-    // Default transfer syntax for DICOM
-    const bool addLittleEndianImplicit = (
-      proposeUncompressedSyntaxes_ &&
-      syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end());
-    
-    const bool addLittleEndianExplicit = (
-      proposeUncompressedSyntaxes_ &&
-      syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end());
-    
-    const bool addBigEndianExplicit = (
-      proposeUncompressedSyntaxes_ &&
-      proposeRetiredBigEndian_ &&
-      syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end());
-    
-    size_t requiredCount = syntaxes.size();
-    if (addLittleEndianImplicit)
+    typedef std::list< std::set<DicomTransferSyntax> >  GroupsOfSyntaxes;
+
+    GroupsOfSyntaxes  groups;
+
+    // Firstly, add one group for each individual transfer syntax
+    for (std::set<DicomTransferSyntax>::const_iterator
+           it = sourceSyntaxes.begin(); it != sourceSyntaxes.end(); ++it)
+    {
+      std::set<DicomTransferSyntax> group;
+      group.insert(*it);
+      groups.push_back(group);
+    }
+
+    // Secondly, add one group with the preferred transfer syntax
+    if (hasPreferred &&
+        sourceSyntaxes.find(preferred) == sourceSyntaxes.end())
+    {
+      std::set<DicomTransferSyntax> group;
+      group.insert(preferred);
+      groups.push_back(group);
+    }
+
+    // Thirdly, add all the uncompressed transfer syntaxes as one single group
+    if (proposeUncompressedSyntaxes_)
     {
-      requiredCount += 1;
+      static const size_t N = 3;
+      static const DicomTransferSyntax UNCOMPRESSED_SYNTAXES[N] = {
+        DicomTransferSyntax_LittleEndianImplicit,
+        DicomTransferSyntax_LittleEndianExplicit,
+        DicomTransferSyntax_BigEndianExplicit
+      };
+
+      std::set<DicomTransferSyntax> group;
+
+      for (size_t i = 0; i < N; i++)
+      {
+        DicomTransferSyntax syntax = UNCOMPRESSED_SYNTAXES[i];
+        if (sourceSyntaxes.find(syntax) == sourceSyntaxes.end() &&
+            (!hasPreferred || preferred != syntax))
+        {
+          group.insert(syntax);
+        }
+      }
+
+      if (!group.empty())
+      {
+        groups.push_back(group);
+      }
     }
-      
-    if (addLittleEndianExplicit ||
-        addBigEndianExplicit)
-    {
-      requiredCount += 1;
-    }
-      
-    if (association_->GetRemainingPropositions() <= requiredCount)
+
+    // Now, propose each of these groups of transfer syntaxes
+    if (association_->GetRemainingPropositions() <= groups.size())
     {
       return false;  // Not enough room
     }
     else
     {
-      for (std::set<DicomTransferSyntax>::const_iterator
-             it = syntaxes.begin(); it != syntaxes.end(); ++it)
+      for (GroupsOfSyntaxes::const_iterator it = groups.begin(); it != groups.end(); ++it)
       {
         association_->ProposePresentationContext(sopClassUid, *it);
-        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it));
-      }
 
-      if (addLittleEndianImplicit)
-      {
-        association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit);
-        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit));
-      }
-
-      if (addLittleEndianExplicit ||
-          addBigEndianExplicit)
-      {
-        std::set<DicomTransferSyntax> uncompressed;
-
-        if (addLittleEndianExplicit)
+        // Remember the syntaxes that were individually proposed, in
+        // order to avoid renegociation if they are seen again (**)
+        if (it->size() == 1)
         {
-          uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit);
-        }
-
-        if (addBigEndianExplicit)
-        {
-          uncompressed.insert(DicomTransferSyntax_BigEndianExplicit);
-        }
-
-        association_->ProposePresentationContext(sopClassUid, uncompressed);
-
-        assert(!uncompressed.empty());
-        if (addLittleEndianExplicit ^ addBigEndianExplicit)
-        {
-          // Only one transfer syntax was proposed for this presentation context
-          assert(uncompressed.size() == 1);
-          proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *uncompressed.begin()));
+          DicomTransferSyntax syntax = *it->begin();
+          proposedOriginalClasses_.insert(std::make_pair(sopClassUid, syntax));
         }
       }
 
@@ -247,7 +250,9 @@
   bool DicomStoreUserConnection::NegotiatePresentationContext(
     uint8_t& presentationContextId,
     const std::string& sopClassUid,
-    DicomTransferSyntax transferSyntax)
+    DicomTransferSyntax transferSyntax,
+    bool hasPreferred,
+    DicomTransferSyntax preferred)
   {
     /**
      * Step 1: Check whether this presentation context is already
@@ -265,6 +270,8 @@
       CLOG(INFO, DICOM) << "Re-negotiating DICOM association with "
                         << parameters_.GetRemoteModality().GetApplicationEntityTitle();
 
+      // Don't renegociate if we know that the remote modality was
+      // already proposed this individual transfer syntax (**)
       if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) !=
           proposedOriginalClasses_.end())
       {
@@ -294,7 +301,7 @@
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      if (!ProposeStorageClass(sopClassUid, mandatory->second))
+      if (!ProposeStorageClass(sopClassUid, mandatory->second, hasPreferred, preferred))
       {
         // Should never happen in real life: There are no more than
         // 128 transfer syntaxes in DICOM!
@@ -314,7 +321,7 @@
     {
       if (it->first != sopClassUid)
       {
-        ProposeStorageClass(it->first, it->second);
+        ProposeStorageClass(it->first, it->second, hasPreferred, preferred);
       }
     }
       
@@ -340,7 +347,7 @@
         if (c != sopClassUid &&
             registeredClasses_.find(c) == registeredClasses_.end())
         {
-          ProposeStorageClass(c, ts);
+          ProposeStorageClass(c, ts, hasPreferred, preferred);
         }
       }
     }
@@ -367,7 +374,8 @@
     LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom);
 
     uint8_t presID;
-    if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax))
+    if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax, proposeUncompressedSyntaxes_,
+                                      DicomTransferSyntax_LittleEndianExplicit))
     {
       throw OrthancException(ErrorCode_NetworkProtocol,
                              "No valid presentation context was negotiated for "
@@ -465,7 +473,9 @@
 
   void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
                                                    const std::string& sopClassUid,
-                                                   DicomTransferSyntax sourceSyntax)
+                                                   DicomTransferSyntax sourceSyntax,
+                                                   bool hasPreferred,
+                                                   DicomTransferSyntax preferred)
   {
     acceptedSyntaxes.clear();
 
@@ -473,7 +483,7 @@
     // syntax. We don't use the return code: Transcoding is possible
     // even if the "sourceSyntax" is not supported.
     uint8_t presID;
-    NegotiatePresentationContext(presID, sopClassUid, sourceSyntax);
+    NegotiatePresentationContext(presID, sopClassUid, sourceSyntax, hasPreferred, preferred);
 
     std::map<DicomTransferSyntax, uint8_t> contexts;
     if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid))
@@ -492,6 +502,7 @@
                                            IDicomTranscoder& transcoder,
                                            const void* buffer,
                                            size_t size,
+                                           DicomTransferSyntax preferredTransferSyntax,
                                            bool hasMoveOriginator,
                                            const std::string& moveOriginatorAET,
                                            uint16_t moveOriginatorID)
@@ -503,13 +514,13 @@
       throw OrthancException(ErrorCode_NullPointer);
     }
 
-    DicomTransferSyntax inputSyntax;
-    LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
+    DicomTransferSyntax sourceSyntax;
+    LookupParameters(sopClassUid, sopInstanceUid, sourceSyntax, *dicom);
 
     std::set<DicomTransferSyntax> accepted;
-    LookupTranscoding(accepted, sopClassUid, inputSyntax);
+    LookupTranscoding(accepted, sopClassUid, sourceSyntax, true, preferredTransferSyntax);
 
-    if (accepted.find(inputSyntax) != accepted.end())
+    if (accepted.find(sourceSyntax) != accepted.end())
     {
       // No need for transcoding
       Store(sopClassUid, sopInstanceUid, *dicom,
@@ -518,54 +529,106 @@
     else
     {
       // Transcoding is needed
-      std::set<DicomTransferSyntax> uncompressedSyntaxes;
-
-      if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
-      {
-        uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
-      }
-
-      if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
-      {
-        uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-      }
-
-      if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
-      {
-        uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
-      }
-
       IDicomTranscoder::DicomImage source;
       source.AcquireParsed(dicom.release());
       source.SetExternalBuffer(buffer, size);
 
       const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed());
-      
+        
       IDicomTranscoder::DicomImage transcoded;
-      if (transcoder.Transcode(transcoded, source, uncompressedSyntaxes, false))
+      bool success = false;
+      bool isDestructiveCompressionAllowed = false;
+      std::set<DicomTransferSyntax> attemptedSyntaxes;
+
+      if (accepted.find(preferredTransferSyntax) != accepted.end())
+      {
+        // New in Orthanc 1.9.0: The preferred transfer syntax is
+        // accepted by the remote modality => transcode to this syntax
+        std::set<DicomTransferSyntax> targetSyntaxes;
+        targetSyntaxes.insert(preferredTransferSyntax);
+        attemptedSyntaxes.insert(preferredTransferSyntax);
+
+        success = transcoder.Transcode(transcoded, source, targetSyntaxes, true);
+        isDestructiveCompressionAllowed = true;
+      }
+
+      if (!success)
       {
-        if (sourceUid != IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed()))
+        // Transcode to either one of the uncompressed transfer
+        // syntaxes that are accepted by the remote modality
+
+        std::set<DicomTransferSyntax> targetSyntaxes;
+
+        if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
+        {
+          targetSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+          attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+        }
+
+        if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
+        {
+          targetSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
+          attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
+        }
+
+        if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
+        {
+          targetSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
+          attemptedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
+        }
+
+        if (!targetSyntaxes.empty())
         {
-          throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
-                                 "instance UID while transcoding to an uncompressed transfer syntax");
+          success = transcoder.Transcode(transcoded, source, targetSyntaxes, false);
+          isDestructiveCompressionAllowed = false;
+        }
+      }
+
+      if (success)
+      {
+        std::string targetUid = IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed());
+        if (sourceUid != targetUid)
+        {
+          if (isDestructiveCompressionAllowed)
+          {
+            LOG(WARNING) << "Because of the use of a preferred transfer syntax that corresponds to "
+                         << "a destructive compression, C-STORE SCU has hanged the SOP Instance UID "
+                         << "of a DICOM instance from \"" << sourceUid << "\" to \"" << targetUid << "\"";
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
+                                   "Instance UID while transcoding to an uncompressed transfer syntax");
+          }
+        }
+
+        DicomTransferSyntax transcodedSyntax;
+          
+        // Sanity check
+        if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) ||
+            accepted.find(transcodedSyntax) == accepted.end())
+        {
+          throw OrthancException(ErrorCode_InternalError);
         }
         else
         {
-          DicomTransferSyntax transcodedSyntax;
-          
-          // Sanity check
-          if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) ||
-              accepted.find(transcodedSyntax) == accepted.end())
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
-          else
-          {
-            Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(),
-                  hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
-          }
+          Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(),
+                hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
         }
       }
+      else
+      {
+        std::string s;
+        for (std::set<DicomTransferSyntax>::const_iterator
+               it = attemptedSyntaxes.begin(); it != attemptedSyntaxes.end(); ++it)
+        {
+          s += " " + std::string(GetTransferSyntaxUid(*it));
+        }
+        
+        throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode from " +
+                               std::string(GetTransferSyntaxUid(sourceSyntax)) +
+                               " to one of [" + s + " ]");
+      }
     }
   }
 }