changeset 3896:210af28c4087 transcoding

merge
author Alain Mazy <alain@mazy.be>
date Thu, 07 May 2020 11:32:15 +0200
parents 37cf1889667a (current diff) 8f7ad4989fec (diff)
children a4c0ae644fe5
files Core/DicomParsing/FromDcmtkBridge.cpp Resources/Patches/openssl-1.1.1d.patch Resources/Patches/openssl-1.1.1f.patch
diffstat 50 files changed, 2727 insertions(+), 1166 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu May 07 11:31:58 2020 +0200
+++ b/CMakeLists.txt	Thu May 07 11:32:15 2020 +0200
@@ -13,6 +13,7 @@
 set(ENABLE_CRYPTO_OPTIONS ON)
 set(ENABLE_DCMTK ON)
 set(ENABLE_DCMTK_NETWORKING ON)
+set(ENABLE_DCMTK_TRANSCODING ON)
 set(ENABLE_GOOGLE_TEST ON)
 set(ENABLE_JPEG ON)
 set(ENABLE_LOCALE ON)
@@ -25,9 +26,6 @@
 set(ENABLE_WEB_SERVER ON)
 set(ENABLE_ZLIB ON)
 
-# To test transcoding
-set(ENABLE_DCMTK_TRANSCODING ON)
-
 set(HAS_EMBEDDED_RESOURCES ON)
 
 
--- a/Core/DicomNetworking/DicomAssociation.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociation.cpp	Thu May 07 11:32:15 2020 +0200
@@ -259,10 +259,10 @@
 
     LOG(INFO) << "Opening a DICOM SCU connection from AET \""
               << parameters.GetLocalApplicationEntityTitle() 
-              << "\" to AET \"" << parameters.GetRemoteApplicationEntityTitle()
-              << "\" on host " << parameters.GetRemoteHost()
-              << ":" << parameters.GetRemotePort() 
-              << " (manufacturer: " << EnumerationToString(parameters.GetRemoteManufacturer()) << ")";
+              << "\" to AET \"" << parameters.GetRemoteModality().GetApplicationEntityTitle()
+              << "\" on host " << parameters.GetRemoteModality().GetHost()
+              << ":" << parameters.GetRemoteModality().GetPortNumber() 
+              << " (manufacturer: " << EnumerationToString(parameters.GetRemoteModality().GetManufacturer()) << ")";
 
     CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_));
     CheckConnecting(parameters, ASC_createAssociationParameters(&params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
@@ -270,7 +270,7 @@
     // Set this application's title and the called application's title in the params
     CheckConnecting(parameters, ASC_setAPTitles(
                       params_, parameters.GetLocalApplicationEntityTitle().c_str(),
-                      parameters.GetRemoteApplicationEntityTitle().c_str(), NULL));
+                      parameters.GetRemoteModality().GetApplicationEntityTitle().c_str(), NULL));
 
     // Set the network addresses of the local and remote entities
     char localHost[HOST_NAME_MAX];
@@ -284,7 +284,8 @@
       snprintf
 #endif
       (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d",
-       parameters.GetRemoteHost().c_str(), parameters.GetRemotePort());
+       parameters.GetRemoteModality().GetHost().c_str(),
+       parameters.GetRemoteModality().GetPortNumber());
 
     CheckConnecting(parameters, ASC_setPresentationAddresses(params_, localHost, remoteHostAndPort));
 
@@ -339,7 +340,7 @@
           else
           {
             LOG(WARNING) << "Unknown transfer syntax received from AET \""
-                         << parameters.GetRemoteApplicationEntityTitle()
+                         << parameters.GetRemoteModality().GetApplicationEntityTitle()
                          << "\": " << pc->acceptedTransferSyntax;
           }
         }
@@ -352,7 +353,7 @@
     {
       throw OrthancException(ErrorCode_NoPresentationContext,
                              "Unable to negotiate a presentation context with AET \"" +
-                             parameters.GetRemoteApplicationEntityTitle() + "\"");
+                             parameters.GetRemoteModality().GetApplicationEntityTitle() + "\"");
     }
   }
 
@@ -517,7 +518,7 @@
 
       throw OrthancException(ErrorCode_NetworkProtocol,
                              "DicomAssociation - " + command + " to AET \"" +
-                             parameters.GetRemoteApplicationEntityTitle() +
+                             parameters.GetRemoteModality().GetApplicationEntityTitle() +
                              "\": " + info);
     }
   }
@@ -604,7 +605,7 @@
      **/
 
     LOG(INFO) << "Reporting modality \""
-              << parameters.GetRemoteApplicationEntityTitle()
+              << parameters.GetRemoteModality().GetApplicationEntityTitle()
               << "\" about storage commitment transaction: " << transactionUid
               << " (" << successSopClassUids.size() << " successes, " 
               << failedSopClassUids.size() << " failures)";
@@ -654,7 +655,7 @@
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "Unable to send N-EVENT-REPORT request to AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
 
       if (!DIMSE_sendMessageUsingMemoryData(
@@ -682,7 +683,7 @@
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "Unable to read N-EVENT-REPORT response from AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
 
       const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP;
@@ -696,14 +697,14 @@
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "Badly formatted N-EVENT-REPORT response from AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
 
       if (content.DimseStatus != 0 /* success */)
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "The request cannot be handled by remote AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
     }
 
@@ -767,7 +768,7 @@
      **/
 
     LOG(INFO) << "Request to modality \""
-              << parameters.GetRemoteApplicationEntityTitle()
+              << parameters.GetRemoteModality().GetApplicationEntityTitle()
               << "\" about storage commitment for " << sopClassUids.size()
               << " instances, with transaction UID: " << transactionUid;
     const DIC_US messageId = association.GetDcmtkAssociation().nextMsgID++;
@@ -801,7 +802,7 @@
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "Unable to send N-ACTION request to AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
 
       if (!DIMSE_sendMessageUsingMemoryData(
@@ -829,7 +830,7 @@
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "Unable to read N-ACTION response from AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
 
       const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP;
@@ -843,14 +844,14 @@
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "Badly formatted N-ACTION response from AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
 
       if (content.DimseStatus != 0 /* success */)
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
                                "The request cannot be handled by remote AET: " +
-                               parameters.GetRemoteApplicationEntityTitle());
+                               parameters.GetRemoteModality().GetApplicationEntityTitle());
       }
     }
 
--- a/Core/DicomNetworking/DicomAssociationParameters.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociationParameters.cpp	Thu May 07 11:32:15 2020 +0200
@@ -37,6 +37,7 @@
 #include "../Compatibility.h"
 #include "../Logging.h"
 #include "../OrthancException.h"
+#include "../SerializationToolbox.h"
 #include "NetworkingCompatibility.h"
 
 #include <boost/thread/mutex.hpp>
@@ -48,68 +49,104 @@
 
 namespace Orthanc
 {
-  void DicomAssociationParameters::ReadDefaultTimeout()
+  void DicomAssociationParameters::CheckHost(const std::string& host)
+  {
+    if (host.size() > HOST_NAME_MAX - 10)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Invalid host name (too long): " + host);
+    }
+  }
+
+  
+  uint32_t DicomAssociationParameters::GetDefaultTimeout()
   {
     boost::mutex::scoped_lock lock(defaultTimeoutMutex_);
-    timeout_ = defaultTimeout_;
+    return defaultTimeout_;
   }
 
 
   DicomAssociationParameters::DicomAssociationParameters() :
-    localAet_("STORESCU"),
-    remoteAet_("ANY-SCP"),
-    remoteHost_("127.0.0.1"),
-    remotePort_(104),
-    manufacturer_(ModalityManufacturer_Generic)
+    localAet_("ORTHANC"),
+    timeout_(GetDefaultTimeout())
   {
-    ReadDefaultTimeout();
+    remote_.SetApplicationEntityTitle("ANY-SCP");
   }
 
     
   DicomAssociationParameters::DicomAssociationParameters(const std::string& localAet,
                                                          const RemoteModalityParameters& remote) :
     localAet_(localAet),
-    remoteAet_(remote.GetApplicationEntityTitle()),
-    remoteHost_(remote.GetHost()),
-    remotePort_(remote.GetPortNumber()),
-    manufacturer_(remote.GetManufacturer()),
-    timeout_(defaultTimeout_)
+    timeout_(GetDefaultTimeout())
   {
-    ReadDefaultTimeout();
+    SetRemoteModality(remote);
   }
 
     
-  void DicomAssociationParameters::SetRemoteHost(const std::string& host)
+  void DicomAssociationParameters::SetRemoteModality(const RemoteModalityParameters& remote)
   {
-    if (host.size() > HOST_NAME_MAX - 10)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange,
-                             "Invalid host name (too long): " + host);
-    }
-
-    remoteHost_ = host;
+    CheckHost(remote.GetHost());
+    remote_ = remote;
   }
 
 
-  void DicomAssociationParameters::SetRemoteModality(const RemoteModalityParameters& parameters)
+  void DicomAssociationParameters::SetRemoteHost(const std::string& host)
   {
-    SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle());
-    SetRemoteHost(parameters.GetHost());
-    SetRemotePort(parameters.GetPortNumber());
-    SetRemoteManufacturer(parameters.GetManufacturer());
+    CheckHost(host);
+    remote_.SetHost(host);
   }
 
 
   bool DicomAssociationParameters::IsEqual(const DicomAssociationParameters& other) const
   {
     return (localAet_ == other.localAet_ &&
-            remoteAet_ == other.remoteAet_ &&
-            remoteHost_ == other.remoteHost_ &&
-            remotePort_ == other.remotePort_ &&
-            manufacturer_ == other.manufacturer_);
+            remote_.GetApplicationEntityTitle() == other.remote_.GetApplicationEntityTitle() &&
+            remote_.GetHost() == other.remote_.GetHost() &&
+            remote_.GetPortNumber() == other.remote_.GetPortNumber() &&
+            remote_.GetManufacturer() == other.remote_.GetManufacturer() &&
+            timeout_ == other.timeout_);
   }
 
+
+  static const char* const LOCAL_AET = "LocalAet";
+  static const char* const REMOTE = "Remote";
+  static const char* const TIMEOUT = "Timeout";  // New in Orthanc in 1.7.0
+
+  
+  void DicomAssociationParameters::SerializeJob(Json::Value& target) const
+  {
+    if (target.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      target[LOCAL_AET] = localAet_;
+      remote_.Serialize(target[REMOTE], true /* force advanced format */);
+      target[TIMEOUT] = timeout_;
+    }
+  }
+
+
+  DicomAssociationParameters DicomAssociationParameters::UnserializeJob(const Json::Value& serialized)
+  {
+    if (serialized.type() == Json::objectValue)
+    {
+      DicomAssociationParameters result;
     
+      result.remote_ = RemoteModalityParameters(serialized[REMOTE]);
+      result.localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET);
+      result.timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, GetDefaultTimeout());
+
+      return result;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+    
+
   void DicomAssociationParameters::SetDefaultTimeout(uint32_t seconds)
   {
     LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " 
--- a/Core/DicomNetworking/DicomAssociationParameters.h	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociationParameters.h	Thu May 07 11:32:15 2020 +0200
@@ -35,6 +35,8 @@
 
 #include "RemoteModalityParameters.h"
 
+#include <json/value.h>
+
 class OFCondition;  // From DCMTK
 
 namespace Orthanc
@@ -42,14 +44,11 @@
   class DicomAssociationParameters
   {
   private:
-    std::string           localAet_;
-    std::string           remoteAet_;
-    std::string           remoteHost_;
-    uint16_t              remotePort_;
-    ModalityManufacturer  manufacturer_;
-    uint32_t              timeout_;
+    std::string               localAet_;
+    RemoteModalityParameters  remote_;
+    uint32_t                  timeout_;
 
-    void ReadDefaultTimeout();
+    static void CheckHost(const std::string& host);
 
   public:
     DicomAssociationParameters();
@@ -62,52 +61,38 @@
       return localAet_;
     }
 
-    const std::string& GetRemoteApplicationEntityTitle() const
-    {
-      return remoteAet_;
-    }
-
-    const std::string& GetRemoteHost() const
-    {
-      return remoteHost_;
-    }
-
-    uint16_t GetRemotePort() const
-    {
-      return remotePort_;
-    }
-
-    ModalityManufacturer GetRemoteManufacturer() const
-    {
-      return manufacturer_;
-    }
-
     void SetLocalApplicationEntityTitle(const std::string& aet)
     {
       localAet_ = aet;
     }
 
+    const RemoteModalityParameters& GetRemoteModality() const
+    {
+      return remote_;
+    }
+
+    void SetRemoteModality(const RemoteModalityParameters& parameters);
+    
     void SetRemoteApplicationEntityTitle(const std::string& aet)
     {
-      remoteAet_ = aet;
+      remote_.SetApplicationEntityTitle(aet);
     }
 
     void SetRemoteHost(const std::string& host);
 
     void SetRemotePort(uint16_t port)
     {
-      remotePort_ = port;
+      remote_.SetPortNumber(port);
     }
 
     void SetRemoteManufacturer(ModalityManufacturer manufacturer)
     {
-      manufacturer_ = manufacturer;
+      remote_.SetManufacturer(manufacturer);
     }
 
-    void SetRemoteModality(const RemoteModalityParameters& parameters);
-
     bool IsEqual(const DicomAssociationParameters& other) const;
 
+    // Setting it to "0" disables the timeout (infinite wait)
     void SetTimeout(uint32_t seconds)
     {
       timeout_ = seconds;
@@ -122,7 +107,13 @@
     {
       return timeout_ != 0;
     }
+
+    void SerializeJob(Json::Value& target) const;
+    
+    static DicomAssociationParameters UnserializeJob(const Json::Value& serialized);
     
     static void SetDefaultTimeout(uint32_t seconds);
+
+    static uint32_t GetDefaultTimeout();
   };
 }
--- a/Core/DicomNetworking/DicomControlUserConnection.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomControlUserConnection.cpp	Thu May 07 11:32:15 2020 +0200
@@ -257,7 +257,7 @@
     if (presID == 0)
     {
       throw OrthancException(ErrorCode_DicomFindUnavailable,
-                             "Remote AET is " + parameters_.GetRemoteApplicationEntityTitle());
+                             "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle());
     }
 
     T_DIMSE_C_FindRQ request;
@@ -309,14 +309,14 @@
         throw OrthancException(ErrorCode_NetworkProtocol,
                                HttpStatus_422_UnprocessableEntity,
                                "C-FIND SCU to AET \"" +
-                               parameters_.GetRemoteApplicationEntityTitle() +
+                               parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status 0x" + buf +
                                " (unable to process - invalid query ?)");
       }
       else
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" +
-                               parameters_.GetRemoteApplicationEntityTitle() +
+                               parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status 0x" + buf);
       }
     }
@@ -331,7 +331,7 @@
     association_->Open(parameters_);
 
     std::unique_ptr<ParsedDicomFile> query(
-      ConvertQueryFields(fields, parameters_.GetRemoteManufacturer()));
+      ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer()));
     DcmDataset* dataset = query->GetDcmtkObject().getDataset();
 
     const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel;
@@ -362,7 +362,7 @@
     if (presID == 0)
     {
       throw OrthancException(ErrorCode_DicomMoveUnavailable,
-                             "Remote AET is " + parameters_.GetRemoteApplicationEntityTitle());
+                             "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle());
     }
 
     T_DIMSE_C_MoveRQ request;
@@ -412,14 +412,14 @@
         throw OrthancException(ErrorCode_NetworkProtocol,
                                HttpStatus_422_UnprocessableEntity,
                                "C-MOVE SCU to AET \"" +
-                               parameters_.GetRemoteApplicationEntityTitle() +
+                               parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status 0x" + buf +
                                " (unable to process - resource not found ?)");
       }
       else
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" +
-                               parameters_.GetRemoteApplicationEntityTitle() +
+                               parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status 0x" + buf);
       }
     }
@@ -434,15 +434,6 @@
   }
     
 
-  DicomControlUserConnection::DicomControlUserConnection(const std::string& localAet,
-                                                         const RemoteModalityParameters& remote) :
-    parameters_(localAet, remote),
-    association_(new DicomAssociation)
-  {
-    SetupPresentationContexts();
-  }
-    
-
   void DicomControlUserConnection::Close()
   {
     assert(association_.get() != NULL);
@@ -479,7 +470,7 @@
     {
       DicomMap fields;
       NormalizeFindQuery(fields, level, originalFields);
-      query.reset(ConvertQueryFields(fields, parameters_.GetRemoteManufacturer()));
+      query.reset(ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer()));
     }
     else
     {
@@ -525,7 +516,7 @@
 
 
     const char* universal;
-    if (parameters_.GetRemoteManufacturer() == ModalityManufacturer_GE)
+    if (parameters_.GetRemoteModality().GetManufacturer() == ModalityManufacturer_GE)
     {
       universal = "*";
     }
@@ -669,10 +660,6 @@
     MoveInternal(targetAet, ResourceType_Instance, query);
   }
 
-  void DicomControlUserConnection::SetTimeout(uint32_t seconds)
-  {
-    parameters_.SetTimeout(seconds);
-  }
 
   void DicomControlUserConnection::FindWorklist(DicomFindAnswers& result,
                                                 ParsedDicomFile& query)
--- a/Core/DicomNetworking/DicomControlUserConnection.h	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomControlUserConnection.h	Thu May 07 11:32:15 2020 +0200
@@ -65,9 +65,6 @@
                       const DicomMap& fields);
     
   public:
-    DicomControlUserConnection(const std::string& localAet,
-                               const RemoteModalityParameters& remote);
-    
     DicomControlUserConnection(const DicomAssociationParameters& params);
     
     const DicomAssociationParameters& GetParameters() const
@@ -108,7 +105,5 @@
 
     void FindWorklist(DicomFindAnswers& result,
                       ParsedDicomFile& query);
-
-    void SetTimeout(uint32_t seconds); // 0 = no timeout
   };
 }
--- a/Core/DicomNetworking/DicomStoreUserConnection.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp	Thu May 07 11:32:15 2020 +0200
@@ -84,13 +84,13 @@
              it = syntaxes.begin(); it != syntaxes.end(); ++it)
       {
         association_->ProposePresentationContext(sopClassUid, *it);
+        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it));
       }
 
       if (addLittleEndianImplicit)
       {
-        std::set<DicomTransferSyntax> uncompressed;
-        uncompressed.insert(DicomTransferSyntax_LittleEndianImplicit);
-        association_->ProposePresentationContext(sopClassUid, uncompressed);
+        association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit);
+        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit));
       }
 
       if (addLittleEndianExplicit ||
@@ -109,6 +109,14 @@
         }
 
         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()));
+        }
       }
 
       return true;
@@ -139,42 +147,27 @@
   }
 
 
-  void DicomStoreUserConnection::Setup()
-  {
-    association_.reset(new DicomAssociation);
-    proposeCommonClasses_ = true;
-    proposeUncompressedSyntaxes_ = true;
-    proposeRetiredBigEndian_ = false;
-  }
-    
-        
-  DicomStoreUserConnection::DicomStoreUserConnection(
-    const std::string& localAet,
-    const RemoteModalityParameters& remote) :
-    parameters_(localAet, remote)
-  {
-    Setup();
-  }
-  
-
   DicomStoreUserConnection::DicomStoreUserConnection(
     const DicomAssociationParameters& params) :
-    parameters_(params)
+    parameters_(params),
+    association_(new DicomAssociation),
+    proposeCommonClasses_(true),
+    proposeUncompressedSyntaxes_(true),
+    proposeRetiredBigEndian_(false)
   {
-    Setup();
   }
     
 
   void DicomStoreUserConnection::RegisterStorageClass(const std::string& sopClassUid,
                                                       DicomTransferSyntax syntax)
   {
-    StorageClasses::iterator found = storageClasses_.find(sopClassUid);
+    RegisteredClasses::iterator found = registeredClasses_.find(sopClassUid);
 
-    if (found == storageClasses_.end())
+    if (found == registeredClasses_.end())
     {
       std::set<DicomTransferSyntax> ts;
       ts.insert(syntax);
-      storageClasses_[sopClassUid] = ts;
+      registeredClasses_[sopClassUid] = ts;
     }
     else
     {
@@ -186,22 +179,26 @@
   void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid,
                                                   std::string& sopInstanceUid,
                                                   DicomTransferSyntax& transferSyntax,
-                                                  DcmDataset& dataset)
+                                                  DcmFileFormat& dicom)
   {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
     OFString a, b;
-    if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() ||
-        !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good())
+    if (!dicom.getDataset()->findAndGetOFString(DCM_SOPClassUID, a).good() ||
+        !dicom.getDataset()->findAndGetOFString(DCM_SOPInstanceUID, b).good())
     {
       throw OrthancException(ErrorCode_NoSopClassOrInstance,
                              "Unable to determine the SOP class/instance for C-STORE with AET " +
-                             parameters_.GetRemoteApplicationEntityTitle());
+                             parameters_.GetRemoteModality().GetApplicationEntityTitle());
     }
 
     sopClassUid.assign(a.c_str());
     sopInstanceUid.assign(b.c_str());
 
-    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(
-          transferSyntax, dataset.getOriginalXfer()))
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom))
     {
       throw OrthancException(ErrorCode_InternalError,
                              "Unknown transfer syntax from DCMTK");
@@ -216,7 +213,7 @@
   {
     /**
      * Step 1: Check whether this presentation context is already
-     * available in the previously negociated assocation.
+     * available in the previously negotiated assocation.
      **/
 
     if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax))
@@ -227,24 +224,35 @@
     // The association must be re-negotiated
     if (association_->IsOpen())
     {
-      LOG(INFO) << "Re-negociating DICOM association with "
-                << parameters_.GetRemoteApplicationEntityTitle();
+      LOG(INFO) << "Re-negotiating DICOM association with "
+                << parameters_.GetRemoteModality().GetApplicationEntityTitle();
+
+      if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) !=
+          proposedOriginalClasses_.end())
+      {
+        LOG(INFO) << "The remote modality has already rejected SOP class UID \""
+                  << sopClassUid << "\" with transfer syntax \""
+                  << GetTransferSyntaxUid(transferSyntax) << "\", don't renegotiate";
+        return false;
+      }
     }
+
+    association_->ClearPresentationContexts();
+    proposedOriginalClasses_.clear();
+    RegisterStorageClass(sopClassUid, transferSyntax);  // (*)
+
     
-    association_->ClearPresentationContexts();
-    RegisterStorageClass(sopClassUid, transferSyntax);
-
-      
     /**
      * Step 2: Propose at least the mandatory SOP class.
      **/
 
     {
-      StorageClasses::const_iterator mandatory = storageClasses_.find(sopClassUid);
+      RegisteredClasses::const_iterator mandatory = registeredClasses_.find(sopClassUid);
 
-      if (mandatory == storageClasses_.end() ||
+      if (mandatory == registeredClasses_.end() ||
           mandatory->second.find(transferSyntax) == mandatory->second.end())
       {
+        // Should never fail because of (*)
         throw OrthancException(ErrorCode_InternalError);
       }
 
@@ -263,8 +271,8 @@
      * registered through the "RegisterStorageClass()" method.
      **/
       
-    for (StorageClasses::const_iterator it = storageClasses_.begin();
-         it != storageClasses_.end(); ++it)
+    for (RegisteredClasses::const_iterator it = registeredClasses_.begin();
+         it != registeredClasses_.end(); ++it)
     {
       if (it->first != sopClassUid)
       {
@@ -292,7 +300,7 @@
         std::string c(dcmShortSCUStorageSOPClassUIDs[i]);
           
         if (c != sopClassUid &&
-            storageClasses_.find(c) == storageClasses_.end())
+            registeredClasses_.find(c) == registeredClasses_.end())
         {
           ProposeStorageClass(c, ts);
         }
@@ -312,13 +320,14 @@
 
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
-                                       DcmDataset& dataset,
+                                       DcmFileFormat& dicom,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
     DicomTransferSyntax transferSyntax;
-    LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dataset);
-    
+    LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom);
+
     uint8_t presID;
     if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax))
     {
@@ -327,7 +336,7 @@
                              "SOP class UID [" + sopClassUid + "] and transfer "
                              "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] "
                              "while sending to modality [" +
-                             parameters_.GetRemoteApplicationEntityTitle() + "]");
+                             parameters_.GetRemoteModality().GetApplicationEntityTitle() + "]");
     }
     
     // Prepare the transmission of data
@@ -339,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;
@@ -349,12 +358,17 @@
       request.opts |= O_STORE_MOVEORIGINATORID;
     }
 
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
     // Finally conduct transmission of data
     T_DIMSE_C_StoreRSP response;
     DcmDataset* statusDetail = NULL;
     DicomAssociation::CheckCondition(
       DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request,
-                      NULL, &dataset, /*progressCallback*/ NULL, NULL,
+                      NULL, dicom.getDataset(), /*progressCallback*/ NULL, NULL,
                       /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
                       /*opt_dimse_timeout*/ GetParameters().GetTimeout(),
                       &response, &statusDetail, NULL),
@@ -379,7 +393,7 @@
       sprintf(buf, "%04X", response.DimseStatus);
       throw OrthancException(ErrorCode_NetworkProtocol,
                              "C-STORE SCU to AET \"" +
-                             GetParameters().GetRemoteApplicationEntityTitle() +
+                             GetParameters().GetRemoteModality().GetApplicationEntityTitle() +
                              "\" has failed with DIMSE status 0x" + buf);
     }
   }
@@ -387,37 +401,148 @@
 
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
-                                       ParsedDicomFile& parsed,
-                                       const std::string& moveOriginatorAET,
-                                       uint16_t moveOriginatorID)
-  {
-    if (parsed.GetDcmtkObject().getDataset() == NULL)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-    
-    Store(sopClassUid, sopInstanceUid, *parsed.GetDcmtkObject().getDataset(),
-          moveOriginatorAET, moveOriginatorID);
-  }
-
-
-  void DicomStoreUserConnection::Store(std::string& sopClassUid,
-                                       std::string& sopInstanceUid,
                                        const void* buffer,
                                        size_t size,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
     std::unique_ptr<DcmFileFormat> dicom(
       FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
 
-    if (dicom.get() == NULL ||
-        dicom->getDataset() == NULL)
+    if (dicom.get() == NULL)
     {
       throw OrthancException(ErrorCode_InternalError);
     }
     
-    Store(sopClassUid, sopInstanceUid, *dicom->getDataset(),
-          moveOriginatorAET, moveOriginatorID);
+    Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+  }
+
+
+  void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
+                                                   const std::string& sopClassUid,
+                                                   DicomTransferSyntax sourceSyntax)
+  {
+    acceptedSyntaxes.clear();
+
+    // Make sure a negotiation has already occurred for this transfer
+    // 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);
+
+    std::map<DicomTransferSyntax, uint8_t> contexts;
+    if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid))
+    {
+      for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
+             it = contexts.begin(); it != contexts.end(); ++it)
+      {
+        acceptedSyntaxes.insert(it->first);
+      }
+    }
+  }
+
+
+  void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */,
+                                           std::string& sopInstanceUid /* out */,
+                                           IDicomTranscoder& transcoder,
+                                           const void* buffer,
+                                           size_t size,
+                                           bool hasMoveOriginator,
+                                           const std::string& moveOriginatorAET,
+                                           uint16_t moveOriginatorID)
+  {
+    std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
+    if (dicom.get() == NULL ||
+        dicom->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    DicomTransferSyntax inputSyntax;
+    LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
+
+    std::set<DicomTransferSyntax> accepted;
+    LookupTranscoding(accepted, sopClassUid, inputSyntax);
+
+    if (accepted.find(inputSyntax) != accepted.end())
+    {
+      // No need for transcoding
+      Store(sopClassUid, sopInstanceUid, *dicom,
+            hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+    }
+    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);
+      }
+
+      std::unique_ptr<DcmFileFormat> transcoded;
+
+      bool hasSopInstanceUidChanged;
+      
+      if (transcoder.HasInplaceTranscode())
+      {
+        if (transcoder.InplaceTranscode(hasSopInstanceUidChanged, *dicom, uncompressedSyntaxes, false))
+        {
+          // In-place transcoding is supported and has succeeded
+          transcoded.reset(dicom.release());
+        }
+      }
+      else
+      {
+        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
+      // point. The "sopInstanceUid" might also have changed (if
+      // using lossy compression).
+        
+      if (transcoded == NULL ||
+          transcoded->getDataset() == NULL)
+      {
+        throw OrthancException(
+          ErrorCode_NotImplemented,
+          "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) +
+          "\" to an uncompressed syntax for modality: " +
+          GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+      }
+      else
+      {
+        DicomTransferSyntax transcodedSyntax;
+
+        // Sanity check
+        if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) ||
+            accepted.find(transcodedSyntax) == accepted.end())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+        else
+        {
+          Store(sopClassUid, sopInstanceUid, *transcoded,
+                hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+        }
+      }
+    }
   }
 }
--- a/Core/DicomNetworking/DicomStoreUserConnection.h	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.h	Thu May 07 11:32:15 2020 +0200
@@ -33,15 +33,23 @@
 
 #pragma once
 
+#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
+#  error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file
+#endif
+
 #include "DicomAssociationParameters.h"
 
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#  include "../DicomParsing/IDicomTranscoder.h"
+#endif
+
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include <set>
 #include <stdint.h>  // For uint8_t
 
 
-class DcmDataset;
+class DcmFileFormat;
 
 namespace Orthanc
 {
@@ -64,30 +72,41 @@
   **/
 
   class DicomAssociation;  // Forward declaration for PImpl design pattern
-  class ParsedDicomFile;
 
   class DicomStoreUserConnection : public boost::noncopyable
   {
   private:
-    typedef std::map<std::string, std::set<DicomTransferSyntax> > StorageClasses;
+    typedef std::map<std::string, std::set<DicomTransferSyntax> > RegisteredClasses;
+
+    // "ProposedOriginalClasses" keeps track of the storage classes
+    // that were proposed with a single transfer syntax
+    typedef std::set< std::pair<std::string, DicomTransferSyntax> > ProposedOriginalClasses;
     
     DicomAssociationParameters           parameters_;
     boost::shared_ptr<DicomAssociation>  association_;  // "shared_ptr" is for PImpl
-    StorageClasses                       storageClasses_;
+    RegisteredClasses                    registeredClasses_;
+    ProposedOriginalClasses              proposedOriginalClasses_;
     bool                                 proposeCommonClasses_;
     bool                                 proposeUncompressedSyntaxes_;
     bool                                 proposeRetiredBigEndian_;
 
-    void Setup();
-
     // Return "false" if there is not enough room remaining in the association
     bool ProposeStorageClass(const std::string& sopClassUid,
                              const std::set<DicomTransferSyntax>& syntaxes);
 
+    bool LookupPresentationContext(uint8_t& presentationContextId,
+                                   const std::string& sopClassUid,
+                                   DicomTransferSyntax transferSyntax);
+    
+    bool NegotiatePresentationContext(uint8_t& presentationContextId,
+                                      const std::string& sopClassUid,
+                                      DicomTransferSyntax transferSyntax);
+
+    void LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
+                           const std::string& sopClassUid,
+                           DicomTransferSyntax sourceSyntax);
+
   public:
-    DicomStoreUserConnection(const std::string& localAet,
-                             const RemoteModalityParameters& remote);
-    
     DicomStoreUserConnection(const DicomAssociationParameters& params);
     
     const DicomAssociationParameters& GetParameters() const
@@ -95,11 +114,6 @@
       return parameters_;
     }
 
-    void SetTimeout(int timeout)
-    {
-      parameters_.SetTimeout(timeout);
-    }
-
     void SetCommonClassesProposed(bool proposed)
     {
       proposeCommonClasses_ = proposed;
@@ -133,50 +147,33 @@
     void RegisterStorageClass(const std::string& sopClassUid,
                               DicomTransferSyntax syntax);
 
-    // Should only be used if transcoding
-    // TODO => to private
-    bool LookupPresentationContext(uint8_t& presentationContextId,
-                                   const std::string& sopClassUid,
-                                   DicomTransferSyntax transferSyntax);
-        
-    // TODO => to private
-    bool NegotiatePresentationContext(uint8_t& presentationContextId,
-                                      const std::string& sopClassUid,
-                                      DicomTransferSyntax transferSyntax);
-
-    // TODO => to private
-    void LookupParameters(std::string& sopClassUid,
-                          std::string& sopInstanceUid,
-                          DicomTransferSyntax& transferSyntax,
-                          DcmDataset& dataset);
-
-  private:
     void Store(std::string& sopClassUid,
                std::string& sopInstanceUid,
-               DcmDataset& dataset,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-
-    void Store(std::string& sopClassUid,
-               std::string& sopInstanceUid,
-               ParsedDicomFile& parsed,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-
-  public:
-    void Store(std::string& sopClassUid,
-               std::string& sopInstanceUid,
-               const void* buffer,
-               size_t size,
+               DcmFileFormat& dicom,
+               bool hasMoveOriginator,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
 
     void Store(std::string& sopClassUid,
                std::string& sopInstanceUid,
                const void* buffer,
-               size_t size)
-    {
-      Store(sopClassUid, sopInstanceUid, buffer, size, "", 0);  // Not a C-Move
-    }
+               size_t size,
+               bool hasMoveOriginator,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
+
+    void LookupParameters(std::string& sopClassUid,
+                          std::string& sopInstanceUid,
+                          DicomTransferSyntax& transferSyntax,
+                          DcmFileFormat& dicom);
+
+    void Transcode(std::string& sopClassUid /* out */,
+                   std::string& sopInstanceUid /* out */,
+                   IDicomTranscoder& transcoder,
+                   const void* buffer,
+                   size_t size,
+                   bool hasMoveOriginator,
+                   const std::string& moveOriginatorAET,
+                   uint16_t moveOriginatorID);
   };
 }
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.h	Thu May 07 11:32:15 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/DicomNetworking/TimeoutDicomConnectionManager.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Thu May 07 11:32:15 2020 +0200
@@ -103,7 +103,7 @@
     if (connection_.get() != NULL)
     {
       LOG(INFO) << "Closing inactive DICOM association with modality: "
-                << connection_->GetParameters().GetRemoteApplicationEntityTitle();
+                << connection_->GetParameters().GetRemoteModality().GetApplicationEntityTitle();
 
       connection_.reset(NULL);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DcmtkTranscoder.cpp	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,328 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DcmtkTranscoder.h"
+
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
+#  error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
+#endif
+
+
+#include "FromDcmtkBridge.h"
+#include "../OrthancException.h"
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmjpeg/djrploss.h>  // for DJ_RPLossy
+#include <dcmtk/dcmjpeg/djrplol.h>   // for DJ_RPLossless
+#include <dcmtk/dcmjpls/djrparam.h>  // for DJLSRepresentationParameter
+
+
+namespace Orthanc
+{
+  static uint16_t GetBitsStored(DcmDataset& dataset)
+  {
+    uint16_t bitsStored;
+    if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good())
+    {
+      return bitsStored;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Missing \"Bits Stored\" tag in DICOM instance");
+    }      
+  }
+
+  
+  static std::string GetSopInstanceUid(DcmDataset& dataset)
+  {
+    const char* v = NULL;
+
+    if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() &&
+        v != NULL)
+    {
+      return std::string(v);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID");
+    }
+  }
+
+  
+  static void CheckSopInstanceUid(DcmFileFormat& dicom,
+                                  const std::string& sopInstanceUid,
+                                  bool mustEqual)
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+      
+    bool ok;
+      
+    if (mustEqual)
+    {
+      ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid);
+    }
+    else
+    {
+      ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid);
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" :
+                             "The SOP instance UID has not changed as expected during transcoding");
+    }
+  }
+    
+
+  void DcmtkTranscoder::SetLossyQuality(unsigned int quality)
+  {
+    if (quality <= 0 ||
+        quality > 100)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      lossyQuality_ = quality;
+    }
+  }
+
+    
+  DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */,
+                                                    const void* buffer,
+                                                    size_t size,
+                                                    const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                                    bool allowNewSopInstanceUid) 
+  {
+    std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
+
+    if (dicom.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (InplaceTranscode(hasSopInstanceUidChanged, *dicom, allowedSyntaxes, allowNewSopInstanceUid))
+    {
+      return dicom.release();
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool DcmtkTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                         DcmFileFormat& dicom,
+                                         const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                         bool allowNewSopInstanceUid) 
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    hasSopInstanceUidChanged = false;
+
+    DicomTransferSyntax syntax;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Cannot determine the transfer syntax");
+    }
+
+    const uint16_t bitsStored = GetBitsStored(*dicom.getDataset());
+    std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset());
+    
+    if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end())
+    {
+      // No transcoding is needed
+      return true;
+    }
+      
+    if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+
+    if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+      
+    if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+
+    if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() &&
+        allowNewSopInstanceUid &&
+        bitsStored == 8)
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossy parameters(lossyQuality_);
+        
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        hasSopInstanceUidChanged = true;
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() &&
+        allowNewSopInstanceUid &&
+        bitsStored <= 12)
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossy parameters(lossyQuality_);
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        hasSopInstanceUidChanged = true;
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossless parameters(6 /* opt_selection_value */,
+                               0 /* opt_point_transform */);
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpls/apps/dcmcjpls.cc"
+      DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
+                                             OFTrue /* opt_useLosslessProcess */);
+
+      /**
+       * WARNING: This call results in a segmentation fault if using
+       * the DCMTK package 3.6.2 from Ubuntu 18.04.
+       **/              
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    if (allowNewSopInstanceUid &&
+        allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpls/apps/dcmcjpls.cc"
+      DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
+                                             OFFalse /* opt_useLosslessProcess */);
+
+      /**
+       * WARNING: This call results in a segmentation fault if using
+       * the DCMTK package 3.6.2 from Ubuntu 18.04.
+       **/              
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        hasSopInstanceUidChanged = true;
+        return true;
+      }
+    }
+#endif
+
+    return false;
+  }
+
+    
+  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(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
+
+    if (transcoded.get() == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      if (transcoded->getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }          
+        
+      FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset());
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DcmtkTranscoder.h	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
+#  error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1
+#  error Transcoding is disabled, cannot compile this file
+#endif
+
+#include "IDicomTranscoder.h"
+
+namespace Orthanc
+{
+  class DcmtkTranscoder : public IDicomTranscoder
+  {
+  private:
+    unsigned int  lossyQuality_;
+    
+  public:
+    DcmtkTranscoder() :
+      lossyQuality_(90)
+    {
+    }
+
+    void SetLossyQuality(unsigned int quality);
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+    
+    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
+    {
+      return true;
+    }
+
+    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,
+                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+  };
+}
--- a/Core/DicomParsing/FromDcmtkBridge.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomParsing/FromDcmtkBridge.cpp	Thu May 07 11:32:15 2020 +0200
@@ -1206,7 +1206,7 @@
   }
 
 
-
+  
   static bool SaveToMemoryBufferInternal(std::string& buffer,
                                          DcmFileFormat& dicom,
                                          E_TransferSyntax xfer)
@@ -1269,7 +1269,7 @@
      * dataset into memory. We now keep the original transfer syntax
      * (if available).
      **/
-    E_TransferSyntax xfer = dataSet.getOriginalXfer();
+    E_TransferSyntax xfer = dataSet.getCurrentXfer();
     if (xfer == EXS_Unknown)
     {
       // No information about the original transfer syntax: This is
@@ -1286,29 +1286,7 @@
   }
 
 
-  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmFileFormat& dicom)
-  {
-    E_TransferSyntax xfer = dicom.getDataset()->getOriginalXfer();
-    if (xfer == EXS_Unknown)
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Cannot write a DICOM instance with unknown transfer syntax");
-    }
-    else if (!dicom.validateMetaInfo(xfer).good())
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Cannot setup the transfer syntax to write a DICOM instance");
-    }
-    else
-    {
-      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
-    }
-  }
-
-
-  bool FromDcmtkBridge::Transcode(std::string& buffer,
-                                  DcmFileFormat& dicom,
+  bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom,
                                   DicomTransferSyntax syntax,
                                   const DcmRepresentationParameter* representation)
   {
@@ -1319,16 +1297,33 @@
     }
     else
     {
+      DicomTransferSyntax sourceSyntax;
+      bool known = LookupOrthancTransferSyntax(sourceSyntax, dicom);
+
       if (!dicom.getDataset()->chooseRepresentation(xfer, representation).good() ||
           !dicom.getDataset()->canWriteXfer(xfer) ||
           !dicom.validateMetaInfo(xfer, EWM_updateMeta).good())
       {
         return false;
       }
-
-      dicom.removeInvalidGroups();
-      
-      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
+      else
+      {
+        dicom.removeInvalidGroups();
+
+        if (known)
+        {
+          LOG(INFO) << "Transcoded an image from transfer syntax "
+                    << GetTransferSyntaxUid(sourceSyntax) << " to "
+                    << GetTransferSyntaxUid(syntax);
+        }
+        else
+        {
+          LOG(INFO) << "Transcoded an image from unknown transfer syntax to "
+                    << GetTransferSyntaxUid(syntax);
+        }
+        
+        return true;
+      }
     }
   }
 
@@ -1787,7 +1782,7 @@
     DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
     DcmPixelSequence* pixelSequence = NULL;
     if (!pixelData.getEncapsulatedRepresentation
-        (dataset.getOriginalXfer(), NULL, pixelSequence).good())
+        (dataset.getCurrentXfer(), NULL, pixelSequence).good())
     {
       return NULL;
     }
@@ -2040,25 +2035,6 @@
   }
 
 
-  bool FromDcmtkBridge::LookupTransferSyntax(std::string& result,
-                                             DcmFileFormat& dicom)
-  {
-    const char* value = NULL;
-
-    if (dicom.getMetaInfo() != NULL &&
-        dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
-        value != NULL)
-    {
-      result.assign(value);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
 #if ORTHANC_ENABLE_LUA == 1
   void FromDcmtkBridge::ExecuteToDicom(DicomMap& target,
                                        LuaFunctionCall& call)
@@ -2655,6 +2631,33 @@
     Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
     ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions);
   }
+
+
+
+  bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target,
+                                                    DcmFileFormat& dicom)
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+        
+    DcmDataset& dataset = *dicom.getDataset();
+
+    E_TransferSyntax xfer = dataset.getCurrentXfer();
+    if (xfer == EXS_Unknown)
+    {
+      dataset.updateOriginalXfer();
+      xfer = dataset.getOriginalXfer();
+      if (xfer == EXS_Unknown)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Cannot determine the transfer syntax of the DICOM instance");
+      }
+    }
+
+    return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer);
+  }
 }
 
 
--- a/Core/DicomParsing/FromDcmtkBridge.h	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomParsing/FromDcmtkBridge.h	Thu May 07 11:32:15 2020 +0200
@@ -205,11 +205,7 @@
     static bool SaveToMemoryBuffer(std::string& buffer,
                                    DcmDataset& dataSet);
 
-    static bool SaveToMemoryBuffer(std::string& buffer,
-                                   DcmFileFormat& dicom);
-
-    static bool Transcode(std::string& buffer,
-                          DcmFileFormat& dicom,
+    static bool Transcode(DcmFileFormat& dicom,
                           DicomTransferSyntax syntax,
                           const DcmRepresentationParameter* representation);
 
@@ -248,9 +244,6 @@
     static void FromJson(DicomMap& values,
                          const Json::Value& result);
 
-    static bool LookupTransferSyntax(std::string& result,
-                                     DcmFileFormat& dicom);
-
 #if ORTHANC_ENABLE_LUA == 1
     static void ExecuteToDicom(DicomMap& target,
                                LuaFunctionCall& call);
@@ -284,5 +277,8 @@
 
     static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target,
                                             E_TransferSyntax source);
+
+    static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target,
+                                            DcmFileFormat& dicom);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/IDicomTranscoder.h	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <boost/noncopyable.hpp>
+#include <set>
+
+class DcmFileFormat;
+
+namespace Orthanc
+{
+  /**
+   * WARNING: This class might be called from several threads at
+   * once. Make sure to implement proper locking.
+   **/
+  
+  class IDicomTranscoder : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomTranscoder()
+    {
+    }
+
+    virtual bool TranscodeToBuffer(std::string& target,
+                                   bool& hasSopInstanceUidChanged /* out */,
+                                   const void* buffer,
+                                   size_t size,
+                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                   bool allowNewSopInstanceUid) = 0;
+
+    /**
+     * Transcoding flavor that creates a new parsed DICOM file. A
+     * "std::set<>" is used to give the possible plugin the
+     * possibility to do a single parsing for all the possible
+     * transfer syntaxes.
+     **/
+    virtual DcmFileFormat* TranscodeToParsed(bool& hasSopInstanceUidChanged /* out */,
+                                             const void* buffer,
+                                             size_t size,
+                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                             bool allowNewSopInstanceUid) = 0;
+    
+    virtual bool HasInplaceTranscode() const = 0;
+
+    /**
+     * In-place transcoding. This method is preferred for C-STORE.
+     **/
+    virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                  DcmFileFormat& dicom,
+                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                  bool allowNewSopInstanceUid) = 0;
+  };
+}
--- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Thu May 07 11:32:15 2020 +0200
@@ -676,7 +676,7 @@
   ImageAccessor* DicomImageDecoder::Decode(DcmDataset& dataset,
                                            unsigned int frame)
   {
-    E_TransferSyntax syntax = dataset.getOriginalXfer();
+    E_TransferSyntax syntax = dataset.getCurrentXfer();
 
     /**
      * Deal with uncompressed, raw images.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,131 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "MemoryBufferTranscoder.h"
+
+#include "../OrthancException.h"
+#include "FromDcmtkBridge.h"
+
+namespace Orthanc
+{
+  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 (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 (useDcmtk_)
+    {
+      return dcmtk_.TranscodeToBuffer(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+#endif
+    {
+      return Transcode(target, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+  }
+
+  
+  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 (useDcmtk_)
+    {
+      return dcmtk_.TranscodeToParsed(hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+#endif
+    {
+      std::string transcoded;
+      if (Transcode(transcoded, hasSopInstanceUidChanged, buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
+      {
+        return FromDcmtkBridge::LoadFromMemoryBuffer(
+          transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size());
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+  }
+
+
+  bool MemoryBufferTranscoder::InplaceTranscode(bool& hasSopInstanceUidChanged,
+                                                DcmFileFormat& dicom,
+                                                const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                                bool allowNewSopInstanceUid)
+  {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    if (useDcmtk_)
+    {
+      return dcmtk_.InplaceTranscode(hasSopInstanceUidChanged, dicom, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+#endif
+    {
+      // "HasInplaceTranscode()" should have been called
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/MemoryBufferTranscoder.h	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,101 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
+#  error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#  include "DcmtkTranscoder.h"
+#endif
+
+namespace Orthanc
+{
+  // This is the basis class for transcoding plugins
+  class MemoryBufferTranscoder : public IDicomTranscoder
+  {
+  private:
+    bool  useDcmtk_;
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DcmtkTranscoder  dcmtk_;
+#endif
+
+  protected:
+    virtual bool Transcode(std::string& target,
+                           bool& hasSopInstanceUidChanged /* out */,
+                           const void* buffer,
+                           size_t size,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) = 0;
+    
+  public:
+    /**
+     * If "useDcmtk" is "true", the transcoder will first try and call
+     * DCMTK, before calling its own "Transcode()" implementation.
+     **/
+    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(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 useDcmtk_;
+    }
+    
+    virtual bool InplaceTranscode(bool& hasSopInstanceUidChanged /* out */,
+                                  DcmFileFormat& dicom,
+                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                  bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+  };
+}
--- a/Core/DicomParsing/ParsedDicomFile.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Thu May 07 11:32:15 2020 +0200
@@ -456,7 +456,7 @@
                                       const UriComponents& uri)
   {
     DcmItem* dicom = pimpl_->file_->getDataset();
-    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getCurrentXfer();
 
     // Special case: Accessing the pixel data
     if (uri.size() == 1 || 
@@ -1564,7 +1564,7 @@
 
     pimpl_->frameIndex_->GetRawFrame(target, frameId);
 
-    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getCurrentXfer();
     switch (transferSyntax)
     {
       case EXS_JPEGProcess1:
@@ -1625,7 +1625,22 @@
 
   bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
   {
-    return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
+    // TODO - Shouldn't "dataset.getCurrentXfer()" be used instead of
+    // using the meta header?
+    const char* value = NULL;
+
+    assert(pimpl_->file_ != NULL);
+    if (pimpl_->file_->getMetaInfo() != NULL &&
+        pimpl_->file_->getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
+        value != NULL)
+    {
+      result.assign(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
   }
 
 
--- a/Core/Enumerations.h	Thu May 07 11:31:58 2020 +0200
+++ b/Core/Enumerations.h	Thu May 07 11:32:15 2020 +0200
@@ -279,13 +279,13 @@
     DicomTransferSyntax_JPEG2000Multicomponent    /*!< JPEG 2000 part 2 multicomponent extensions (lossless or lossy) */,
     DicomTransferSyntax_JPIPReferenced    /*!< JPIP Referenced */,
     DicomTransferSyntax_JPIPReferencedDeflate    /*!< JPIP Referenced Deflate */,
-    DicomTransferSyntax_MPEG2MainProfileAtMainLevel    /*!< MPEG2 Main Profile at Main Level */,
-    DicomTransferSyntax_MPEG2MainProfileAtHighLevel    /*!< MPEG2 Main Profile at High Level */,
-    DicomTransferSyntax_MPEG4HighProfileLevel4_1    /*!< MPEG4 High Profile / Level 4.1 */,
-    DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1    /*!< MPEG4 BD-compatible High Profile / Level 4.1 */,
-    DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo    /*!< MPEG4 High Profile / Level 4.2 For 2D Video */,
-    DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo    /*!< MPEG4 High Profile / Level 4.2 For 3D Video */,
-    DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2    /*!< 1.2.840.10008.1.2.4.106 */,
+    DicomTransferSyntax_MPEG2MainProfileAtMainLevel    /*!< MPEG2 Main Profile / Main Level */,
+    DicomTransferSyntax_MPEG2MainProfileAtHighLevel    /*!< MPEG2 Main Profile / High Level */,
+    DicomTransferSyntax_MPEG4HighProfileLevel4_1    /*!< MPEG4 AVC/H.264 High Profile / Level 4.1 */,
+    DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1    /*!< MPEG4 AVC/H.264 BD-compatible High Profile / Level 4.1 */,
+    DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo    /*!< MPEG4 AVC/H.264 High Profile / Level 4.2 For 2D Video */,
+    DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo    /*!< MPEG4 AVC/H.264 High Profile / Level 4.2 For 3D Video */,
+    DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2    /*!< MPEG4 AVC/H.264 Stereo High Profile / Level 4.2 */,
     DicomTransferSyntax_HEVCMainProfileLevel5_1    /*!< HEVC/H.265 Main Profile / Level 5.1 */,
     DicomTransferSyntax_HEVCMain10ProfileLevel5_1    /*!< HEVC/H.265 Main 10 Profile / Level 5.1 */,
     DicomTransferSyntax_RLELossless    /*!< RLE - Run Length Encoding (lossless) */,
--- a/NEWS	Thu May 07 11:31:58 2020 +0200
+++ b/NEWS	Thu May 07 11:32:15 2020 +0200
@@ -12,6 +12,11 @@
   - "/queries/.../answers/../retrieve": "TargetAet" not mandatory anymore
     (defaults to the local AET)
 
+Maintenance
+-----------
+
+* Upgraded dependencies for static builds (notably on Windows and LSB):
+  - openssl 1.1.1g
 
 
 Version 1.6.1 (2020-04-21)
--- a/OrthancServer/LuaScripting.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Thu May 07 11:32:15 2020 +0200
@@ -113,14 +113,15 @@
 
         if (connection_.get() == NULL)
         {
-          connection_.reset(new DicomStoreUserConnection(localAet_, remote_));
+          DicomAssociationParameters params(localAet_, remote_);
+          connection_.reset(new DicomStoreUserConnection(params));
         }
 
         std::string sopClassUid, sopInstanceUid;  // Unused
 
         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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu May 07 11:32:15 2020 +0200
@@ -54,12 +54,14 @@
 namespace Orthanc
 {
   static const char* const KEY_LEVEL = "Level";
-  static const char* const KEY_QUERY = "Query";
+  static const char* const KEY_LOCAL_AET = "LocalAet";
   static const char* const KEY_NORMALIZE = "Normalize";
+  static const char* const KEY_QUERY = "Query";
   static const char* const KEY_RESOURCES = "Resources";
+  static const char* const KEY_TARGET_AET = "TargetAet";
+  static const char* const KEY_TIMEOUT = "Timeout";
   static const char* const SOP_CLASS_UID = "SOPClassUID";
   static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
-
   
   static RemoteModalityParameters MyGetModalityUsingSymbolicName(const std::string& name)
   {
@@ -68,45 +70,62 @@
   }
 
 
+  static void InjectAssociationTimeout(DicomAssociationParameters& params,
+                                       const Json::Value& body)
+  {
+    if (body.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object");
+    }
+    else if (body.isMember(KEY_TIMEOUT))
+    {
+      // New in Orthanc 1.7.0
+      params.SetTimeout(SerializationToolbox::ReadUnsignedInteger(body, KEY_TIMEOUT));
+    }
+  }
+
+  static DicomAssociationParameters GetAssociationParameters(RestApiPostCall& call,
+                                                             const Json::Value& body)
+  {   
+    const std::string& localAet =
+      OrthancRestApi::GetContext(call).GetDefaultLocalApplicationEntityTitle();
+    const RemoteModalityParameters remote =
+      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+
+    DicomAssociationParameters params(localAet, remote);
+    InjectAssociationTimeout(params, body);
+    
+    return params;
+  }
+
+
+  static DicomAssociationParameters GetAssociationParameters(RestApiPostCall& call)
+  {
+    Json::Value body;
+    call.ParseJsonRequest(body);
+    return GetAssociationParameters(call, body);
+  }
+  
+
   /***************************************************************************
    * DICOM C-Echo SCU
    ***************************************************************************/
 
   static void DicomEcho(RestApiPostCall& call)
   {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
-    Json::Value request;
-    call.ParseJsonRequest(request);
-    int timeout = Toolbox::GetJsonIntegerField(request, "Timeout", -1);
-
-    try
-    {
-      DicomControlUserConnection connection(localAet, remote);
+    DicomControlUserConnection connection(GetAssociationParameters(call));
 
-      // New in Orthanc 1.7.0
-      if (timeout != -1)
-      {
-        connection.SetTimeout(timeout);
-      }
-
-      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);
   }
 
 
@@ -198,7 +217,6 @@
   static void DicomFindPatient(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
-    ServerContext& context = OrthancRestApi::GetContext(call);
 
     DicomMap fields;
     DicomMap::SetupFindPatientTemplate(fields);
@@ -207,14 +225,10 @@
       return;
     }
 
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-    
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(localAet, remote);
+      DicomControlUserConnection connection(GetAssociationParameters(call));
       FindPatient(answers, connection, fields);
     }
 
@@ -226,7 +240,6 @@
   static void DicomFindStudy(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
-    ServerContext& context = OrthancRestApi::GetContext(call);
 
     DicomMap fields;
     DicomMap::SetupFindStudyTemplate(fields);
@@ -241,14 +254,10 @@
       return;
     }        
       
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(localAet, remote);
+      DicomControlUserConnection connection(GetAssociationParameters(call));
       FindStudy(answers, connection, fields);
     }
 
@@ -260,7 +269,6 @@
   static void DicomFindSeries(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
-    ServerContext& context = OrthancRestApi::GetContext(call);
 
     DicomMap fields;
     DicomMap::SetupFindSeriesTemplate(fields);
@@ -276,14 +284,10 @@
       return;
     }        
          
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(localAet, remote);
+      DicomControlUserConnection connection(GetAssociationParameters(call));
       FindSeries(answers, connection, fields);
     }
 
@@ -295,7 +299,6 @@
   static void DicomFindInstance(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
-    ServerContext& context = OrthancRestApi::GetContext(call);
 
     DicomMap fields;
     DicomMap::SetupFindInstanceTemplate(fields);
@@ -312,14 +315,10 @@
       return;
     }        
          
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
     DicomFindAnswers answers(false);
 
     {
-      DicomControlUserConnection connection(localAet, remote);
+      DicomControlUserConnection connection(GetAssociationParameters(call));
       FindInstance(answers, connection, fields);
     }
 
@@ -344,7 +343,6 @@
   static void DicomFind(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
-    ServerContext& context = OrthancRestApi::GetContext(call);
 
     DicomMap m;
     DicomMap::SetupFindPatientTemplate(m);
@@ -353,11 +351,7 @@
       return;
     }
  
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
-    DicomControlUserConnection connection(localAet, remote);
+    DicomControlUserConnection connection(GetAssociationParameters(call));
     
     DicomFindAnswers patients(false);
     FindPatient(patients, connection, m);
@@ -620,8 +614,8 @@
     Json::Value body;
     if (call.ParseJsonRequest(body))
     {
-      targetAet = Toolbox::GetJsonStringField(body, "TargetAet", context.GetDefaultLocalApplicationEntityTitle());
-      timeout = Toolbox::GetJsonIntegerField(body, "Timeout", -1);
+      targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
+      timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1);
     }
     else
     {
@@ -643,7 +637,12 @@
       job->SetTargetAet(targetAet);
       job->SetLocalAet(query.GetHandler().GetLocalAet());
       job->SetRemoteModality(query.GetHandler().GetRemoteModality());
-      job->SetTimeout(timeout);
+
+      if (timeout >= 0)
+      {
+        // New in Orthanc 1.7.0
+        job->SetTimeout(static_cast<uint32_t>(timeout));
+      }
 
       LOG(WARNING) << "Driving C-Move SCU on remote modality "
                    << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle()
@@ -968,13 +967,11 @@
     GetInstancesToExport(request, *job, remote, call);
 
     std::string localAet = Toolbox::GetJsonStringField
-      (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle());
+      (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle());
     std::string moveOriginatorAET = Toolbox::GetJsonStringField
       (request, "MoveOriginatorAet", context.GetDefaultLocalApplicationEntityTitle());
     int moveOriginatorID = Toolbox::GetJsonIntegerField
       (request, "MoveOriginatorID", 0 /* By default, not a C-MOVE */);
-    int timeout = Toolbox::GetJsonIntegerField
-      (request, "Timeout", -1);
 
     job->SetLocalAet(localAet);
     job->SetRemoteModality(MyGetModalityUsingSymbolicName(remote));
@@ -991,7 +988,10 @@
     }
 
     // New in Orthanc 1.7.0
-    job->SetTimeout(timeout);
+    if (request.isMember(KEY_TIMEOUT))
+    {
+      job->SetTimeout(SerializationToolbox::ReadUnsignedInteger(request, KEY_TIMEOUT));
+    }
 
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, request);
@@ -1000,17 +1000,12 @@
 
   static void DicomStoreStraight(RestApiPostCall& call)
   {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
-    DicomStoreUserConnection connection(localAet, remote);
+    Json::Value body = Json::objectValue;  // No body
+    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;
@@ -1044,22 +1039,18 @@
     ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
     
     std::string localAet = Toolbox::GetJsonStringField
-      (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle());
+      (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle());
     std::string targetAet = Toolbox::GetJsonStringField
-      (request, "TargetAet", context.GetDefaultLocalApplicationEntityTitle());
-    int timeout = Toolbox::GetJsonIntegerField
-      (request, "Timeout", -1);
+      (request, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
 
     const RemoteModalityParameters source =
       MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-    DicomControlUserConnection connection(localAet, source);
+    DicomAssociationParameters params(localAet, source);
+    InjectAssociationTimeout(params, request);
 
-    if (timeout > -1)
-    {
-      connection.SetTimeout(timeout);
-    }
-    
+    DicomControlUserConnection connection(params);
+
     for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
     {
       DicomMap resource;
@@ -1325,15 +1316,9 @@
 
   static void DicomFindWorklist(RestApiPostCall& call)
   {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
     Json::Value json;
     if (call.ParseJsonRequest(json))
     {
-      const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-      const RemoteModalityParameters remote =
-        MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
       std::unique_ptr<ParsedDicomFile> query
         (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0),
                                          "" /* no private creator */));
@@ -1341,7 +1326,7 @@
       DicomFindAnswers answers(true);
 
       {
-        DicomControlUserConnection connection(localAet, remote);
+        DicomControlUserConnection connection(GetAssociationParameters(call, json));
         connection.FindWorklist(answers, *query);
       }
 
--- a/OrthancServer/QueryRetrieveHandler.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Thu May 07 11:32:15 2020 +0200
@@ -82,7 +82,8 @@
       FixQueryLua(fixed, context_, modality_.GetApplicationEntityTitle()); 
 
       {
-        DicomControlUserConnection connection(localAet_, modality_);
+        DicomAssociationParameters params(localAet_, modality_);
+        DicomControlUserConnection connection(params);
         connection.Find(answers_, level_, fixed, findNormalized_);
       }
 
--- a/OrthancServer/ServerContext.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerContext.h	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Thu May 07 11:32:15 2020 +0200
@@ -48,12 +48,7 @@
   {
     if (connection_.get() == NULL)
     {
-      connection_.reset(new DicomStoreUserConnection(localAet_, remote_));
-
-      if (timeout_ > -1)
-      {
-        connection_->SetTimeout(timeout_);
-      }
+      connection_.reset(new DicomStoreUserConnection(parameters_));
     }
   }
 
@@ -64,7 +59,7 @@
     OpenConnection();
 
     LOG(INFO) << "Sending instance " << instance << " to modality \"" 
-              << remote_.GetApplicationEntityTitle() << "\"";
+              << parameters_.GetRemoteModality().GetApplicationEntityTitle() << "\"";
 
     std::string dicom;
 
@@ -79,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_)
     {
@@ -108,7 +93,7 @@
         assert(IsStarted());
         connection_.reset(NULL);
         
-        const std::string& remoteAet = remote_.GetApplicationEntityTitle();
+        const std::string& remoteAet = parameters_.GetRemoteModality().GetApplicationEntityTitle();
         
         LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet;
 
@@ -120,8 +105,7 @@
         std::vector<std::string> a(sopClassUids_.begin(), sopClassUids_.end());
         std::vector<std::string> b(sopInstanceUids_.begin(), sopInstanceUids_.end());
 
-        DicomAssociationParameters parameters(localAet_, remote_);
-        DicomAssociation::RequestStorageCommitment(parameters, transactionUid_, a, b);
+        DicomAssociation::RequestStorageCommitment(parameters_, transactionUid_, a, b);
       }
     }
 
@@ -139,7 +123,6 @@
 
   DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) :
     context_(context),
-    localAet_("ORTHANC"),
     moveOriginatorId_(0),      // By default, not a C-MOVE
     storageCommitment_(false)  // By default, no storage commitment
   {
@@ -155,7 +138,7 @@
     }
     else
     {
-      localAet_ = aet;
+      parameters_.SetLocalApplicationEntityTitle(aet);
     }
   }
 
@@ -168,11 +151,24 @@
     }
     else
     {
-      remote_ = remote;
+      parameters_.SetRemoteModality(remote);
     }
   }
 
     
+  void DicomModalityStoreJob::SetTimeout(uint32_t seconds)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetTimeout(seconds);
+    }
+  }
+
+
   const std::string& DicomModalityStoreJob::GetMoveOriginatorAet() const
   {
     if (HasMoveOriginator())
@@ -260,8 +256,8 @@
   {
     SetOfInstancesJob::GetPublicContent(value);
     
-    value["LocalAet"] = localAet_;
-    value["RemoteAet"] = remote_.GetApplicationEntityTitle();
+    value["LocalAet"] = parameters_.GetLocalApplicationEntityTitle();
+    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
 
     if (HasMoveOriginator())
     {
@@ -276,12 +272,9 @@
   }
 
 
-  static const char* LOCAL_AET = "LocalAet";
-  static const char* REMOTE = "Remote";
   static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet";
   static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId";
   static const char* STORAGE_COMMITMENT = "StorageCommitment";
-  static const char* TIMEOUT = "Timeout";
   
 
   DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context,
@@ -289,15 +282,12 @@
     SetOfInstancesJob(serialized),
     context_(context)
   {
-    localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET);
-    remote_ = RemoteModalityParameters(serialized[REMOTE]);
     moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET);
     moveOriginatorId_ = static_cast<uint16_t>
       (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID));
     EnableStorageCommitment(SerializationToolbox::ReadBoolean(serialized, STORAGE_COMMITMENT));
 
-    // New in Orthanc in 1.7.0
-    timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, -1);
+    parameters_ = DicomAssociationParameters::UnserializeJob(serialized);
   }
 
 
@@ -309,12 +299,10 @@
     }
     else
     {
-      target[LOCAL_AET] = localAet_;
-      remote_.Serialize(target[REMOTE], true /* force advanced format */);
+      parameters_.SerializeJob(target);
       target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_;
       target[MOVE_ORIGINATOR_ID] = moveOriginatorId_;
       target[STORAGE_COMMITMENT] = storageCommitment_;
-      target[TIMEOUT] = timeout_;
       return true;
     }
   }  
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.h	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h	Thu May 07 11:32:15 2020 +0200
@@ -47,9 +47,7 @@
   {
   private:
     ServerContext&                             context_;
-    std::string                                localAet_;
-    RemoteModalityParameters                   remote_;
-    int                                        timeout_;
+    DicomAssociationParameters                 parameters_;
     std::string                                moveOriginatorAet_;
     uint16_t                                   moveOriginatorId_;
     std::unique_ptr<DicomStoreUserConnection>  connection_;
@@ -75,29 +73,16 @@
     DicomModalityStoreJob(ServerContext& context,
                           const Json::Value& serialized);
 
-    const std::string& GetLocalAet() const
+    const DicomAssociationParameters& GetParameters() const
     {
-      return localAet_;
+      return parameters_;
     }
 
     void SetLocalAet(const std::string& aet);
 
-    const RemoteModalityParameters& GetRemoteModality() const
-    {
-      return remote_;
-    }
-
     void SetRemoteModality(const RemoteModalityParameters& remote);
 
-    void SetTimeout(int timeout)
-    {
-      timeout_ = timeout;
-    }
-
-    int GetTimeout() const
-    {
-      return timeout_;
-    }
+    void SetTimeout(uint32_t seconds);
 
     bool HasMoveOriginator() const
     {
@@ -125,5 +110,10 @@
     virtual void Reset() ORTHANC_OVERRIDE;
 
     void EnableStorageCommitment(bool enabled);
+
+    bool HasStorageCommitment() const
+    {
+      return storageCommitment_;
+    }
   };
 }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp	Thu May 07 11:32:15 2020 +0200
@@ -97,14 +97,9 @@
   {
     if (connection_.get() == NULL)
     {
-      connection_.reset(new DicomControlUserConnection(localAet_, remote_));
+      connection_.reset(new DicomControlUserConnection(parameters_));
     }
     
-    if (timeout_ > -1)
-    {
-      connection_->SetTimeout(timeout_);
-    }
-
     connection_->Move(targetAet_, findAnswer);
   }
 
@@ -158,7 +153,7 @@
     }
     else
     {
-      localAet_ = aet;
+      parameters_.SetLocalApplicationEntityTitle(aet);
     }
   }
 
@@ -184,7 +179,20 @@
     }
     else
     {
-      remote_ = remote;
+      parameters_.SetRemoteModality(remote);
+    }
+  }
+
+
+  void DicomMoveScuJob::SetTimeout(uint32_t seconds)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parameters_.SetTimeout(seconds);
     }
   }
 
@@ -198,9 +206,9 @@
   void DicomMoveScuJob::GetPublicContent(Json::Value& value)
   {
     SetOfCommandsJob::GetPublicContent(value);
-    
-    value["LocalAet"] = localAet_;
-    value["RemoteAet"] = remote_.GetApplicationEntityTitle();
+
+    value["LocalAet"] = parameters_.GetLocalApplicationEntityTitle();
+    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
     value["Query"] = query_;
   }
 
@@ -211,18 +219,14 @@
     context_(context),
     query_(Json::arrayValue)
   {
-    localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET);
+    parameters_ = DicomAssociationParameters::UnserializeJob(serialized);
     targetAet_ = SerializationToolbox::ReadString(serialized, TARGET_AET);
-    remote_ = RemoteModalityParameters(serialized[REMOTE]);
 
     if (serialized.isMember(QUERY) &&
         serialized[QUERY].type() == Json::arrayValue)
     {
       query_ = serialized[QUERY];
     }
-
-    // New in Orthanc in 1.7.0
-    timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, -1);
   }
 
   
@@ -234,11 +238,9 @@
     }
     else
     {
-      target[LOCAL_AET] = localAet_;
+      parameters_.SerializeJob(target);
       target[TARGET_AET] = targetAet_;
       target[QUERY] = query_;
-      target[TIMEOUT] = timeout_;
-      remote_.Serialize(target[REMOTE], true /* force advanced format */);
       return true;
     }
   }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.h	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h	Thu May 07 11:32:15 2020 +0200
@@ -49,12 +49,10 @@
     class Command;
     class Unserializer;
     
-    ServerContext&            context_;
-    std::string               localAet_;
-    std::string               targetAet_;
-    RemoteModalityParameters  remote_;
-    int                       timeout_;
-    Json::Value               query_;
+    ServerContext&              context_;
+    DicomAssociationParameters  parameters_;
+    std::string                 targetAet_;
+    Json::Value                 query_;
 
     std::unique_ptr<DicomControlUserConnection>  connection_;
     
@@ -74,39 +72,25 @@
     
     void AddFindAnswer(QueryRetrieveHandler& query,
                        size_t i);
-    
-    const std::string& GetLocalAet() const
+
+    const DicomAssociationParameters& GetParameters() const
     {
-      return localAet_;
+      return parameters_;
     }
+    
+    void SetLocalAet(const std::string& aet);
 
-    void SetLocalAet(const std::string& aet);
+    void SetRemoteModality(const RemoteModalityParameters& remote);
+
+    void SetTimeout(uint32_t timeout);
 
     const std::string& GetTargetAet() const
     {
       return targetAet_;
     }
-
+    
     void SetTargetAet(const std::string& aet);
 
-    const RemoteModalityParameters& GetRemoteModality() const
-    {
-      return remote_;
-    }
-
-    void SetRemoteModality(const RemoteModalityParameters& remote);
-
-    void SetTimeout(int timeout)
-    {
-      timeout_ = timeout;
-    }
-
-    int GetTimeout() const
-    {
-      return timeout_;
-    }
-
-
     virtual void Stop(JobStopReason reason);
 
     virtual void GetJobType(std::string& target)
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.h	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Thu May 07 11:32:15 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	Thu May 07 11:31:58 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Thu May 07 11:32:15 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/CMake/OpenSslConfigurationStatic-1.1.1.cmake	Thu May 07 11:31:58 2020 +0200
+++ b/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake	Thu May 07 11:32:15 2020 +0200
@@ -1,6 +1,6 @@
-SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.1.1f)
-SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.1.1f.tar.gz")
-SET(OPENSSL_MD5 "3f486f2f4435ef14b81814dbbc7b48bb")
+SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.1.1g)
+SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.1.1g.tar.gz")
+SET(OPENSSL_MD5 "76766e98997660138cdaf13a187bd234")
 
 if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
   set(FirstRun OFF)
@@ -27,7 +27,7 @@
   # Apply the patches
   execute_process(
     COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-    ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1f.patch
+    ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1g.patch
     WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
     RESULT_VARIABLE Failure
     )
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu May 07 11:31:58 2020 +0200
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu May 07 11:32:15 2020 +0200
@@ -501,6 +501,10 @@
   # New in Orthanc 1.6.0
   if (ENABLE_DCMTK_TRANSCODING)
     add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=1)
+    list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL
+      ${ORTHANC_ROOT}/Core/DicomParsing/DcmtkTranscoder.cpp
+      ${ORTHANC_ROOT}/Core/DicomParsing/MemoryBufferTranscoder.cpp
+      )
   else()
     add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=0)
   endif()
--- a/Resources/Configuration.json	Thu May 07 11:31:58 2020 +0200
+++ b/Resources/Configuration.json	Thu May 07 11:32:15 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/Resources/DicomTransferSyntaxes.json	Thu May 07 11:31:58 2020 +0200
+++ b/Resources/DicomTransferSyntaxes.json	Thu May 07 11:32:15 2020 +0200
@@ -274,7 +274,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.100",
-    "Name" : "MPEG2 Main Profile at Main Level",
+    "Name" : "MPEG2 Main Profile / Main Level",
     "Value" : "MPEG2MainProfileAtMainLevel",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG2MainProfileAtMainLevel"
@@ -282,7 +282,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.101",
-    "Name" : "MPEG2 Main Profile at High Level",
+    "Name" : "MPEG2 Main Profile / High Level",
     "Value" : "MPEG2MainProfileAtHighLevel",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG2MainProfileAtHighLevel"
@@ -290,7 +290,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.102",
-    "Name" : "MPEG4 High Profile / Level 4.1",
+    "Name" : "MPEG4 AVC/H.264 High Profile / Level 4.1",
     "Value" : "MPEG4HighProfileLevel4_1",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4HighProfileLevel4_1",
@@ -299,7 +299,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.103",
-    "Name" : "MPEG4 BD-compatible High Profile / Level 4.1",
+    "Name" : "MPEG4 AVC/H.264 BD-compatible High Profile / Level 4.1",
     "Value" : "MPEG4BDcompatibleHighProfileLevel4_1",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4BDcompatibleHighProfileLevel4_1",
@@ -308,7 +308,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.104",
-    "Name" : "MPEG4 High Profile / Level 4.2 For 2D Video",
+    "Name" : "MPEG4 AVC/H.264 High Profile / Level 4.2 For 2D Video",
     "Value" : "MPEG4HighProfileLevel4_2_For2DVideo",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For2DVideo",
@@ -317,7 +317,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.105",
-    "Name" : "MPEG4 High Profile / Level 4.2 For 3D Video",
+    "Name" : "MPEG4 AVC/H.264 High Profile / Level 4.2 For 3D Video",
     "Value" : "MPEG4HighProfileLevel4_2_For3DVideo",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For3DVideo",
@@ -326,7 +326,7 @@
 
   {
     "UID" : "1.2.840.10008.1.2.4.106",
-    "Name" : "1.2.840.10008.1.2.4.106",
+    "Name" : "MPEG4 AVC/H.264 Stereo High Profile / Level 4.2",
     "Value" : "MPEG4StereoHighProfileLevel4_2",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4StereoHighProfileLevel4_2",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/TestTranscoding.cpp	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,967 @@
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmFileFormat& dicom,
+                                           DicomTransferSyntax syntax)
+  {
+    E_TransferSyntax xfer;
+    if (!LookupDcmtkTransferSyntax(xfer, syntax))
+    {
+      return false;
+    }
+    else if (!dicom.validateMetaInfo(xfer).good())
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot setup the transfer syntax to write a DICOM instance");
+    }
+    else
+    {
+      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
+    }
+  }
+
+
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmFileFormat& dicom)
+  {
+    E_TransferSyntax xfer = dicom.getDataset()->getCurrentXfer();
+    if (xfer == EXS_Unknown)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot write a DICOM instance with unknown transfer syntax");
+    }
+    else if (!dicom.validateMetaInfo(xfer).good())
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot setup the transfer syntax to write a DICOM instance");
+    }
+    else
+    {
+      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
+    }
+  }
+
+
+
+
+
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+
+#include "../Core/DicomParsing/Internals/DicomFrameIndex.h"
+
+namespace Orthanc
+{
+  class IParsedDicomImage : public boost::noncopyable
+  {
+  public:
+    virtual ~IParsedDicomImage()
+    {
+    }
+
+    virtual DicomTransferSyntax GetTransferSyntax() = 0;
+
+    virtual std::string GetSopClassUid() = 0;
+
+    virtual std::string GetSopInstanceUid() = 0;
+
+    virtual unsigned int GetFramesCount() = 0;
+
+    // Can return NULL, for compressed transfer syntaxes
+    virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) = 0;
+    
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) = 0;
+    
+    virtual void WriteToMemoryBuffer(std::string& target) = 0;
+  };
+
+
+  class IDicomImageReader : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomImageReader()
+    {
+    }
+
+    virtual IParsedDicomImage* Read(const void* data,
+                                    size_t size) = 0;
+
+    virtual IParsedDicomImage* Transcode(const void* data,
+                                         size_t size,
+                                         DicomTransferSyntax syntax,
+                                         bool allowNewSopInstanceUid) = 0;
+  };
+
+
+  class DcmtkImageReader : public IDicomImageReader
+  {
+  private:
+    class Image : public IParsedDicomImage
+    {
+    private:
+      std::unique_ptr<DcmFileFormat>    dicom_;
+      std::unique_ptr<DicomFrameIndex>  index_;
+      DicomTransferSyntax               transferSyntax_;
+      std::string                       sopClassUid_;
+      std::string                       sopInstanceUid_;
+
+      static std::string GetStringTag(DcmDataset& dataset,
+                                      const DcmTagKey& tag)
+      {
+        const char* value = NULL;
+
+        if (!dataset.findAndGetString(tag, value).good() ||
+            value == NULL)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Missing SOP class/instance UID in DICOM instance");
+        }
+        else
+        {
+          return std::string(value);
+        }
+      }
+
+    public:
+      Image(DcmFileFormat* dicom,
+            DicomTransferSyntax syntax) :
+        dicom_(dicom),
+        transferSyntax_(syntax)
+      {
+        if (dicom == NULL ||
+            dicom_->getDataset() == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        DcmDataset& dataset = *dicom_->getDataset();
+        index_.reset(new DicomFrameIndex(dataset));
+
+        sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
+        sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
+      }
+
+      virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
+      {
+        return transferSyntax_;
+      }
+
+      virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
+      {
+        return sopClassUid_;
+      }
+    
+      virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
+      {
+        return sopInstanceUid_;
+      }
+
+      virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
+      {
+        return index_->GetFramesCount();
+      }
+
+      virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
+      {
+        assert(dicom_.get() != NULL);
+        if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, transferSyntax_))
+        {
+          throw OrthancException(ErrorCode_InternalError,
+                                 "Cannot write the DICOM instance to a memory buffer");
+        }
+      }
+
+      virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) ORTHANC_OVERRIDE
+      {
+        assert(dicom_.get() != NULL &&
+               dicom_->getDataset() != NULL);
+        return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
+      }
+
+      virtual void GetCompressedFrame(std::string& target,
+                                      unsigned int frame) ORTHANC_OVERRIDE
+      {
+        assert(index_.get() != NULL);
+        index_->GetRawFrame(target, frame);
+      }
+    };
+
+    unsigned int lossyQuality_;
+
+    static DicomTransferSyntax DetectTransferSyntax(DcmFileFormat& dicom)
+    {
+      if (dicom.getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+        
+      DcmDataset& dataset = *dicom.getDataset();
+
+      E_TransferSyntax xfer = dataset.getCurrentXfer();
+      if (xfer == EXS_Unknown)
+      {
+        dataset.updateOriginalXfer();
+        xfer = dataset.getCurrentXfer();
+        if (xfer == EXS_Unknown)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Cannot determine the transfer syntax of the DICOM instance");
+        }
+      }
+
+      DicomTransferSyntax syntax;
+      if (FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, xfer))
+      {
+        return syntax;
+      }
+      else
+      {
+        throw OrthancException(
+          ErrorCode_BadFileFormat,
+          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
+      }
+    }
+
+
+    static uint16_t GetBitsStored(DcmFileFormat& dicom)
+    {
+      if (dicom.getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      uint16_t bitsStored;
+      if (dicom.getDataset()->findAndGetUint16(DCM_BitsStored, bitsStored).good())
+      {
+        return bitsStored;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing \"Bits Stored\" tag in DICOM instance");
+      }      
+    }
+    
+      
+  public:
+    DcmtkImageReader() :
+      lossyQuality_(90)
+    {
+    }
+
+    void SetLossyQuality(unsigned int quality)
+    {
+      if (quality <= 0 ||
+          quality > 100)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        lossyQuality_ = quality;
+      }
+    }
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+
+    virtual IParsedDicomImage* Read(const void* data,
+                                    size_t size)
+    {
+      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
+      if (dicom.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      DicomTransferSyntax transferSyntax = DetectTransferSyntax(*dicom);
+
+      return new Image(dicom.release(), transferSyntax);
+    }
+
+    virtual IParsedDicomImage* Transcode(const void* data,
+                                         size_t size,
+                                         DicomTransferSyntax syntax,
+                                         bool allowNewSopInstanceUid)
+    {
+      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
+      if (dicom.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      const uint16_t bitsStored = GetBitsStored(*dicom);
+
+      if (syntax == DetectTransferSyntax(*dicom))
+      {
+        // No transcoding is needed
+        return new Image(dicom.release(), syntax);
+      }
+      
+      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+
+      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+      
+      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+
+      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
+          allowNewSopInstanceUid &&
+          bitsStored == 8)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        
+        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy))
+        {
+          return new Image(dicom.release(), syntax);
+        }
+      }
+#endif
+      
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
+          allowNewSopInstanceUid &&
+          bitsStored <= 12)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy))
+        {
+          return new Image(dicom.release(), syntax);
+        }
+      }
+#endif
+
+      //LOG(INFO) << "Unable to transcode DICOM image using the built-in reader";
+      return NULL;
+    }
+  };
+  
+
+  
+  class IDicomTranscoder1 : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomTranscoder1()
+    {
+    }
+
+    virtual DcmFileFormat& GetDicom() = 0;
+
+    virtual DicomTransferSyntax GetTransferSyntax() = 0;
+
+    virtual std::string GetSopClassUid() = 0;
+
+    virtual std::string GetSopInstanceUid() = 0;
+
+    virtual unsigned int GetFramesCount() = 0;
+
+    virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0;
+
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) = 0;
+
+    // NB: Transcoding can change the value of "GetSopInstanceUid()"
+    // and "GetTransferSyntax()" if lossy compression is applied
+    virtual bool Transcode(std::string& target,
+                           DicomTransferSyntax syntax,
+                           bool allowNewSopInstanceUid) = 0;
+
+    virtual void WriteToMemoryBuffer(std::string& target) = 0;
+  };
+
+
+  class DcmtkTranscoder2 : public IDicomTranscoder1
+  {
+  private:
+    std::unique_ptr<DcmFileFormat>    dicom_;
+    std::unique_ptr<DicomFrameIndex>  index_;
+    DicomTransferSyntax               transferSyntax_;
+    std::string                       sopClassUid_;
+    std::string                       sopInstanceUid_;
+    uint16_t                          bitsStored_;
+    unsigned int                      lossyQuality_;
+
+    static std::string GetStringTag(DcmDataset& dataset,
+                                    const DcmTagKey& tag)
+    {
+      const char* value = NULL;
+
+      if (!dataset.findAndGetString(tag, value).good() ||
+          value == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing SOP class/instance UID in DICOM instance");
+      }
+      else
+      {
+        return std::string(value);
+      }
+    }
+
+    void Setup(DcmFileFormat* dicom)
+    {
+      lossyQuality_ = 90;
+      
+      dicom_.reset(dicom);
+      
+      if (dicom == NULL ||
+          dicom_->getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      DcmDataset& dataset = *dicom_->getDataset();
+      index_.reset(new DicomFrameIndex(dataset));
+
+      E_TransferSyntax xfer = dataset.getCurrentXfer();
+      if (xfer == EXS_Unknown)
+      {
+        dataset.updateOriginalXfer();
+        xfer = dataset.getCurrentXfer();
+        if (xfer == EXS_Unknown)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Cannot determine the transfer syntax of the DICOM instance");
+        }
+      }
+
+      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
+      {
+        throw OrthancException(
+          ErrorCode_BadFileFormat,
+          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
+      }
+
+      if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good())
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing \"Bits Stored\" tag in DICOM instance");
+      }      
+
+      sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
+      sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
+    }
+    
+  public:
+    DcmtkTranscoder2(DcmFileFormat* dicom)  // Takes ownership
+    {
+      Setup(dicom);
+    }
+
+    DcmtkTranscoder2(const void* dicom,
+                    size_t size)
+    {
+      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
+    }
+
+    void SetLossyQuality(unsigned int quality)
+    {
+      if (quality <= 0 ||
+          quality > 100)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        lossyQuality_ = quality;
+      }
+    }
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+
+    unsigned int GetBitsStored() const
+    {
+      return bitsStored_;
+    }
+
+    virtual DcmFileFormat& GetDicom()
+    {
+      assert(dicom_ != NULL);
+      return *dicom_;
+    }
+
+    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
+    {
+      return transferSyntax_;
+    }
+
+    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
+    {
+      return sopClassUid_;
+    }
+    
+    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
+    {
+      return sopInstanceUid_;
+    }
+
+    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
+    {
+      return index_->GetFramesCount();
+    }
+
+    virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
+    {
+      if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_))
+      {
+        throw OrthancException(ErrorCode_InternalError,
+                               "Cannot write the DICOM instance to a memory buffer");
+      }
+    }
+
+    virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE
+    {
+      assert(dicom_->getDataset() != NULL);
+      return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
+    }
+
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) ORTHANC_OVERRIDE
+    {
+      index_->GetRawFrame(target, frame);
+    }
+
+    virtual bool Transcode(std::string& target,
+                           DicomTransferSyntax syntax,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
+    {
+      assert(dicom_ != NULL &&
+             dicom_->getDataset() != NULL);
+      
+      if (syntax == GetTransferSyntax())
+      {
+        printf("NO TRANSCODING\n");
+        
+        // No change in the transfer syntax => simply serialize the current dataset
+        WriteToMemoryBuffer(target);
+        return true;
+      }
+      
+      printf(">> %d\n", bitsStored_);
+
+      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit;
+        return true;
+      }
+
+      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit;
+        return true;
+      }
+      
+      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_BigEndianExplicit;
+        return true;
+      }
+
+      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_DeflatedLittleEndianExplicit;
+        return true;
+      }
+
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
+          allowNewSopInstanceUid &&
+          GetBitsStored() == 8)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        
+        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
+            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+        {
+          transferSyntax_ = DicomTransferSyntax_JPEGProcess1;
+          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
+          return true;
+        }
+      }
+#endif
+      
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
+          allowNewSopInstanceUid &&
+          GetBitsStored() <= 12)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
+            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+        {
+          transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4;
+          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
+          return true;
+        }
+      }
+#endif
+
+      return false;
+    }
+  };
+}
+
+
+
+
+#include <boost/filesystem.hpp>
+
+
+static void TestFile(const std::string& path)
+{
+  static unsigned int count = 0;
+  count++;
+  
+
+  printf("** %s\n", path.c_str());
+
+  std::string s;
+  SystemToolbox::ReadFile(s, path);
+
+  Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size());
+
+  /*if (transcoder.GetBitsStored() != 8)  // TODO
+    return; */
+
+  {
+    char buf[1024];
+    sprintf(buf, "/tmp/source-%06d.dcm", count);
+    printf(">> %s\n", buf);
+    Orthanc::SystemToolbox::WriteFile(s, buf);
+  }
+
+  printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
+         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
+         transcoder.GetFramesCount(), transcoder.GetTransferSyntax());
+
+  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
+  {
+    std::string f;
+    transcoder.GetCompressedFrame(f, i);
+
+    if (i == 0)
+    {
+      char buf[1024];
+      sprintf(buf, "/tmp/frame-%06d.raw", count);
+      printf(">> %s\n", buf);
+      Orthanc::SystemToolbox::WriteFile(f, buf);
+    }
+  }
+
+  {
+    std::string t;
+    transcoder.WriteToMemoryBuffer(t);
+
+    Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
+    printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size());
+  }
+
+  {
+    std::string a = transcoder.GetSopInstanceUid();
+    DicomTransferSyntax b = transcoder.GetTransferSyntax();
+    
+    DicomTransferSyntax syntax = DicomTransferSyntax_JPEGProcess2_4;
+    //DicomTransferSyntax syntax = DicomTransferSyntax_LittleEndianExplicit;
+
+    std::string t;
+    bool ok = transcoder.Transcode(t, syntax, true);
+    printf("Transcoding: %d\n", ok);
+
+    if (ok)
+    {
+      printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str());
+      printf("[%s] => [%s]\n", GetTransferSyntaxUid(b),
+             GetTransferSyntaxUid(transcoder.GetTransferSyntax()));
+      
+      {
+        char buf[1024];
+        sprintf(buf, "/tmp/transcoded-%06d.dcm", count);
+        printf(">> %s\n", buf);
+        Orthanc::SystemToolbox::WriteFile(t, buf);
+      }
+
+      Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
+      printf("  => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size());
+    }
+  }
+  
+  printf("\n");
+}
+
+TEST(Toto, DISABLED_Transcode)
+{
+  //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
+
+  if (1)
+  {
+    const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";
+    
+    for (boost::filesystem::directory_iterator it(PATH);
+         it != boost::filesystem::directory_iterator(); ++it)
+    {
+      if (boost::filesystem::is_regular_file(it->status()))
+      {
+        TestFile(it->path().string());
+      }
+    }
+  }
+
+  if (0)
+  {
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
+  }
+
+  if (0)
+  {
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
+  }
+}
+
+
+TEST(Toto, DISABLED_Transcode2)
+{
+  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
+  {
+    DicomTransferSyntax a = (DicomTransferSyntax) i;
+
+    std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
+                        std::string(GetTransferSyntaxUid(a)) + ".dcm");
+    if (Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      printf("\n======= %s\n", GetTransferSyntaxUid(a));
+
+      std::string source;
+      Orthanc::SystemToolbox::ReadFile(source, path);
+
+      DcmtkImageReader reader;
+
+      {
+        std::unique_ptr<IParsedDicomImage> image(
+          reader.Read(source.c_str(), source.size()));
+        ASSERT_TRUE(image.get() != NULL);
+        ASSERT_EQ(a, image->GetTransferSyntax());
+
+        std::string target;
+        image->WriteToMemoryBuffer(target);
+      }
+
+      for (int j = 0; j <= DicomTransferSyntax_XML; j++)
+      {
+        DicomTransferSyntax b = (DicomTransferSyntax) j;
+        //if (a == b) continue;
+
+        std::unique_ptr<IParsedDicomImage> image(
+          reader.Transcode(source.c_str(), source.size(), b, true));
+        if (image.get() != NULL)
+        {
+          printf("[%s] -> [%s]\n", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));
+
+          std::string target;
+          image->WriteToMemoryBuffer(target);
+
+          char buf[1024];
+          sprintf(buf, "/tmp/%s-%s.dcm", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));
+          
+          SystemToolbox::WriteFile(target, buf);
+        }
+        else if (a != DicomTransferSyntax_JPEG2000 &&
+                 a != DicomTransferSyntax_JPEG2000LosslessOnly)
+        {
+          ASSERT_TRUE(b != DicomTransferSyntax_LittleEndianImplicit &&
+                      b != DicomTransferSyntax_LittleEndianExplicit &&
+                      b != DicomTransferSyntax_BigEndianExplicit &&
+                      b != DicomTransferSyntax_DeflatedLittleEndianExplicit);
+        }
+      }
+    }
+  }
+}
+
+
+#include "../Core/DicomNetworking/DicomAssociation.h"
+#include "../Core/DicomNetworking/DicomControlUserConnection.h"
+#include "../Core/DicomNetworking/DicomStoreUserConnection.h"
+
+TEST(Toto, DISABLED_DicomAssociation)
+{
+  DicomAssociationParameters params;
+  params.SetLocalApplicationEntityTitle("ORTHANC");
+  params.SetRemoteApplicationEntityTitle("PACS");
+  params.SetRemotePort(2001);
+
+#if 0
+  DicomAssociation assoc;
+  assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass);
+  assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass);
+  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
+                                   DicomTransferSyntax_JPEGProcess1);
+  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
+                                   DicomTransferSyntax_JPEGProcess2_4);
+  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
+                                   DicomTransferSyntax_JPEG2000);
+  
+  assoc.Open(params);
+
+  int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage);
+  printf(">> %d\n", presID);
+    
+  std::map<DicomTransferSyntax, uint8_t> pc;
+  printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage));
+  
+  for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
+         it = pc.begin(); it != pc.end(); ++it)
+  {
+    printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second);
+  }
+#else
+  {
+    DicomControlUserConnection assoc(params);
+
+    try
+    {
+      printf(">> %d\n", assoc.Echo());
+    }
+    catch (OrthancException&)
+    {
+    }
+  }
+    
+  params.SetRemoteApplicationEntityTitle("PACS");
+  params.SetRemotePort(2000);
+
+  {
+    DicomControlUserConnection assoc(params);
+    printf(">> %d\n", assoc.Echo());
+  }
+
+#endif
+}
+
+static void TestTranscode(DicomStoreUserConnection& scu,
+                          const std::string& sopClassUid,
+                          DicomTransferSyntax transferSyntax)
+{
+  std::set<DicomTransferSyntax> accepted;
+
+  scu.LookupTranscoding(accepted, sopClassUid, transferSyntax);
+  if (accepted.empty())
+  {
+    throw OrthancException(ErrorCode_NetworkProtocol,
+                           "The SOP class is not supported by the remote modality");
+  }
+
+  {
+    unsigned int count = 0;
+    for (std::set<DicomTransferSyntax>::const_iterator
+           it = accepted.begin(); it != accepted.end(); ++it)
+    {
+      LOG(INFO) << "available for transcoding " << (count++) << ": " << sopClassUid
+                << " / " << GetTransferSyntaxUid(*it);
+    }
+  }
+  
+  if (accepted.find(transferSyntax) != accepted.end())
+  {
+    printf("**** OK, without transcoding !! [%s]\n", GetTransferSyntaxUid(transferSyntax));
+  }
+  else
+  {
+    // Transcoding - only in Orthanc >= 1.7.0
+
+    const DicomTransferSyntax uncompressed[] = {
+      DicomTransferSyntax_LittleEndianImplicit,  // Default transfer syntax
+      DicomTransferSyntax_LittleEndianExplicit,
+      DicomTransferSyntax_BigEndianExplicit
+    };
+
+    bool found = false;
+    for (size_t i = 0; i < 3; i++)
+    {
+      if (accepted.find(uncompressed[i]) != accepted.end())
+      {
+        printf("**** TRANSCODING to %s\n", GetTransferSyntaxUid(uncompressed[i]));
+        found = true;
+        break;
+      }
+    }
+
+    if (!found)
+    {
+      printf("**** KO KO KO\n");
+    }
+  }
+}
+
+
+TEST(Toto, DISABLED_Store)
+{
+  DicomAssociationParameters params;
+  params.SetLocalApplicationEntityTitle("ORTHANC");
+  params.SetRemoteApplicationEntityTitle("STORESCP");
+  params.SetRemotePort(2000);
+
+  DicomStoreUserConnection assoc(params);
+  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1);
+  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4);
+  //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
+
+  //assoc.SetUncompressedSyntaxesProposed(false);  // Necessary for transcoding
+  assoc.SetCommonClassesProposed(false);
+  assoc.SetRetiredBigEndianProposed(true);
+  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
+  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
+  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
+}
+
+
+TEST(Toto, DISABLED_Store2)
+{
+  DicomAssociationParameters params;
+  params.SetLocalApplicationEntityTitle("ORTHANC");
+  params.SetRemoteApplicationEntityTitle("STORESCP");
+  params.SetRemotePort(2000);
+
+  DicomStoreUserConnection assoc(params);
+  //assoc.SetCommonClassesProposed(false);
+  assoc.SetRetiredBigEndianProposed(true);
+
+  std::string s;
+  Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm");
+
+  std::string c, i;
+  assoc.Store(c, i, s.c_str(), s.size());
+  printf("[%s] [%s]\n", c.c_str(), i.c_str());
+}
+
--- a/Resources/Patches/openssl-1.1.1d.patch	Thu May 07 11:31:58 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-diff -urEb openssl-1.1.1d.orig/crypto/rand/rand_unix.c openssl-1.1.1d/crypto/rand/rand_unix.c
---- openssl-1.1.1d.orig/crypto/rand/rand_unix.c	2019-09-10 15:13:07.000000000 +0200
-+++ openssl-1.1.1d/crypto/rand/rand_unix.c	2020-03-05 16:29:33.030136203 +0100
-@@ -340,7 +340,7 @@
- #  endif
- 
-     /* Linux supports this since version 3.17 */
--#  if defined(__linux) && defined(__NR_getrandom)
-+#  if defined(__linux) && defined(__NR_getrandom) && !defined(__LSB_VERSION__)
-     return syscall(__NR_getrandom, buf, buflen, 0);
- #  elif (defined(__FreeBSD__) || defined(__NetBSD__)) && defined(KERN_ARND)
-     return sysctl_random(buf, buflen);
--- a/Resources/Patches/openssl-1.1.1f.patch	Thu May 07 11:31:58 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-diff -urEb openssl-1.1.1f.orig/crypto/rand/rand_unix.c openssl-1.1.1f/crypto/rand/rand_unix.c
---- openssl-1.1.1f.orig/crypto/rand/rand_unix.c	2020-03-31 14:17:45.000000000 +0200
-+++ openssl-1.1.1f/crypto/rand/rand_unix.c	2020-04-02 16:38:56.091240847 +0200
-@@ -445,6 +445,7 @@
-              * system call and this should always succeed which renders
-              * this alternative but essentially identical source moot.
-              */
-+#if !defined(__LSB_VERSION__)  // "syscall()" is not available in LSB
-             if (uname(&un) == 0) {
-                 kernel[0] = atoi(un.release);
-                 p = strchr(un.release, '.');
-@@ -455,6 +456,7 @@
-                     return 0;
-                 }
-             }
-+#endif
-             /* Open /dev/random and wait for it to be readable */
-             if ((fd = open(DEVRANDOM_WAIT, O_RDONLY)) != -1) {
-                 if (DEVRANDM_WAIT_USE_SELECT && fd < FD_SETSIZE) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/openssl-1.1.1g.patch	Thu May 07 11:32:15 2020 +0200
@@ -0,0 +1,19 @@
+diff -urEb openssl-1.1.1g.orig/crypto/rand/rand_unix.c openssl-1.1.1g/crypto/rand/rand_unix.c
+--- openssl-1.1.1g.orig/crypto/rand/rand_unix.c	2020-05-05 17:58:08.785998440 +0200
++++ openssl-1.1.1g/crypto/rand/rand_unix.c	2020-05-05 17:58:55.201881117 +0200
+@@ -445,6 +445,7 @@
+              * system call and this should always succeed which renders
+              * this alternative but essentially identical source moot.
+              */
++#if !defined(__LSB_VERSION__)  // "syscall()" is not available in LSB
+             if (uname(&un) == 0) {
+                 kernel[0] = atoi(un.release);
+                 p = strchr(un.release, '.');
+@@ -455,6 +456,7 @@
+                     return 0;
+                 }
+             }
++#endif
+             /* Open /dev/random and wait for it to be readable */
+             if ((fd = open(DEVRANDOM_WAIT, O_RDONLY)) != -1) {
+                 if (DEVRANDM_WAIT_USE_SELECT && fd < FD_SETSIZE) {
--- a/UnitTestsSources/FromDcmtkTests.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu May 07 11:32:15 2020 +0200
@@ -1924,630 +1924,98 @@
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
 
-#include "../Core/DicomParsing/Internals/DicomFrameIndex.h"
-
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <dcmtk/dcmdata/dcpixel.h>
-#include <dcmtk/dcmdata/dcpxitem.h>
-#include <dcmtk/dcmjpeg/djrploss.h>   // for DJ_RPLossy
-#include <dcmtk/dcmjpeg/djrplol.h>    // for DJ_RPLossless
-
+#include "../Core/DicomNetworking/DicomStoreUserConnection.h"
+#include "../Core/DicomParsing/DcmtkTranscoder.h"
 
-namespace Orthanc
+TEST(Toto, DISABLED_Transcode3)
 {
-  class IDicomTranscoder : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomTranscoder()
-    {
-    }
-
-    virtual DcmFileFormat& GetDicom() = 0;
+  DicomAssociationParameters p;
+  p.SetRemotePort(2000);
 
-    virtual DicomTransferSyntax GetTransferSyntax() = 0;
-
-    virtual std::string GetSopClassUid() = 0;
-
-    virtual std::string GetSopInstanceUid() = 0;
-
-    virtual unsigned int GetFramesCount() = 0;
-
-    virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0;
+  DicomStoreUserConnection scu(p);
+  scu.SetCommonClassesProposed(false);
+  scu.SetRetiredBigEndianProposed(true);
 
-    virtual void GetCompressedFrame(std::string& target,
-                                    unsigned int frame) = 0;
+  DcmtkTranscoder transcoder;
 
-    // NB: Transcoding can change the value of "GetSopInstanceUid()"
-    // and "GetTransferSyntax()" if lossy compression is applied
-    virtual bool Transcode(std::string& target,
-                           std::set<DicomTransferSyntax> syntaxes,
-                           bool allowNewSopInstanceUid) = 0;
-
-    virtual void WriteToMemoryBuffer(std::string& target) = 0;
-  };
-
+  for (int j = 0; j < 2; j++)
+    for (int i = 0; i <= DicomTransferSyntax_XML; i++)
+    {
+      DicomTransferSyntax a = (DicomTransferSyntax) i;
 
-  class DcmtkTranscoder : public IDicomTranscoder
-  {
-  private:
-    std::unique_ptr<DcmFileFormat>    dicom_;
-    std::unique_ptr<DicomFrameIndex>  index_;
-    DicomTransferSyntax               transferSyntax_;
-    std::string                       sopClassUid_;
-    std::string                       sopInstanceUid_;
-    uint16_t                          bitsStored_;
-    unsigned int                      lossyQuality_;
-
-    static std::string GetStringTag(DcmDataset& dataset,
-                                    const DcmTagKey& tag)
-    {
-      const char* value = NULL;
-
-      if (!dataset.findAndGetString(tag, value).good() ||
-          value == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing SOP class/instance UID in DICOM instance");
-      }
-      else
+      std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
+                          std::string(GetTransferSyntaxUid(a)) + ".dcm");
+      if (Orthanc::SystemToolbox::IsRegularFile(path))
       {
-        return std::string(value);
-      }
-    }
+        printf("\n======= %s\n", GetTransferSyntaxUid(a));
+
+        std::string source;
+        Orthanc::SystemToolbox::ReadFile(source, path);
 
-    void Setup(DcmFileFormat* dicom)
-    {
-      lossyQuality_ = 90;
-      
-      dicom_.reset(dicom);
-      
-      if (dicom == NULL ||
-          dicom_->getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_NullPointer);
-      }
-
-      DcmDataset& dataset = *dicom_->getDataset();
-      index_.reset(new DicomFrameIndex(dataset));
-
-      E_TransferSyntax xfer = dataset.getOriginalXfer();
-      if (xfer == EXS_Unknown)
-      {
-        dataset.updateOriginalXfer();
-        xfer = dataset.getOriginalXfer();
-        if (xfer == EXS_Unknown)
+        std::string c, i;
+        try
+        {
+          scu.Transcode(c, i, transcoder, source.c_str(), source.size(), false, "", 0);
+        }
+        catch (OrthancException& e)
         {
-          throw OrthancException(ErrorCode_BadFileFormat,
-                                 "Cannot determine the transfer syntax of the DICOM instance");
+          if (e.GetErrorCode() == ErrorCode_NotImplemented)
+          {
+            LOG(ERROR) << "cannot transcode " << GetTransferSyntaxUid(a);
+          }
+          else
+          {
+            throw e;
+          }
         }
       }
-
-      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
-      {
-        throw OrthancException(
-          ErrorCode_BadFileFormat,
-          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
-      }
-
-      if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good())
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing \"Bits Stored\" tag in DICOM instance");
-      }      
-
-      sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
-      sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
     }
-    
-  public:
-    DcmtkTranscoder(DcmFileFormat* dicom)  // Takes ownership
-    {
-      Setup(dicom);
-    }
-
-    DcmtkTranscoder(const void* dicom,
-                    size_t size)
-    {
-      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
-    }
-
-    void SetLossyQuality(unsigned int quality)
-    {
-      if (quality <= 0 ||
-          quality > 100)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        lossyQuality_ = quality;
-      }
-    }
-
-    unsigned int GetLossyQuality() const
-    {
-      return lossyQuality_;
-    }
-
-    unsigned int GetBitsStored() const
-    {
-      return bitsStored_;
-    }
-
-    virtual DcmFileFormat& GetDicom()
-    {
-      assert(dicom_ != NULL);
-      return *dicom_;
-    }
-
-    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
-    {
-      return transferSyntax_;
-    }
-
-    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
-    {
-      return sopClassUid_;
-    }
-    
-    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
-    {
-      return sopInstanceUid_;
-    }
-
-    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
-    {
-      return index_->GetFramesCount();
-    }
-
-    virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
-    {
-      if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_))
-      {
-        throw OrthancException(ErrorCode_InternalError,
-                               "Cannot write the DICOM instance to a memory buffer");
-      }
-    }
-
-    virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE
-    {
-      assert(dicom_->getDataset() != NULL);
-      return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
-    }
-
-    virtual void GetCompressedFrame(std::string& target,
-                                    unsigned int frame) ORTHANC_OVERRIDE
-    {
-      index_->GetRawFrame(target, frame);
-    }
-
-    virtual bool Transcode(std::string& target,
-                           std::set<DicomTransferSyntax> syntaxes,
-                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      assert(dicom_ != NULL &&
-             dicom_->getDataset() != NULL);
-      
-      if (syntaxes.find(GetTransferSyntax()) != syntaxes.end())
-      {
-        printf("NO TRANSCODING\n");
-        
-        // No change in the transfer syntax => simply serialize the current dataset
-        WriteToMemoryBuffer(target);
-        return true;
-      }
-      
-      printf(">> %d\n", bitsStored_);
-
-      DJ_RPLossy rpLossy(lossyQuality_);
-
-      if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != syntaxes.end() &&
-          FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianImplicit, NULL))
-      {
-        transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit;
-        return true;
-      }
-      else if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != syntaxes.end() &&
-               FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianExplicit, NULL))
-      {
-        transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit;
-        return true;
-      }
-      else if (syntaxes.find(DicomTransferSyntax_BigEndianExplicit) != syntaxes.end() &&
-               FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_BigEndianExplicit, NULL))
-      {
-        transferSyntax_ = DicomTransferSyntax_BigEndianExplicit;
-        return true;
-      }
-      else if (syntaxes.find(DicomTransferSyntax_JPEGProcess1) != syntaxes.end() &&
-               allowNewSopInstanceUid &&
-               GetBitsStored() == 8 &&
-               FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess1, &rpLossy))
-      {
-        transferSyntax_ = DicomTransferSyntax_JPEGProcess1;
-        sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
-        return true;
-      }
-      else if (syntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != syntaxes.end() &&
-               allowNewSopInstanceUid &&
-               GetBitsStored() <= 12 &&
-               FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess2_4, &rpLossy))
-      {
-        transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4;
-        sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  };
 }
 
 
-
-
-static bool Transcode(std::string& buffer,
-                      DcmDataset& dataSet,
-                      E_TransferSyntax xfer)
+TEST(Toto, DISABLED_Transcode4)
 {
-  // Determine the transfer syntax which shall be used to write the
-  // information to the file. We always switch to the Little Endian
-  // syntax, with explicit length.
-
-  // http://support.dcmtk.org/docs/dcxfer_8h-source.html
-
-
-  /**
-   * Note that up to Orthanc 0.7.1 (inclusive), the
-   * "EXS_LittleEndianExplicit" was always used to save the DICOM
-   * dataset into memory. We now keep the original transfer syntax
-   * (if available).
-   **/
-  //E_TransferSyntax xfer = dataSet.getOriginalXfer();
-  if (xfer == EXS_Unknown)
-  {
-    // No information about the original transfer syntax: This is
-    // most probably a DICOM dataset that was read from memory.
-    xfer = EXS_LittleEndianExplicit;
-  }
-
-  E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
+  std::string source;
+  Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
 
-  // Create the meta-header information
-  DcmFileFormat ff(&dataSet);
-  ff.validateMetaInfo(xfer);
-  ff.removeInvalidGroups();
-
-  // Create a memory buffer with the proper size
-  {
-    const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType);  // (*)
-    buffer.resize(estimatedSize);
-  }
-
-  DcmOutputBufferStream ob(&buffer[0], buffer.size());
+  std::unique_ptr<DcmFileFormat> toto(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size()));
+  DicomTransferSyntax sourceSyntax;
+  ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
 
-  // Fill the memory buffer with the meta-header and the dataset
-  ff.transferInit();
-  OFCondition c = ff.write(ob, xfer, encodingType, NULL,
-                           /*opt_groupLength*/ EGL_recalcGL,
-                           /*opt_paddingType*/ EPD_withoutPadding);
-  ff.transferEnd();
+  DcmtkTranscoder transcoder;
 
-  if (c.good())
-  {
-    // The DICOM file is successfully written, truncate the target
-    // buffer if its size was overestimated by (*)
-    ob.flush();
-
-    size_t effectiveSize = static_cast<size_t>(ob.tell());
-    if (effectiveSize < buffer.size())
-    {
-      buffer.resize(effectiveSize);
-    }
-
-    return true;
-  }
-  else
+  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
   {
-    // Error
-    buffer.clear();
-    return false;
-  }
-}
-
-
-#include <boost/filesystem.hpp>
-
-
-static void TestFile(const std::string& path)
-{
-  static unsigned int count = 0;
-  count++;
-  
-
-  printf("** %s\n", path.c_str());
-
-  std::string s;
-  SystemToolbox::ReadFile(s, path);
-
-  Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size());
-
-  /*if (transcoder.GetBitsStored() != 8)  // TODO
-    return; */
-
-  {
-    char buf[1024];
-    sprintf(buf, "/tmp/source-%06d.dcm", count);
-    printf(">> %s\n", buf);
-    Orthanc::SystemToolbox::WriteFile(s, buf);
-  }
-
-  printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
-         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
-         transcoder.GetFramesCount(), transcoder.GetTransferSyntax());
-
-  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
-  {
-    std::string f;
-    transcoder.GetCompressedFrame(f, i);
-
-    if (i == 0)
-    {
-      char buf[1024];
-      sprintf(buf, "/tmp/frame-%06d.raw", count);
-      printf(">> %s\n", buf);
-      Orthanc::SystemToolbox::WriteFile(f, buf);
-    }
-  }
-
-  {
-    std::string t;
-    transcoder.WriteToMemoryBuffer(t);
-
-    Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size());
-    printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size());
-  }
-
-  {
-    std::string a = transcoder.GetSopInstanceUid();
-    DicomTransferSyntax b = transcoder.GetTransferSyntax();
-    
-    std::set<DicomTransferSyntax> syntaxes;
-    syntaxes.insert(DicomTransferSyntax_JPEGProcess2_4);
-    //syntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
+    DicomTransferSyntax a = (DicomTransferSyntax) i;
+    std::set<DicomTransferSyntax> s;
+    s.insert(a);
 
     std::string t;
-    bool ok = transcoder.Transcode(t, syntaxes, true);
-    printf("Transcoding: %d\n", ok);
 
-    if (ok)
+    bool hasSopInstanceUidChanged;
+                                   
+    if (!transcoder.TranscodeToBuffer(t, hasSopInstanceUidChanged, source.c_str(), source.size(), s, true))
     {
-      printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str());
-      printf("[%s] => [%s]\n", GetTransferSyntaxUid(b),
-             GetTransferSyntaxUid(transcoder.GetTransferSyntax()));
-      
-      {
-        char buf[1024];
-        sprintf(buf, "/tmp/transcoded-%06d.dcm", count);
-        printf(">> %s\n", buf);
-        Orthanc::SystemToolbox::WriteFile(t, buf);
-      }
-
-      Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size());
-      printf("  => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size());
-    }
-  }
-  
-  printf("\n");
-}
-
-TEST(Toto, DISABLED_Transcode)
-{
-  //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
-
-  if (0)
-  {
-    std::string s;
-    //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm");
-    //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm");
-    //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm");
-    SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
-
-    std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size()));
-
-    // less /home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcxfer.h
-    printf(">> %d\n", dicom->getDataset()->getOriginalXfer());  // => 4 == EXS_JPEGProcess1
-
-    const DcmRepresentationParameter *p;
-
-#if 0
-    E_TransferSyntax target = EXS_LittleEndianExplicit;
-    p = NULL;
-#elif 0
-    E_TransferSyntax target = EXS_JPEGProcess14SV1;  
-    DJ_RPLossless rp_lossless(6, 0);
-    p = &rp_lossless;
-#else
-    E_TransferSyntax target = EXS_JPEGProcess1;
-    DJ_RPLossy rp_lossy(90);  // quality
-    p = &rp_lossy;
-#endif 
-  
-    ASSERT_TRUE(dicom->getDataset()->chooseRepresentation(target, p).good());
-    ASSERT_TRUE(dicom->getDataset()->canWriteXfer(target));
-
-    std::string t;
-    ASSERT_TRUE(Transcode(t, *dicom->getDataset(), target));
-
-    SystemToolbox::WriteFile(s, "source.dcm");
-    SystemToolbox::WriteFile(t, "target.dcm");
-  }
-
-  if (1)
-  {
-    const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";
-    
-    for (boost::filesystem::directory_iterator it(PATH);
-         it != boost::filesystem::directory_iterator(); ++it)
-    {
-      if (boost::filesystem::is_regular_file(it->status()))
-      {
-        TestFile(it->path().string());
-      }
+      printf("**************** CANNOT: [%s] => [%s]\n",
+             GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
     }
-  }
-
-  if (0)
-  {
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
-  }
-
-  if (0)
-  {
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
-  }
-}
-
-
-
-#include "../Core/DicomNetworking/DicomAssociation.h"
-#include "../Core/DicomNetworking/DicomControlUserConnection.h"
-#include "../Core/DicomNetworking/DicomStoreUserConnection.h"
-
-TEST(Toto, DISABLED_DicomAssociation)
-{
-  DicomAssociationParameters params;
-  params.SetLocalApplicationEntityTitle("ORTHANC");
-  params.SetRemoteApplicationEntityTitle("PACS");
-  params.SetRemotePort(2001);
-
-#if 0
-  DicomAssociation assoc;
-  assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass);
-  assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass);
-  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
-                                   DicomTransferSyntax_JPEGProcess1);
-  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
-                                   DicomTransferSyntax_JPEGProcess2_4);
-  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
-                                   DicomTransferSyntax_JPEG2000);
-  
-  assoc.Open(params);
-
-  int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage);
-  printf(">> %d\n", presID);
-    
-  std::map<DicomTransferSyntax, uint8_t> pc;
-  printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage));
-  
-  for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
-         it = pc.begin(); it != pc.end(); ++it)
-  {
-    printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second);
-  }
-#else
-  {
-    DicomControlUserConnection assoc(params);
-
-    try
+    else
     {
-      printf(">> %d\n", assoc.Echo());
-    }
-    catch (OrthancException&)
-    {
-    }
-  }
-    
-  params.SetRemoteApplicationEntityTitle("PACS");
-  params.SetRemotePort(2000);
-
-  {
-    DicomControlUserConnection assoc(params);
-    printf(">> %d\n", assoc.Echo());
-  }
-
-#endif
-}
-
-static void TestTranscode(DicomStoreUserConnection& scu,
-                          const std::string& sopClassUid,
-                          DicomTransferSyntax transferSyntax)
-{
-  uint8_t id;
+      bool lossy = (a == DicomTransferSyntax_JPEGProcess1 ||
+                    a == DicomTransferSyntax_JPEGProcess2_4 ||
+                    a == DicomTransferSyntax_JPEGLSLossy);
       
-  if (scu.NegotiatePresentationContext(id, sopClassUid, transferSyntax))
-  {
-    printf("**** OK, without transcoding !! %d\n", id);
-  }
-  else
-  {
-    // Transcoding - only in Orthanc >= 1.7.0
-
-    const DicomTransferSyntax uncompressed[] = {
-      DicomTransferSyntax_LittleEndianImplicit,  // Default transfer syntax
-      DicomTransferSyntax_LittleEndianExplicit,
-      DicomTransferSyntax_BigEndianExplicit
-    };
-
-    bool found = false;
-    for (size_t i = 0; i < 3; i++)
-    {
-      if (scu.LookupPresentationContext(id, sopClassUid, uncompressed[i]))
+      printf("SIZE: %lu\n", t.size());
+      if (hasSopInstanceUidChanged)
       {
-        printf("**** TRANSCODING to %s => %d\n",
-               GetTransferSyntaxUid(uncompressed[i]), id);
-        found = true;
-        break;
+        ASSERT_TRUE(lossy);
       }
-    }
-
-    if (!found)
-    {
-      printf("**** KO KO KO\n");
+      else
+      {
+        ASSERT_FALSE(lossy);
+      }
     }
   }
 }
 
-
-TEST(Toto, DISABLED_Store)
-{
-  DicomAssociationParameters params;
-  params.SetLocalApplicationEntityTitle("ORTHANC");
-  params.SetRemoteApplicationEntityTitle("STORESCP");
-  params.SetRemotePort(2000);
-
-  DicomStoreUserConnection assoc(params);
-  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1);
-  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4);
-  //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
-
-  //assoc.SetUncompressedSyntaxesProposed(false);  // Necessary for transcoding
-  //assoc.SetCommonClassesProposed(false);
-  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
-  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
-}
-
-
-TEST(Toto, DISABLED_Store2)
-{
-  DicomAssociationParameters params;
-  params.SetLocalApplicationEntityTitle("ORTHANC");
-  params.SetRemoteApplicationEntityTitle("STORESCP");
-  params.SetRemotePort(2000);
-
-  DicomStoreUserConnection assoc(params);
-  //assoc.SetCommonClassesProposed(false);
-  assoc.SetRetiredBigEndianProposed(true);
-
-  std::string s;
-  Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm");
-
-  std::string c, i;
-  assoc.Store(c, i, s.c_str(), s.size());
-  printf("[%s] [%s]\n", c.c_str(), i.c_str());
-}
-
 #endif
--- a/UnitTestsSources/MultiThreadingTests.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Thu May 07 11:32:15 2020 +0200
@@ -62,6 +62,7 @@
 
 #include "../OrthancServer/ServerJobs/ArchiveJob.h"
 #include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h"
+#include "../OrthancServer/ServerJobs/DicomMoveScuJob.h"
 #include "../OrthancServer/ServerJobs/MergeStudyJob.h"
 #include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h"
 #include "../OrthancServer/ServerJobs/ResourceModificationJob.h"
@@ -1414,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);
@@ -1514,11 +1515,11 @@
     job.reset(unserializer.UnserializeJob(s));
 
     DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job);
-    ASSERT_EQ("LOCAL", tmp.GetLocalAet());
-    ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle());
-    ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost());
-    ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber());
-    ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer());
+    ASSERT_EQ("LOCAL", tmp.GetParameters().GetLocalApplicationEntityTitle());
+    ASSERT_EQ("REMOTE", tmp.GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ("192.168.1.1", tmp.GetParameters().GetRemoteModality().GetHost());
+    ASSERT_EQ(1000, tmp.GetParameters().GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetParameters().GetRemoteModality().GetManufacturer());
     ASSERT_TRUE(tmp.HasMoveOriginator());
     ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet());
     ASSERT_EQ(42, tmp.GetMoveOriginatorId());
@@ -1902,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;
@@ -1932,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";
@@ -1998,6 +2001,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 
   {
@@ -2007,6 +2011,7 @@
     s["AET"] = "AET";
     s["Host"] = "host";
     s["Port"] = "104";
+    s["AllowTranscoding"] = false;
     
     RemoteModalityParameters modality(s);
     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
@@ -2015,6 +2020,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_FALSE(modality.IsTranscodingAllowed());
   }
 
   {
@@ -2032,5 +2038,143 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 }
+
+
+TEST_F(OrthancJobsSerialization, DicomAssociationParameters)
+{
+  Json::Value v;
+
+  {
+    v = Json::objectValue;
+    DicomAssociationParameters p;
+    p.SerializeJob(v);
+  }
+
+  {
+    DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v);
+    ASSERT_EQ("ORTHANC", p.GetLocalApplicationEntityTitle());
+    ASSERT_EQ("ANY-SCP", p.GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ(104u, p.GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer());
+    ASSERT_EQ("127.0.0.1", p.GetRemoteModality().GetHost());
+    ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), p.GetTimeout());
+  }
+
+  {
+    v = Json::objectValue;
+    DicomAssociationParameters p;
+    p.SetLocalApplicationEntityTitle("HELLO");
+    p.SetRemoteApplicationEntityTitle("WORLD");
+    p.SetRemotePort(42);
+    p.SetRemoteHost("MY_HOST");
+    p.SetTimeout(43);
+    p.SerializeJob(v);
+  }
+
+  {
+    DicomAssociationParameters p = DicomAssociationParameters::UnserializeJob(v);
+    ASSERT_EQ("HELLO", p.GetLocalApplicationEntityTitle());
+    ASSERT_EQ("WORLD", p.GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ(42u, p.GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_Generic, p.GetRemoteModality().GetManufacturer());
+    ASSERT_EQ("MY_HOST", p.GetRemoteModality().GetHost());
+    ASSERT_EQ(43u, p.GetTimeout());
+  }
+  
+  {
+    DicomModalityStoreJob job(GetContext());
+    job.Serialize(v);
+  }
+  
+  {
+    OrthancJobUnserializer unserializer(GetContext());
+    std::unique_ptr<DicomModalityStoreJob> job(
+      dynamic_cast<DicomModalityStoreJob*>(unserializer.UnserializeJob(v)));
+    ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle());
+    ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost());
+    ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
+    ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout());
+    ASSERT_FALSE(job->HasMoveOriginator());
+    ASSERT_THROW(job->GetMoveOriginatorAet(), OrthancException);
+    ASSERT_THROW(job->GetMoveOriginatorId(), OrthancException);
+    ASSERT_FALSE(job->HasStorageCommitment());
+  }
+  
+  {
+    RemoteModalityParameters r;
+    r.SetApplicationEntityTitle("HELLO");
+    r.SetPortNumber(42);
+    r.SetHost("MY_HOST");
+
+    DicomModalityStoreJob job(GetContext());
+    job.SetLocalAet("WORLD");
+    job.SetRemoteModality(r);
+    job.SetTimeout(43);
+    job.SetMoveOriginator("ORIGINATOR", 100);
+    job.EnableStorageCommitment(true);
+    job.Serialize(v);
+  }
+  
+  {
+    OrthancJobUnserializer unserializer(GetContext());
+    std::unique_ptr<DicomModalityStoreJob> job(
+      dynamic_cast<DicomModalityStoreJob*>(unserializer.UnserializeJob(v)));
+    ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle());
+    ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost());
+    ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
+    ASSERT_EQ(43u, job->GetParameters().GetTimeout());
+    ASSERT_TRUE(job->HasMoveOriginator());
+    ASSERT_EQ("ORIGINATOR", job->GetMoveOriginatorAet());
+    ASSERT_EQ(100, job->GetMoveOriginatorId());
+    ASSERT_TRUE(job->HasStorageCommitment());
+  }
+    
+  {
+    DicomMoveScuJob job(GetContext());
+    job.Serialize(v);
+  }
+  
+  {
+    OrthancJobUnserializer unserializer(GetContext());
+    std::unique_ptr<DicomMoveScuJob> job(
+      dynamic_cast<DicomMoveScuJob*>(unserializer.UnserializeJob(v)));
+    ASSERT_EQ("ORTHANC", job->GetParameters().GetLocalApplicationEntityTitle());
+    ASSERT_EQ("ANY-SCP", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ("127.0.0.1", job->GetParameters().GetRemoteModality().GetHost());
+    ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
+    ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout());
+  }
+  
+  {
+    RemoteModalityParameters r;
+    r.SetApplicationEntityTitle("HELLO");
+    r.SetPortNumber(42);
+    r.SetHost("MY_HOST");
+
+    DicomMoveScuJob job(GetContext());
+    job.SetLocalAet("WORLD");
+    job.SetRemoteModality(r);
+    job.SetTimeout(43);
+    job.Serialize(v);
+  }
+  
+  {
+    OrthancJobUnserializer unserializer(GetContext());
+    std::unique_ptr<DicomMoveScuJob> job(
+      dynamic_cast<DicomMoveScuJob*>(unserializer.UnserializeJob(v)));
+    ASSERT_EQ("WORLD", job->GetParameters().GetLocalApplicationEntityTitle());
+    ASSERT_EQ("HELLO", job->GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+    ASSERT_EQ("MY_HOST", job->GetParameters().GetRemoteModality().GetHost());
+    ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber());
+    ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer());
+    ASSERT_EQ(43u, job->GetParameters().GetTimeout());
+  }
+}
--- a/UnitTestsSources/VersionsTests.cpp	Thu May 07 11:31:58 2020 +0200
+++ b/UnitTestsSources/VersionsTests.cpp	Thu May 07 11:32:15 2020 +0200
@@ -185,7 +185,7 @@
 TEST(Version, OpenSslStatic)
 {
   ASSERT_TRUE(OPENSSL_VERSION_NUMBER == 0x1000210fL /* openssl-1.0.2p */ ||
-              OPENSSL_VERSION_NUMBER == 0x1010106fL /* openssl-1.1.1f */);
+              OPENSSL_VERSION_NUMBER == 0x1010107fL /* openssl-1.1.1g */);
 }
 #endif