changeset 5899:f622e5964cfa get-scu

Get-SCU: proposed TS + Get-SCP: accept more transcoding
author Alain Mazy <am@orthanc.team>
date Tue, 03 Dec 2024 15:19:55 +0100
parents ed74c56db02f
children dfd5effec064
files NEWS OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp OrthancFramework/Sources/DicomNetworking/DicomAssociation.h OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h OrthancServer/Sources/QueryRetrieveHandler.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp OrthancServer/Sources/main.cpp
diffstat 14 files changed, 94 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Dec 02 15:49:03 2024 +0100
+++ b/NEWS	Tue Dec 03 15:19:55 2024 +0100
@@ -33,6 +33,8 @@
   - When opening a DICOM SCU connection, Orthanc now only proposes the contexts that it is
     going to use in the connection and not all contexts as in previous versions.  E.g, when
     performing a C-ECHO, Orthanc will not propose C-MOVE or C-FIND.
+* DICOM Get-SCP: Orthanc won't refuse anymore to send e.g. a LittleEndianExplicit file when
+  the accepted transfer syntax is a compressed one.
 * Introduced a new thread to update the statistics at regular interval for the
   DB plugins that are implementing the UpdateAndGetStatistics function (currently only
   PostgreSQL).  This avoids very long update times in case you don't call /statistics
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -346,12 +346,12 @@
       assert(presentationContextId <= 255);
       const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str();
 
-      const std::set<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
+      const std::list<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
           
       std::vector<const char*> transferSyntaxes;
       transferSyntaxes.reserve(source.size());
           
-      for (std::set<DicomTransferSyntax>::const_iterator
+      for (std::list<DicomTransferSyntax>::const_iterator
              it = source.begin(); it != source.end(); ++it)
       {
         transferSyntaxes.push_back(GetTransferSyntaxUid(*it));
@@ -454,10 +454,10 @@
   void DicomAssociation::ProposeGenericPresentationContext(const std::string& abstractSyntax,
                                                            DicomAssociationRole role)
   {
-    std::set<DicomTransferSyntax> ts;
-    ts.insert(DicomTransferSyntax_LittleEndianImplicit);
-    ts.insert(DicomTransferSyntax_LittleEndianExplicit);
-    ts.insert(DicomTransferSyntax_BigEndianExplicit);  // Retired
+    std::list<DicomTransferSyntax> ts;
+    ts.push_back(DicomTransferSyntax_LittleEndianExplicit); // the most standard one first !
+    ts.push_back(DicomTransferSyntax_LittleEndianImplicit);
+    ts.push_back(DicomTransferSyntax_BigEndianExplicit);  // Retired but was historicaly proposed by Orthanc
     ProposePresentationContext(abstractSyntax, ts, role);
   }
     
@@ -478,8 +478,8 @@
                                                     DicomTransferSyntax transferSyntax,
                                                     DicomAssociationRole role)
   {
-    std::set<DicomTransferSyntax> ts;
-    ts.insert(transferSyntax);
+    std::list<DicomTransferSyntax> ts;
+    ts.push_back(transferSyntax);
     ProposePresentationContext(abstractSyntax, ts, role);
   }
 
@@ -491,7 +491,7 @@
     
   void DicomAssociation::ProposePresentationContext(
     const std::string& abstractSyntax,
-    const std::set<DicomTransferSyntax>& transferSyntaxes)
+    const std::list<DicomTransferSyntax>& transferSyntaxes)
   {
     ProposePresentationContext(abstractSyntax, transferSyntaxes, DicomAssociationRole_Default);
   }
@@ -499,7 +499,7 @@
 
   void DicomAssociation::ProposePresentationContext(
     const std::string& abstractSyntax,
-    const std::set<DicomTransferSyntax>& transferSyntaxes,
+    const std::list<DicomTransferSyntax>& transferSyntaxes,
     DicomAssociationRole role)
   {
     if (transferSyntaxes.empty())
@@ -694,9 +694,9 @@
     DicomAssociation association;
 
     {
-      std::set<DicomTransferSyntax> transferSyntaxes;
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+      std::list<DicomTransferSyntax> transferSyntaxes;
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianExplicit);
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianImplicit);
 
       association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass,
                                              transferSyntaxes, DicomAssociationRole_Scp);
@@ -872,9 +872,9 @@
     DicomAssociation association;
 
     {
-      std::set<DicomTransferSyntax> transferSyntaxes;
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-      transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+      std::list<DicomTransferSyntax> transferSyntaxes;
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianExplicit);
+      transferSyntaxes.push_back(DicomTransferSyntax_LittleEndianImplicit);
       
       // association.SetRole(DicomAssociationRole_Default);
       association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass,
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.h	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.h	Tue Dec 03 15:19:55 2024 +0100
@@ -44,6 +44,7 @@
 #include <stdint.h>   // For uint8_t
 #include <boost/noncopyable.hpp>
 #include <set>
+#include <list>
 
 namespace Orthanc
 {
@@ -58,7 +59,7 @@
     struct ProposedPresentationContext
     {
       std::string                    abstractSyntax_;
-      std::set<DicomTransferSyntax>  transferSyntaxes_;
+      std::list<DicomTransferSyntax> transferSyntaxes_;
       DicomAssociationRole           role_;
     };
 
@@ -121,11 +122,11 @@
 
     void ProposePresentationContext(
       const std::string& abstractSyntax,
-      const std::set<DicomTransferSyntax>& transferSyntaxes);
+      const std::list<DicomTransferSyntax>& transferSyntaxes);
 
     void ProposePresentationContext(
       const std::string& abstractSyntax,
-      const std::set<DicomTransferSyntax>& transferSyntaxes,
+      const std::list<DicomTransferSyntax>& transferSyntaxes,
       DicomAssociationRole role);
 
     T_ASC_Association& GetDcmtkAssociation() const;
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -237,7 +237,7 @@
   void DicomControlUserConnection::SetupPresentationContexts(
     ScuOperationFlags scuOperation,
     const std::set<std::string>& acceptedStorageSopClasses,
-    const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes)
+    const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes)
   {
     assert(association_.get() != NULL);
 
@@ -283,7 +283,7 @@
 
       for (std::set<std::string>::const_iterator it = acceptedStorageSopClasses.begin(); it != acceptedStorageSopClasses.end(); ++it)
       {
-        association_->ProposePresentationContext(*it, acceptedTransferSyntaxes, DicomAssociationRole_Scp);
+        association_->ProposePresentationContext(*it, proposedStorageTransferSyntaxes, DicomAssociationRole_Scp);
       }
     }
   }
@@ -708,7 +708,7 @@
   {
     assert((scuOperation & ScuOperationFlags_Get) == 0);  // you must provide acceptedStorageSopClassUids for Get SCU
     std::set<std::string> emptyStorageSopClasses;
-    std::set<DicomTransferSyntax> emptyStorageTransferSyntaxes;
+    std::list<DicomTransferSyntax> emptyStorageTransferSyntaxes;
 
     SetupPresentationContexts(scuOperation, emptyStorageSopClasses, emptyStorageTransferSyntaxes);
   }
@@ -716,11 +716,11 @@
   DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params, 
                                                          ScuOperationFlags scuOperation,
                                                          const std::set<std::string>& acceptedStorageSopClasses,
-                                                         const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes) :
+                                                         const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes) :
     parameters_(params),
     association_(new DicomAssociation)
   {
-    SetupPresentationContexts(scuOperation, acceptedStorageSopClasses, acceptedTransferSyntaxes);
+    SetupPresentationContexts(scuOperation, acceptedStorageSopClasses, proposedStorageTransferSyntaxes);
   }
     
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h	Tue Dec 03 15:19:55 2024 +0100
@@ -69,7 +69,7 @@
 
     void SetupPresentationContexts(ScuOperationFlags scuOperation,
                                    const std::set<std::string>& acceptedStorageSopClasses,
-                                   const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes); // TODO-GET: in order of preference ?
+                                   const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes);
 
     void FindInternal(DicomFindAnswers& answers,
                       DcmDataset* dataset,
@@ -88,7 +88,7 @@
     explicit DicomControlUserConnection(const DicomAssociationParameters& params, 
                                         ScuOperationFlags scuOperation,
                                         const std::set<std::string>& acceptedStorageSopClasses,
-                                        const std::set<DicomTransferSyntax>& acceptedTransferSyntaxes); // TODO-GET: in order of preference ?
+                                        const std::list<DicomTransferSyntax>& proposedStorageTransferSyntaxes);
 
     const DicomAssociationParameters& GetParameters() const
     {
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -57,7 +57,7 @@
                                                      bool hasPreferred,
                                                      DicomTransferSyntax preferred)
   {
-    typedef std::list< std::set<DicomTransferSyntax> >  GroupsOfSyntaxes;
+    typedef std::list< std::list<DicomTransferSyntax> >  GroupsOfSyntaxes;
 
     GroupsOfSyntaxes  groups;
 
@@ -65,8 +65,8 @@
     for (std::set<DicomTransferSyntax>::const_iterator
            it = sourceSyntaxes.begin(); it != sourceSyntaxes.end(); ++it)
     {
-      std::set<DicomTransferSyntax> group;
-      group.insert(*it);
+      std::list<DicomTransferSyntax> group;
+      group.push_back(*it);
       groups.push_back(group);
     }
 
@@ -74,8 +74,8 @@
     if (hasPreferred &&
         sourceSyntaxes.find(preferred) == sourceSyntaxes.end())
     {
-      std::set<DicomTransferSyntax> group;
-      group.insert(preferred);
+      std::list<DicomTransferSyntax> group;
+      group.push_back(preferred);
       groups.push_back(group);
     }
 
@@ -89,7 +89,7 @@
         DicomTransferSyntax_BigEndianExplicit
       };
 
-      std::set<DicomTransferSyntax> group;
+      std::list<DicomTransferSyntax> group;
 
       for (size_t i = 0; i < N; i++)
       {
@@ -97,7 +97,7 @@
         if (sourceSyntaxes.find(syntax) == sourceSyntaxes.end() &&
             (!hasPreferred || preferred != syntax))
         {
-          group.insert(syntax);
+          group.push_back(syntax);
         }
       }
 
--- a/OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h	Tue Dec 03 15:19:55 2024 +0100
@@ -28,6 +28,7 @@
 
 #include <boost/noncopyable.hpp>
 #include <string>
+#include <list>
 
 namespace Orthanc
 {
@@ -47,11 +48,18 @@
                                   const std::string& calledAet,
                                   DicomRequestType type) = 0;
 
+    // Get the set of TransferSyntaxes that are accepted when negotiation a C-Store association, acting as SCP when it has been initiated by the C-Store SCU.
     virtual void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target,
                                              const std::string& remoteIp,
                                              const std::string& remoteAet,
                                              const std::string& calledAet) = 0;
-    
+
+    // Get the list of TransferSyntaxes that are proposed when initiating a C-Store SCP which actually only happens in a C-Get SCU
+    virtual void GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& target,
+                                                    const std::string& remoteIp,
+                                                    const std::string& remoteAet,
+                                                    const std::string& calledAet) = 0;
+
     virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
                                            const std::string& remoteAet,
                                            const std::string& calledAet) = 0;
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -201,6 +201,18 @@
 }
 
 
+void DicomFilter::GetProposedStorageTransferSyntaxes(std::list<Orthanc::DicomTransferSyntax>& target,
+                                                     const std::string& remoteIp,
+                                                     const std::string& remoteAet,
+                                                     const std::string& calledAet)
+{
+  // default TS
+  target.push_back(Orthanc::DicomTransferSyntax_LittleEndianExplicit);
+  target.push_back(Orthanc::DicomTransferSyntax_LittleEndianImplicit);
+  target.push_back(Orthanc::DicomTransferSyntax_BigEndianExplicit);
+}
+
+
 bool DicomFilter::IsUnknownSopClassAccepted(const std::string& remoteIp,
                                             const std::string& remoteAet,
                                             const std::string& calledAet)
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h	Tue Dec 03 15:19:55 2024 +0100
@@ -64,6 +64,12 @@
                                            const std::string& remoteAet,
                                            const std::string& calledAet) ORTHANC_OVERRIDE;
 
+  virtual void GetProposedStorageTransferSyntaxes(std::list<Orthanc::DicomTransferSyntax>& target,
+                                                  const std::string& remoteIp,
+                                                  const std::string& remoteAet,
+                                                  const std::string& calledAet) ORTHANC_OVERRIDE;
+
+
   virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
                                          const std::string& remoteAet,
                                          const std::string& calledAet) ORTHANC_OVERRIDE;
--- a/OrthancServer/Sources/QueryRetrieveHandler.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Sources/QueryRetrieveHandler.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -79,13 +79,7 @@
           params.SetTimeout(timeout_);
         }
         
-        std::set<std::string> acceptedStorageClasses;
-        std::set<DicomTransferSyntax> acceptedTransferSyntaxes;
-        
-        context_.GetAcceptedSopClasses(acceptedStorageClasses, 120); // limit to 120 SOP Classes since there are 128 presentation contexts available.
-        context_.GetAcceptedTransferSyntaxes(acceptedTransferSyntaxes);
-
-        DicomControlUserConnection connection(params, static_cast<ScuOperationFlags>(ScuOperationFlags_Find | ScuOperationFlags_Move | ScuOperationFlags_Get), acceptedStorageClasses, acceptedTransferSyntaxes);
+        DicomControlUserConnection connection(params, static_cast<ScuOperationFlags>(ScuOperationFlags_Find));
         connection.Find(answers_, level_, fixed, findNormalized_);
       }
 
--- a/OrthancServer/Sources/ServerContext.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -2241,6 +2241,24 @@
   }
 
 
+  void ServerContext::GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& syntaxes) const
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    
+    // // TODO: investigate: actually, neither Orthanc 1.12.4 nor DCM4CHEE will accept to send a LittleEndianExplicit file
+    // //                    while e.g., Jpeg-LS has been presented (and accepted) as the preferred TS for the C-Store SCP.
+    // // if we have defined IngestTranscoding, let's propose this TS first to avoid any unnecessary transcoding
+    // if (isIngestTranscoding_)
+    // {
+    //   syntaxes.push_back(ingestTransferSyntax_);
+    // }
+    
+    // then, propose the default ones
+    syntaxes.push_back(DicomTransferSyntax_LittleEndianExplicit);
+    syntaxes.push_back(DicomTransferSyntax_LittleEndianImplicit);
+  }
+  
+
   bool ServerContext::IsUnknownSopClassAccepted() const
   {
     boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
--- a/OrthancServer/Sources/ServerContext.h	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Sources/ServerContext.h	Tue Dec 03 15:19:55 2024 +0100
@@ -597,6 +597,8 @@
 
     void SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax>& syntaxes);
 
+    void GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& syntaxes) const;
+
     void SetAcceptedSopClasses(const std::list<std::string>& acceptedSopClasses,
                                const std::set<std::string>& rejectedSopClasses);
 
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -116,7 +116,7 @@
       std::set<std::string> sopClassesToPropose;
       std::set<std::string> sopClassesInStudy;
       std::set<std::string> acceptedSopClasses;
-      std::set<DicomTransferSyntax> storageAcceptedTransferSyntaxes;
+      std::list<DicomTransferSyntax> proposedTransferSyntaxes;
 
       if (findAnswer.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) &&
           findAnswer.LookupStringValues(sopClassesInStudy, DICOM_TAG_SOP_CLASSES_IN_STUDY, false))
@@ -138,12 +138,12 @@
         throw OrthancException(ErrorCode_NoPresentationContext, "Cannot perform C-Get, no SOPClassUID have been accepted by Orthanc.");        
       }
 
-      context_.GetAcceptedTransferSyntaxes(storageAcceptedTransferSyntaxes);
+      context_.GetProposedStorageTransferSyntaxes(proposedTransferSyntaxes);
 
       connection_.reset(new DicomControlUserConnection(parameters_, 
                                                        ScuOperationFlags_Get, 
                                                        sopClassesToPropose,
-                                                       storageAcceptedTransferSyntaxes));
+                                                       proposedTransferSyntaxes));
     }
     
     connection_->Get(findAnswer, InstanceReceivedHandler, &context_);
--- a/OrthancServer/Sources/main.cpp	Mon Dec 02 15:49:03 2024 +0100
+++ b/OrthancServer/Sources/main.cpp	Tue Dec 03 15:19:55 2024 +0100
@@ -494,6 +494,13 @@
     context_.GetAcceptedTransferSyntaxes(target);
   }
 
+  virtual void GetProposedStorageTransferSyntaxes(std::list<DicomTransferSyntax>& target,
+                                                  const std::string& remoteIp,
+                                                  const std::string& remoteAet,
+                                                  const std::string& calledAet) ORTHANC_OVERRIDE
+  {
+    context_.GetProposedStorageTransferSyntaxes(target);
+  }
   
   virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
                                          const std::string& remoteAet,