changeset 3894:8f7ad4989fec transcoding

transcoding to uncompressed transfer syntaxes over DICOM protocol is implemented
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 07 May 2020 11:13:29 +0200
parents 7a5fa8f307e9
children 210af28c4087
files Core/DicomNetworking/DicomStoreUserConnection.cpp Core/DicomNetworking/DicomStoreUserConnection.h Core/DicomNetworking/RemoteModalityParameters.cpp Core/DicomNetworking/RemoteModalityParameters.h Core/DicomParsing/DcmtkTranscoder.cpp Core/DicomParsing/DcmtkTranscoder.h Core/DicomParsing/IDicomTranscoder.h Core/DicomParsing/MemoryBufferTranscoder.cpp Core/DicomParsing/MemoryBufferTranscoder.h OrthancServer/LuaScripting.cpp OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerJobs/DicomModalityStoreJob.cpp OrthancServer/ServerJobs/LuaJobManager.cpp OrthancServer/ServerJobs/LuaJobManager.h OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp OrthancServer/ServerJobs/Operations/StoreScuOperation.h OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Resources/Configuration.json UnitTestsSources/FromDcmtkTests.cpp UnitTestsSources/MultiThreadingTests.cpp
diffstat 25 files changed, 302 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/DicomStoreUserConnection.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp	Thu May 07 11:13:29 2020 +0200
@@ -321,6 +321,7 @@
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
                                        DcmFileFormat& dicom,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
@@ -347,8 +348,8 @@
     request.DataSetType = DIMSE_DATASET_PRESENT;
     strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN);
 
-    if (!moveOriginatorAET.empty())
-    {
+    if (hasMoveOriginator)
+    {    
       strncpy(request.MoveOriginatorApplicationEntityTitle, 
               moveOriginatorAET.c_str(), DIC_AE_LEN);
       request.opts = O_STORE_MOVEORIGINATORAETITLE;
@@ -402,6 +403,7 @@
                                        std::string& sopInstanceUid,
                                        const void* buffer,
                                        size_t size,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
@@ -413,7 +415,7 @@
       throw OrthancException(ErrorCode_InternalError);
     }
     
-    Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID);
+    Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
   }
 
 
@@ -446,6 +448,7 @@
                                            IDicomTranscoder& transcoder,
                                            const void* buffer,
                                            size_t size,
+                                           bool hasMoveOriginator,
                                            const std::string& moveOriginatorAET,
                                            uint16_t moveOriginatorID)
   {
@@ -465,7 +468,8 @@
     if (accepted.find(inputSyntax) != accepted.end())
     {
       // No need for transcoding
-      Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID);
+      Store(sopClassUid, sopInstanceUid, *dicom,
+            hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
     }
     else
     {
@@ -489,9 +493,11 @@
 
       std::unique_ptr<DcmFileFormat> transcoded;
 
+      bool hasSopInstanceUidChanged;
+      
       if (transcoder.HasInplaceTranscode())
       {
-        if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false))
+        if (transcoder.InplaceTranscode(hasSopInstanceUidChanged, *dicom, uncompressedSyntaxes, false))
         {
           // In-place transcoding is supported and has succeeded
           transcoded.reset(dicom.release());
@@ -499,7 +505,13 @@
       }
       else
       {
-        transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false));
+        transcoded.reset(transcoder.TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, uncompressedSyntaxes, false));
+      }
+
+      if (hasSopInstanceUidChanged)
+      {
+        throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
+                               "instance UID while transcoding to an uncompressed transfer syntax");
       }
 
       // WARNING: The "dicom" variable must not be used below this
@@ -527,7 +539,8 @@
         }
         else
         {
-          Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID);
+          Store(sopClassUid, sopInstanceUid, *transcoded,
+                hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
         }
       }
     }
--- a/Core/DicomNetworking/DicomStoreUserConnection.h	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.h	Thu May 07 11:13:29 2020 +0200
@@ -150,6 +150,7 @@
     void Store(std::string& sopClassUid,
                std::string& sopInstanceUid,
                DcmFileFormat& dicom,
+               bool hasMoveOriginator,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
 
@@ -157,24 +158,10 @@
                std::string& sopInstanceUid,
                const void* buffer,
                size_t size,
+               bool hasMoveOriginator,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
 
-    void Store(std::string& sopClassUid,
-               std::string& sopInstanceUid,
-               DcmFileFormat& dicom)
-    {
-      Store(sopClassUid, sopInstanceUid, dicom, "", 0);  // Not a C-Move
-    }
-
-    void Store(std::string& sopClassUid,
-               std::string& sopInstanceUid,
-               const void* buffer,
-               size_t size)
-    {
-      Store(sopClassUid, sopInstanceUid, buffer, size, "", 0);  // Not a C-Move
-    }
-
     void LookupParameters(std::string& sopClassUid,
                           std::string& sopInstanceUid,
                           DicomTransferSyntax& transferSyntax,
@@ -185,17 +172,8 @@
                    IDicomTranscoder& transcoder,
                    const void* buffer,
                    size_t size,
+                   bool hasMoveOriginator,
                    const std::string& moveOriginatorAET,
                    uint16_t moveOriginatorID);
-
-    void Transcode(std::string& sopClassUid /* out */,
-                   std::string& sopInstanceUid /* out */,
-                   IDicomTranscoder& transcoder,
-                   const void* buffer,
-                   size_t size)
-    {
-      Transcode(sopClassUid, sopInstanceUid, transcoder,
-                buffer, size, "", 0);  // Not a C-Move
-    }
   };
 }
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.cpp	Thu May 07 11:13:29 2020 +0200
@@ -51,6 +51,7 @@
 static const char* KEY_ALLOW_N_ACTION = "AllowNAction";
 static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport";
 static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment";
+static const char* KEY_ALLOW_TRANSCODING = "AllowTranscoding";
 static const char* KEY_HOST = "Host";
 static const char* KEY_MANUFACTURER = "Manufacturer";
 static const char* KEY_PORT = "Port";
@@ -71,6 +72,7 @@
     allowGet_ = true;
     allowNAction_ = true;  // For storage commitment
     allowNEventReport_ = true;  // For storage commitment
+    allowTranscoding_ = true;
   }
 
 
@@ -233,6 +235,11 @@
       allowNAction_ = allow;
       allowNEventReport_ = allow;
     }
+
+    if (serialized.isMember(KEY_ALLOW_TRANSCODING))
+    {
+      allowTranscoding_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_TRANSCODING);
+    }
   }
 
 
@@ -314,7 +321,8 @@
             !allowGet_ ||
             !allowMove_ ||
             !allowNAction_ ||
-            !allowNEventReport_);
+            !allowNEventReport_ ||
+            !allowTranscoding_);
   }
 
   
@@ -336,6 +344,7 @@
       target[KEY_ALLOW_MOVE] = allowMove_;
       target[KEY_ALLOW_N_ACTION] = allowNAction_;
       target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_;
+      target[KEY_ALLOW_TRANSCODING] = allowTranscoding_;
     }
     else
     {
--- a/Core/DicomNetworking/RemoteModalityParameters.h	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.h	Thu May 07 11:13:29 2020 +0200
@@ -55,6 +55,7 @@
     bool                  allowGet_;
     bool                  allowNAction_;
     bool                  allowNEventReport_;
+    bool                  allowTranscoding_;
     
     void Clear();
 
@@ -131,5 +132,15 @@
 
     void Serialize(Json::Value& target,
                    bool forceAdvancedFormat) const;
+
+    bool IsTranscodingAllowed() const
+    {
+      return allowTranscoding_;
+    }
+
+    void SetTranscodingAllowed(bool allowed)
+    {
+      allowTranscoding_ = allowed;
+    }
   };
 }
--- a/Core/DicomParsing/DcmtkTranscoder.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomParsing/DcmtkTranscoder.cpp	Thu May 07 11:13:29 2020 +0200
@@ -129,7 +129,8 @@
   }
 
     
-  DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(const void* buffer,
+  DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */,
+                                                    const void* buffer,
                                                     size_t size,
                                                     const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                                     bool allowNewSopInstanceUid) 
@@ -141,7 +142,7 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid))
+    if (InplaceTranscode(hasSopInstanceUidChanged, *dicom, allowedSyntaxes, allowNewSopInstanceUid))
     {
       return dicom.release();
     }
@@ -152,7 +153,8 @@
   }
 
 
-  bool DcmtkTranscoder::InplaceTranscode(DcmFileFormat& dicom,
+  bool DcmtkTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                         DcmFileFormat& dicom,
                                          const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                          bool allowNewSopInstanceUid) 
   {
@@ -161,6 +163,8 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
+    hasSopInstanceUidChanged = false;
+
     DicomTransferSyntax syntax;
     if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom))
     {
@@ -170,7 +174,7 @@
 
     const uint16_t bitsStored = GetBitsStored(*dicom.getDataset());
     std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset());
-
+    
     if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end())
     {
       // No transcoding is needed
@@ -216,6 +220,7 @@
       if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, &parameters))
       {
         CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        hasSopInstanceUidChanged = true;
         return true;
       }
     }
@@ -231,6 +236,7 @@
       if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, &parameters))
       {
         CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        hasSopInstanceUidChanged = true;
         return true;
       }
     }
@@ -284,6 +290,7 @@
       if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, &parameters))
       {
         CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        hasSopInstanceUidChanged = true;
         return true;
       }
     }
@@ -294,13 +301,14 @@
 
     
   bool DcmtkTranscoder::TranscodeToBuffer(std::string& target,
+                                          bool& hasSopInstanceUidChanged /* out */,
                                           const void* buffer,
                                           size_t size,
                                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                           bool allowNewSopInstanceUid) 
   {
     std::unique_ptr<DcmFileFormat> transcoded(
-      TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
+      TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
 
     if (transcoded.get() == NULL)
     {
--- a/Core/DicomParsing/DcmtkTranscoder.h	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomParsing/DcmtkTranscoder.h	Thu May 07 11:13:29 2020 +0200
@@ -63,7 +63,8 @@
       return lossyQuality_;
     }
     
-    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
+    virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */,
+                                             const void* buffer,
                                              size_t size,
                                              const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                              bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
@@ -73,11 +74,13 @@
       return true;
     }
 
-    virtual bool InplaceTranscode(DcmFileFormat& dicom,
+    virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                  DcmFileFormat& dicom,
                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
     
     virtual bool TranscodeToBuffer(std::string& target,
+                                   bool& hasSopInstanceUidChanged /* out */,
                                    const void* buffer,
                                    size_t size,
                                    const std::set<DicomTransferSyntax>& allowedSyntaxes,
--- a/Core/DicomParsing/IDicomTranscoder.h	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomParsing/IDicomTranscoder.h	Thu May 07 11:13:29 2020 +0200
@@ -55,6 +55,7 @@
     }
 
     virtual bool TranscodeToBuffer(std::string& target,
+                                   bool& hasSopInstanceUidChanged /* out */,
                                    const void* buffer,
                                    size_t size,
                                    const std::set<DicomTransferSyntax>& allowedSyntaxes,
@@ -66,7 +67,8 @@
      * possibility to do a single parsing for all the possible
      * transfer syntaxes.
      **/
-    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
+    virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */,
+                                             const void* buffer,
                                              size_t size,
                                              const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                              bool allowNewSopInstanceUid) = 0;
@@ -76,7 +78,8 @@
     /**
      * In-place transcoding. This method is preferred for C-STORE.
      **/
-    virtual bool InplaceTranscode(DcmFileFormat& dicom,
+    virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                  DcmFileFormat& dicom,
                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                   bool allowNewSopInstanceUid) = 0;
   };
--- a/Core/DicomParsing/MemoryBufferTranscoder.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp	Thu May 07 11:13:29 2020 +0200
@@ -39,52 +39,66 @@
 
 namespace Orthanc
 {
-  MemoryBufferTranscoder::MemoryBufferTranscoder(bool tryDcmtk) :
-    tryDcmtk_(tryDcmtk)
+  MemoryBufferTranscoder::MemoryBufferTranscoder()
+  {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    useDcmtk_ = true;
+#else
+    useDcmtk_ = false;
+#endif
+  }
+
+
+  void MemoryBufferTranscoder::SetDcmtkUsed(bool used)
   {
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1
-    if (tryDcmtk)
+    if (useDcmtk)
     {
       throw OrthancException(ErrorCode_NotImplemented,
                              "Orthanc was built without support for DMCTK transcoding");
     }
 #endif    
+
+    useDcmtk_ = used;
   }
 
+
   bool MemoryBufferTranscoder::TranscodeToBuffer(std::string& target,
+                                                 bool& hasSopInstanceUidChanged,
                                                  const void* buffer,
                                                  size_t size,
                                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                                  bool allowNewSopInstanceUid)
   {
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    if (tryDcmtk_)
+    if (useDcmtk_)
     {
-      return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+      return dcmtk_.TranscodeToBuffer(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
     }
     else
 #endif
     {
-      return Transcode(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+      return Transcode(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
     }
   }
 
   
-  DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(const void* buffer,
+  DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged,
+                                                           const void* buffer,
                                                            size_t size,
                                                            const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                                            bool allowNewSopInstanceUid)
   {
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    if (tryDcmtk_)
+    if (useDcmtk_)
     {
-      return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+      return dcmtk_.TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
     }
     else
 #endif
     {
       std::string transcoded;
-      if (Transcode(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
+      if (Transcode(transcoded, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
       {
         return FromDcmtkBridge::LoadFromMemoryBuffer(
           transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size());
@@ -97,14 +111,15 @@
   }
 
 
-  bool MemoryBufferTranscoder::InplaceTranscode(DcmFileFormat& dicom,
+  bool MemoryBufferTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged,
+                                                DcmFileFormat& dicom,
                                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                                 bool allowNewSopInstanceUid)
   {
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    if (tryDcmtk_)
+    if (useDcmtk_)
     {
-      return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid);
+      return dcmtk_.InplaceTranscode(hasSopInstanceUidChanged, dicom, allowedSyntaxes, allowNewSopInstanceUid);
     }
     else
 #endif
--- a/Core/DicomParsing/MemoryBufferTranscoder.h	Wed May 06 12:48:28 2020 +0200
+++ b/Core/DicomParsing/MemoryBufferTranscoder.h	Thu May 07 11:13:29 2020 +0200
@@ -47,7 +47,7 @@
   class MemoryBufferTranscoder : public IDicomTranscoder
   {
   private:
-    bool  tryDcmtk_;
+    bool  useDcmtk_;
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
     DcmtkTranscoder  dcmtk_;
@@ -55,6 +55,7 @@
 
   protected:
     virtual bool Transcode(std::string& target,
+                           bool& hasSopInstanceUidChanged /* out */,
                            const void* buffer,
                            size_t size,
                            const std::set<DicomTransferSyntax>& allowedSyntaxes,
@@ -62,28 +63,38 @@
     
   public:
     /**
-     * If "tryDcmtk" is "true", the transcoder will first try and call
+     * If "useDcmtk" is "true", the transcoder will first try and call
      * DCMTK, before calling its own "Transcode()" implementation.
      **/
-    MemoryBufferTranscoder(bool tryDcmtk);
+    MemoryBufferTranscoder();
+
+    void SetDcmtkUsed(bool used);
+
+    bool IsDcmtkUsed() const
+    {
+      return useDcmtk_;
+    }
     
     virtual bool TranscodeToBuffer(std::string& target,
+                                   bool& hasSopInstanceUidChanged /* out */,
                                    const void* buffer,
                                    size_t size,
                                    const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                    bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
     
-    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
+    virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */,
+                                             const void* buffer,
                                              size_t size,
                                              const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                              bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
 
     virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE
     {
-      return tryDcmtk_;
+      return useDcmtk_;
     }
     
-    virtual bool InplaceTranscode(DcmFileFormat& dicom,
+    virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                  DcmFileFormat& dicom,
                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
   };
--- a/OrthancServer/LuaScripting.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Thu May 07 11:13:29 2020 +0200
@@ -588,7 +588,7 @@
       }
 
       // This is not a C-MOVE: No need to call "StoreScuCommand::SetMoveOriginator()"
-      return lock.AddStoreScuOperation(localAet, modality);
+      return lock.AddStoreScuOperation(context_, localAet, modality);
     }
 
     if (operation == "store-peer")
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Thu May 07 11:13:29 2020 +0200
@@ -121,7 +121,7 @@
 
         const void* data = dicom.empty() ? NULL : dicom.c_str();
         connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(),
-                           originatorAet_, originatorId_);
+                           true, originatorAet_, originatorId_);
 
         return Status_Success;
       }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu May 07 11:13:29 2020 +0200
@@ -113,23 +113,19 @@
 
   static void DicomEcho(RestApiPostCall& call)
   {
-    try
-    {
-      DicomControlUserConnection connection(GetAssociationParameters(call));
+    DicomControlUserConnection connection(GetAssociationParameters(call));
 
-      if (connection.Echo())
-      {
-        // Echo has succeeded
-        call.GetOutput().AnswerBuffer("{}", MimeType_Json);
-        return;
-      }
+    if (connection.Echo())
+    {
+      // Echo has succeeded
+      call.GetOutput().AnswerBuffer("{}", MimeType_Json);
+      return;
     }
-    catch (OrthancException&)
+    else
     {
+      // Echo has failed
+      call.GetOutput().SignalError(HttpStatus_500_InternalServerError);
     }
-
-    // Echo has failed
-    call.GetOutput().SignalError(HttpStatus_500_InternalServerError);
   }
 
 
@@ -1008,8 +1004,8 @@
     DicomStoreUserConnection connection(GetAssociationParameters(call, body));
 
     std::string sopClassUid, sopInstanceUid;
-    connection.Store(sopClassUid, sopInstanceUid,
-                     call.GetBodyData(), call.GetBodySize());
+    connection.Store(sopClassUid, sopInstanceUid, call.GetBodyData(),
+                     call.GetBodySize(), false /* Not a C-MOVE */, "", 0);
 
     Json::Value answer = Json::objectValue;
     answer[SOP_CLASS_UID] = sopClassUid;
--- a/OrthancServer/ServerContext.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Thu May 07 11:13:29 2020 +0200
@@ -35,6 +35,7 @@
 #include "ServerContext.h"
 
 #include "../Core/Cache/SharedArchive.h"
+#include "../Core/DicomParsing/DcmtkTranscoder.h"
 #include "../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../Core/FileStorage/StorageAccessor.h"
 #include "../Core/HttpServer/FilesystemHttpSender.h"
@@ -243,7 +244,8 @@
     metricsRegistry_(new MetricsRegistry),
     isHttpServerSecure_(true),
     isExecuteLuaEnabled_(false),
-    overwriteInstances_(false)
+    overwriteInstances_(false),
+    dcmtkTranscoder_(new DcmtkTranscoder)
   {
     {
       OrthancConfiguration::ReaderLock lock;
@@ -264,6 +266,9 @@
 
       // New configuration option in Orthanc 1.6.0
       storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
+
+      // New option in Orthanc 1.7.0
+      transcodingEnabled_ = lock.GetConfiguration().GetBooleanParameter("TranscodingEnabled", true);
     }
 
     jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
@@ -1108,4 +1113,66 @@
 
     return NULL;
   }
+
+
+  void ServerContext::StoreWithTranscoding(std::string& sopClassUid,
+                                           std::string& sopInstanceUid,
+                                           DicomStoreUserConnection& connection,
+                                           const std::string& dicom,
+                                           bool hasMoveOriginator,
+                                           const std::string& moveOriginatorAet,
+                                           uint16_t moveOriginatorId)
+  {
+    const void* data = dicom.empty() ? NULL : dicom.c_str();
+    
+    if (!transcodingEnabled_ ||
+        !connection.GetParameters().GetRemoteModality().IsTranscodingAllowed())
+    {
+      connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(),
+                       hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
+    }
+    else
+    {
+      IDicomTranscoder* transcoder = dcmtkTranscoder_.get();
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+      if (HasPlugins())
+      {
+        transcoder = &GetPlugins();
+      }
+#endif
+
+      if (transcoder == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      else
+      {
+        connection.Transcode(sopClassUid, sopInstanceUid, *transcoder, data, dicom.size(),
+                             hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
+      }
+    }
+  }
+
+
+  bool ServerContext::TranscodeMemoryBuffer(std::string& target,
+                                            bool& hasSopInstanceUidChanged,
+                                            const std::string& source,
+                                            const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                            bool allowNewSopInstanceUid)
+  {
+    const char* data = source.empty() ? NULL : source.c_str();
+    
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins())
+    {
+      return GetPlugins().TranscodeToBuffer(
+        target, hasSopInstanceUidChanged, data, source.size(), allowedSyntaxes, allowNewSopInstanceUid);
+    }
+#endif
+
+    assert(dcmtkTranscoder_.get() != NULL);
+    return dcmtkTranscoder_->TranscodeToBuffer(
+      target, hasSopInstanceUidChanged, data, source.size(), allowedSyntaxes, allowNewSopInstanceUid);
+  }
 }
--- a/OrthancServer/ServerContext.h	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerContext.h	Thu May 07 11:13:29 2020 +0200
@@ -40,6 +40,7 @@
 #include "ServerJobs/IStorageCommitmentFactory.h"
 
 #include "../Core/Cache/MemoryCache.h"
+#include "../Core/DicomParsing/IDicomTranscoder.h"
 
 
 namespace Orthanc
@@ -225,6 +226,9 @@
 
     std::unique_ptr<StorageCommitmentReports>  storageCommitmentReports_;
 
+    bool transcodingEnabled_;
+    std::unique_ptr<IDicomTranscoder>  dcmtkTranscoder_;
+
   public:
     class DicomCacheLocker : public boost::noncopyable
     {
@@ -450,5 +454,20 @@
     {
       return *storageCommitmentReports_;
     }
+
+    void StoreWithTranscoding(std::string& sopClassUid,
+                              std::string& sopInstanceUid,
+                              DicomStoreUserConnection& connection,
+                              const std::string& dicom,
+                              bool hasMoveOriginator,
+                              const std::string& moveOriginatorAet,
+                              uint16_t moveOriginatorId);
+
+    // This method can be used even if "TranscodingEnabled" is set to "false"
+    bool TranscodeMemoryBuffer(std::string& target,
+                               bool& hasSopInstanceUidChanged,
+                               const std::string& source,
+                               const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                               bool allowNewSopInstanceUid);
   };
 }
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Thu May 07 11:13:29 2020 +0200
@@ -74,18 +74,8 @@
     }
     
     std::string sopClassUid, sopInstanceUid;
-
-    const void* data = dicom.empty() ? NULL : dicom.c_str();
-    
-    if (HasMoveOriginator())
-    {
-      connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(),
-                         moveOriginatorAet_, moveOriginatorId_);
-    }
-    else
-    {
-      connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size());
-    }
+    context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, *connection_, dicom,
+                                  HasMoveOriginator(), moveOriginatorAet_, moveOriginatorId_);
 
     if (storageCommitment_)
     {
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Thu May 07 11:13:29 2020 +0200
@@ -200,11 +200,13 @@
   }
 
 
-  size_t LuaJobManager::Lock::AddStoreScuOperation(const std::string& localAet,
+  size_t LuaJobManager::Lock::AddStoreScuOperation(ServerContext& context,
+                                                   const std::string& localAet,
                                                    const RemoteModalityParameters& modality)
   {
     assert(jobLock_.get() != NULL);
-    return jobLock_->AddOperation(new StoreScuOperation(that_.connectionManager_, localAet, modality));    
+    return jobLock_->AddOperation(new StoreScuOperation(
+                                    context, that_.connectionManager_, localAet, modality));    
   }
 
 
--- a/OrthancServer/ServerJobs/LuaJobManager.h	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.h	Thu May 07 11:13:29 2020 +0200
@@ -91,7 +91,8 @@
 
       size_t AddDeleteResourceOperation(ServerContext& context);
 
-      size_t AddStoreScuOperation(const std::string& localAet,
+      size_t AddStoreScuOperation(ServerContext& context,
+                                  const std::string& localAet,
                                   const RemoteModalityParameters& modality);
 
       size_t AddStorePeerOperation(const WebServiceParameters& peer);
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Thu May 07 11:13:29 2020 +0200
@@ -35,6 +35,7 @@
 #include "StoreScuOperation.h"
 
 #include "DicomInstanceOperationValue.h"
+#include "../../ServerContext.h"
 
 #include "../../../Core/Logging.h"
 #include "../../../Core/OrthancException.h"
@@ -63,10 +64,9 @@
       std::string dicom;
       instance.ReadDicom(dicom);
 
-      const void* data = dicom.empty() ? NULL : dicom.c_str();
-      
       std::string sopClassUid, sopInstanceUid;  // Unused
-      lock.GetConnection().Store(sopClassUid, sopInstanceUid, data, dicom.size());
+      context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, lock.GetConnection(), dicom,
+                                    false /* Not a C-MOVE */, "", 0);
     }
     catch (OrthancException& e)
     {
@@ -87,8 +87,10 @@
   }
 
 
-  StoreScuOperation::StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+  StoreScuOperation::StoreScuOperation(ServerContext& context,
+                                       TimeoutDicomConnectionManager& connectionManager,
                                        const Json::Value& serialized) :
+    context_(context),
     connectionManager_(connectionManager)
   {
     if (SerializationToolbox::ReadString(serialized, "Type") != "StoreScu" ||
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Thu May 07 11:13:29 2020 +0200
@@ -38,24 +38,30 @@
 
 namespace Orthanc
 {
+  class ServerContext;
+  
   class StoreScuOperation : public IJobOperation
   {
   private:
+    ServerContext&                  context_;
     TimeoutDicomConnectionManager&  connectionManager_;
     std::string                     localAet_;
     RemoteModalityParameters        modality_;
     
   public:
-    StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+    StoreScuOperation(ServerContext& context,
+                      TimeoutDicomConnectionManager& connectionManager,
                       const std::string& localAet,
                       const RemoteModalityParameters& modality) :
+      context_(context),
       connectionManager_(connectionManager),
       localAet_(localAet),
       modality_(modality)
     {
     }
 
-    StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+    StoreScuOperation(ServerContext& context,
+                      TimeoutDicomConnectionManager& connectionManager,
                       const Json::Value& serialized);
 
     const std::string& GetLocalAet() const
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Thu May 07 11:13:29 2020 +0200
@@ -127,7 +127,7 @@
     else if (type == "StoreScu")
     {
       return new StoreScuOperation(
-        context_.GetLuaScripting().GetDicomConnectionManager(), source);
+        context_, context_.GetLuaScripting().GetDicomConnectionManager(), source);
     }
     else if (type == "SystemCall")
     {
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Thu May 07 11:13:29 2020 +0200
@@ -4791,4 +4791,16 @@
     
     return NULL;
   }
+
+
+  bool OrthancPlugins::Transcode(std::string& target,
+                                 bool& hasSopInstanceUidChanged /* out */,
+                                 const void* buffer,
+                                 size_t size,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid)
+  {
+    // TODO
+    return false;
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Wed May 06 12:48:28 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Thu May 07 11:13:29 2020 +0200
@@ -56,6 +56,7 @@
 #include "../../Core/DicomNetworking/IFindRequestHandlerFactory.h"
 #include "../../Core/DicomNetworking/IMoveRequestHandlerFactory.h"
 #include "../../Core/DicomNetworking/IWorklistRequestHandlerFactory.h"
+#include "../../Core/DicomParsing/MemoryBufferTranscoder.h"
 #include "../../Core/FileStorage/IStorageArea.h"
 #include "../../Core/HttpServer/IHttpHandler.h"
 #include "../../Core/HttpServer/IIncomingHttpRequestFilter.h"
@@ -82,7 +83,8 @@
     public IIncomingHttpRequestFilter,
     public IFindRequestHandlerFactory,
     public IMoveRequestHandlerFactory,
-    public IStorageCommitmentFactory
+    public IStorageCommitmentFactory,
+    public MemoryBufferTranscoder
   {
   private:
     class PImpl;
@@ -223,6 +225,15 @@
                                 _OrthancPluginService service,
                                 const void* parameters);
 
+  protected:
+    // From "MemoryBufferTranscoder"
+    virtual bool Transcode(std::string& target,
+                           bool& hasSopInstanceUidChanged /* out */,
+                           const void* buffer,
+                           size_t size,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+    
   public:
     OrthancPlugins();
 
--- a/Resources/Configuration.json	Wed May 06 12:48:28 2020 +0200
+++ b/Resources/Configuration.json	Thu May 07 11:13:29 2020 +0200
@@ -209,9 +209,17 @@
      * registered remote SCU modalities. Starting with Orthanc 1.5.0,
      * it is possible to specify which DICOM commands are allowed,
      * separately for each remote modality, using the syntax
-     * below. The "AllowEcho" (resp.  "AllowStore") option only has an
-     * effect respectively if global option "DicomAlwaysAllowEcho"
-     * (resp. "DicomAlwaysAllowStore") is set to false.
+     * below.
+     *
+     * The "AllowEcho" (resp.  "AllowStore") option only has an effect
+     * respectively if global option "DicomAlwaysAllowEcho"
+     * (resp. "DicomAlwaysAllowStore") is set to "false".
+     *
+     * Starting with Orthanc 1.7.0, "AllowTranscoding" can be used to
+     * disable the transcoding to uncompressed transfer syntaxes if
+     * the remote modality doesn't support compressed transfer
+     * syntaxes. This option only has an effect if global option
+     * "EnableTranscoding" is set to "true".
      **/
     //"untrusted" : {
     //  "AET" : "ORTHANC",
@@ -222,7 +230,8 @@
     //  "AllowFind" : false,
     //  "AllowMove" : false,
     //  "AllowStore" : true,
-    //  "AllowStorageCommitment" : false  // new in 1.6.0
+    //  "AllowStorageCommitment" : false,  // new in 1.6.0
+    //  "AllowTranscoding" : true          // new in 1.7.0
     //}
   },
 
@@ -532,5 +541,10 @@
 
   // Maximum number of storage commitment reports (i.e. received from
   // remote modalities) to be kept in memory (new in Orthanc 1.6.0).
-  "StorageCommitmentReportsSize" : 100
+  "StorageCommitmentReportsSize" : 100,
+
+  // Whether Orthanc transcodes DICOM files to an uncompressed
+  // transfer syntax, if remote modalities do not support compressed
+  // transfer syntaxes (new in Orthanc 1.7.0).
+  "TranscodingEnabled" : true
 }
--- a/UnitTestsSources/FromDcmtkTests.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu May 07 11:13:29 2020 +0200
@@ -1955,7 +1955,7 @@
         std::string c, i;
         try
         {
-          scu.Transcode(c, i, transcoder, source.c_str(), source.size());
+          scu.Transcode(c, i, transcoder, source.c_str(), source.size(), false, "", 0);
         }
         catch (OrthancException& e)
         {
@@ -1991,15 +1991,29 @@
     s.insert(a);
 
     std::string t;
-    
-    if (!transcoder.TranscodeToBuffer(t, source.c_str(), source.size(), s, true))
+
+    bool hasSopInstanceUidChanged;
+                                   
+    if (!transcoder.TranscodeToBuffer(t, hasSopInstanceUidChanged, source.c_str(), source.size(), s, true))
     {
       printf("**************** CANNOT: [%s] => [%s]\n",
              GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
     }
     else
     {
+      bool lossy = (a == DicomTransferSyntax_JPEGProcess1 ||
+                    a == DicomTransferSyntax_JPEGProcess2_4 ||
+                    a == DicomTransferSyntax_JPEGLSLossy);
+      
       printf("SIZE: %lu\n", t.size());
+      if (hasSopInstanceUidChanged)
+      {
+        ASSERT_TRUE(lossy);
+      }
+      else
+      {
+        ASSERT_FALSE(lossy);
+      }
     }
   }
 }
--- a/UnitTestsSources/MultiThreadingTests.cpp	Wed May 06 12:48:28 2020 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Thu May 07 11:13:29 2020 +0200
@@ -1415,7 +1415,7 @@
       modality.SetPortNumber(1000);
       modality.SetManufacturer(ModalityManufacturer_StoreScp);
 
-      StoreScuOperation operation(luaManager, "TEST", modality);
+      StoreScuOperation operation(GetContext(), luaManager, "TEST", modality);
 
       ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
       operation.Serialize(s);
@@ -1903,6 +1903,7 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 
   s = Json::nullValue;
@@ -1933,6 +1934,7 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 
   s["Port"] = "46";
@@ -1999,6 +2001,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 
   {
@@ -2008,6 +2011,7 @@
     s["AET"] = "AET";
     s["Host"] = "host";
     s["Port"] = "104";
+    s["AllowTranscoding"] = false;
     
     RemoteModalityParameters modality(s);
     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
@@ -2016,6 +2020,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_FALSE(modality.IsTranscodingAllowed());
   }
 
   {
@@ -2033,6 +2038,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 }