changeset 3900:32e95d28efb2 transcoding

integration mainline->default
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 07 May 2020 12:37:36 +0200
parents 104e27133ebd (diff) fe0e4ef52a72 (current diff)
children 603a7b86fa5f
files NEWS Plugins/Engine/OrthancPlugins.cpp
diffstat 86 files changed, 3072 insertions(+), 4967 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed May 06 08:40:48 2020 +0200
+++ b/.hgignore	Thu May 07 12:37:36 2020 +0200
@@ -6,3 +6,10 @@
 .vs/
 .vscode/
 *~
+
+# when opening Orthanc in VSCode, it might find a java project and create files we wan't to ignore:
+.settings/
+.classpath
+.project
+Resources/Testing/Issue32/Java/bin
+Resources/Testing/Issue32/Java/target
--- a/CMakeLists.txt	Wed May 06 08:40:48 2020 +0200
+++ b/CMakeLists.txt	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociation.cpp	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociationParameters.cpp	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociationParameters.h	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomControlUserConnection.cpp	Thu May 07 12:37:36 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 = "*";
     }
--- a/Core/DicomNetworking/DicomControlUserConnection.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomControlUserConnection.h	Thu May 07 12:37:36 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
--- a/Core/DicomNetworking/DicomStoreUserConnection.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp	Thu May 07 12:37:36 2020 +0200
@@ -48,8 +48,28 @@
   bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid,
                                                      const std::set<DicomTransferSyntax>& syntaxes)
   {
+    // Default transfer syntax for DICOM
+    const bool addLittleEndianImplicit = (
+      proposeUncompressedSyntaxes_ &&
+      syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end());
+    
+    const bool addLittleEndianExplicit = (
+      proposeUncompressedSyntaxes_ &&
+      syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end());
+    
+    const bool addBigEndianExplicit = (
+      proposeUncompressedSyntaxes_ &&
+      proposeRetiredBigEndian_ &&
+      syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end());
+    
     size_t requiredCount = syntaxes.size();
-    if (proposeUncompressedSyntaxes_)
+    if (addLittleEndianImplicit)
+    {
+      requiredCount += 1;
+    }
+      
+    if (addLittleEndianExplicit ||
+        addBigEndianExplicit)
     {
       requiredCount += 1;
     }
@@ -58,40 +78,49 @@
     {
       return false;  // Not enough room
     }
-      
-    for (std::set<DicomTransferSyntax>::const_iterator
-           it = syntaxes.begin(); it != syntaxes.end(); ++it)
+    else
     {
-      association_->ProposePresentationContext(sopClassUid, *it);
-    }
-
-    if (proposeUncompressedSyntaxes_)
-    {
-      std::set<DicomTransferSyntax> uncompressed;
-        
-      if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end())
+      for (std::set<DicomTransferSyntax>::const_iterator
+             it = syntaxes.begin(); it != syntaxes.end(); ++it)
       {
-        uncompressed.insert(DicomTransferSyntax_LittleEndianImplicit);
+        association_->ProposePresentationContext(sopClassUid, *it);
+        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it));
       }
-        
-      if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end())
+
+      if (addLittleEndianImplicit)
       {
-        uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit);
-      }
-        
-      if (proposeRetiredBigEndian_ &&
-          syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end())
-      {
-        uncompressed.insert(DicomTransferSyntax_BigEndianExplicit);
+        association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit);
+        proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit));
       }
 
-      if (!uncompressed.empty())
+      if (addLittleEndianExplicit ||
+          addBigEndianExplicit)
       {
+        std::set<DicomTransferSyntax> uncompressed;
+
+        if (addLittleEndianExplicit)
+        {
+          uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit);
+        }
+
+        if (addBigEndianExplicit)
+        {
+          uncompressed.insert(DicomTransferSyntax_BigEndianExplicit);
+        }
+
         association_->ProposePresentationContext(sopClassUid, uncompressed);
+
+        assert(!uncompressed.empty());
+        if (addLittleEndianExplicit ^ addBigEndianExplicit)
+        {
+          // Only one transfer syntax was proposed for this presentation context
+          assert(uncompressed.size() == 1);
+          proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *uncompressed.begin()));
+        }
       }
-    }      
 
-    return true;
+      return true;
+    }
   }
 
 
@@ -116,8 +145,8 @@
 
     return false;
   }
-    
-        
+
+
   DicomStoreUserConnection::DicomStoreUserConnection(
     const DicomAssociationParameters& params) :
     parameters_(params),
@@ -129,16 +158,16 @@
   }
     
 
-  void DicomStoreUserConnection::PrepareStorageClass(const std::string& sopClassUid,
-                                                     DicomTransferSyntax syntax)
+  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
     {
@@ -147,6 +176,36 @@
   }
 
 
+  void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid,
+                                                  std::string& sopInstanceUid,
+                                                  DicomTransferSyntax& transferSyntax,
+                                                  DcmFileFormat& dicom)
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    OFString a, b;
+    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_.GetRemoteModality().GetApplicationEntityTitle());
+    }
+
+    sopClassUid.assign(a.c_str());
+    sopInstanceUid.assign(b.c_str());
+
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom))
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Unknown transfer syntax from DCMTK");
+    }
+  }
+  
+
   bool DicomStoreUserConnection::NegotiatePresentationContext(
     uint8_t& presentationContextId,
     const std::string& sopClassUid,
@@ -154,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))
@@ -163,22 +222,37 @@
     }
 
     // The association must be re-negotiated
-    LOG(INFO) << "Re-negociating DICOM association with "
-              << parameters_.GetRemoteApplicationEntityTitle();
+    if (association_->IsOpen())
+    {
+      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();
-    PrepareStorageClass(sopClassUid, transferSyntax);
+    proposedOriginalClasses_.clear();
+    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);
       }
 
@@ -194,11 +268,11 @@
       
     /**
      * Step 3: Propose all the previously spotted SOP classes, as
-     * registered through the "PrepareStorageClass()" method.
+     * 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)
       {
@@ -217,15 +291,16 @@
 
     if (proposeCommonClasses_)
     {
+      // The method "ProposeStorageClass()" will automatically add
+      // "LittleEndianImplicit"
       std::set<DicomTransferSyntax> ts;
-      ts.insert(DicomTransferSyntax_LittleEndianImplicit);
         
       for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
       {
         std::string c(dcmShortSCUStorageSOPClassUIDs[i]);
           
         if (c != sopClassUid &&
-            storageClasses_.find(c) == storageClasses_.end())
+            registeredClasses_.find(c) == registeredClasses_.end())
         {
           ProposeStorageClass(c, ts);
         }
@@ -245,36 +320,23 @@
 
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
-                                       DcmDataset& dataset,
+                                       DcmFileFormat& dicom,
+                                       bool hasMoveOriginator,
                                        const std::string& moveOriginatorAET,
                                        uint16_t moveOriginatorID)
   {
-    OFString a, b;
-    if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() ||
-        !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good())
-    {
-      throw OrthancException(ErrorCode_NoSopClassOrInstance,
-                             "Unable to determine the SOP class/instance for C-STORE with AET " +
-                             parameters_.GetRemoteApplicationEntityTitle());
-    }
-
-    sopClassUid.assign(a.c_str());
-    sopInstanceUid.assign(b.c_str());
+    DicomTransferSyntax transferSyntax;
+    LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom);
 
-    DicomTransferSyntax transferSyntax;
-    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(
-          transferSyntax, dataset.getOriginalXfer()))
+    uint8_t presID;
+    if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax))
     {
-      throw OrthancException(ErrorCode_InternalError,
-                             "Unknown transfer syntax from DCMTK");
-    }
-
-    // Figure out which accepted presentation context should be used
-    uint8_t presID;
-    if (!NegotiatePresentationContext(presID, sopClassUid.c_str(), transferSyntax))
-    {
-      throw OrthancException(ErrorCode_InternalError,
-                             "No valid presentation context was negotiated upfront");
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "No valid presentation context was negotiated for "
+                             "SOP class UID [" + sopClassUid + "] and transfer "
+                             "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] "
+                             "while sending to modality [" +
+                             parameters_.GetRemoteModality().GetApplicationEntityTitle() + "]");
     }
     
     // Prepare the transmission of data
@@ -286,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;
@@ -296,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),
@@ -326,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);
     }
   }
@@ -334,26 +401,148 @@
 
   void DicomStoreUserConnection::Store(std::string& sopClassUid,
                                        std::string& sopInstanceUid,
-                                       ParsedDicomFile& parsed,
-                                       const std::string& moveOriginatorAET,
-                                       uint16_t moveOriginatorID)
-  {
-    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));
 
-    Store(sopClassUid, sopInstanceUid, *dicom->getDataset(),
-          moveOriginatorAET, moveOriginatorID);
+    if (dicom.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.h	Thu May 07 12:37:36 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,16 +72,20 @@
   **/
 
   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_;
-    StorageClasses                       storageClasses_;
+    boost::shared_ptr<DicomAssociation>  association_;  // "shared_ptr" is for PImpl
+    RegisteredClasses                    registeredClasses_;
+    ProposedOriginalClasses              proposedOriginalClasses_;
     bool                                 proposeCommonClasses_;
     bool                                 proposeUncompressedSyntaxes_;
     bool                                 proposeRetiredBigEndian_;
@@ -81,11 +93,18 @@
     // Return "false" if there is not enough room remaining in the association
     bool ProposeStorageClass(const std::string& sopClassUid,
                              const std::set<DicomTransferSyntax>& syntaxes);
-        
-    // Should only be used if transcoding
+
     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 DicomAssociationParameters& params);
@@ -125,23 +144,13 @@
       return proposeRetiredBigEndian_;
     }      
 
-    void PrepareStorageClass(const std::string& sopClassUid,
-                             DicomTransferSyntax syntax);
-
-    // TODO => to private
-    bool NegotiatePresentationContext(uint8_t& presentationContextId,
-                                      const std::string& sopClassUid,
-                                      DicomTransferSyntax transferSyntax);
+    void RegisterStorageClass(const std::string& sopClassUid,
+                              DicomTransferSyntax syntax);
 
     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,
+               DcmFileFormat& dicom,
+               bool hasMoveOriginator,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
 
@@ -149,7 +158,22 @@
                std::string& sopInstanceUid,
                const void* buffer,
                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/DicomUserConnection.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1822 +0,0 @@
-/**
- * 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/>.
- **/
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-#include "../PrecompiledHeaders.h"
-#include "DicomUserConnection.h"
-
-#if !defined(DCMTK_VERSION_NUMBER)
-#  error The macro DCMTK_VERSION_NUMBER must be defined
-#endif
-
-#include "../Compatibility.h"
-#include "../DicomFormat/DicomArray.h"
-#include "../Logging.h"
-#include "../OrthancException.h"
-#include "../DicomParsing/FromDcmtkBridge.h"
-#include "../DicomParsing/ToDcmtkBridge.h"
-#include "NetworkingCompatibility.h"
-
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcistrmb.h>
-#include <dcmtk/dcmdata/dcistrmf.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmnet/diutil.h>
-
-#include <set>
-
-
-static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax;
-
-/**
- * "If we have more than 64 storage SOP classes, tools such as
- * storescu will fail because they attempt to negotiate two
- * presentation contexts for each SOP class, and there is a total
- * limit of 128 contexts for one association."
- **/
-static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64;
-
-
-namespace Orthanc
-{
-  // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds
-  static uint32_t defaultTimeout_ = 10;
-
-  struct DicomUserConnection::PImpl
-  {
-    // Connection state
-    uint32_t dimseTimeout_;
-    uint32_t acseTimeout_;
-    T_ASC_Network* net_;
-    T_ASC_Parameters* params_;
-    T_ASC_Association* assoc_;
-
-    bool IsOpen() const
-    {
-      return assoc_ != NULL;
-    }
-
-    void CheckIsOpen() const;
-
-    void Store(std::string& sopClassUidOut  /* out */,
-               std::string& sopInstanceUidOut  /* out */,
-               DcmInputStream& is, 
-               DicomUserConnection& connection,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-  };
-
-
-  static void Check(const OFCondition& cond,
-                    const std::string& aet,
-                    const std::string& command)
-  {
-    if (cond.bad())
-    {
-      // Reformat the error message from DCMTK by turning multiline
-      // errors into a single line
-      
-      std::string s(cond.text());
-      std::string info;
-      info.reserve(s.size());
-
-      bool isMultiline = false;
-      for (size_t i = 0; i < s.size(); i++)
-      {
-        if (s[i] == '\r')
-        {
-          // Ignore
-        }
-        else if (s[i] == '\n')
-        {
-          if (isMultiline)
-          {
-            info += "; ";
-          }
-          else
-          {
-            info += " (";
-            isMultiline = true;
-          }
-        }
-        else
-        {
-          info.push_back(s[i]);
-        }
-      }
-
-      if (isMultiline)
-      {
-        info += ")";
-      }
-
-      throw OrthancException(ErrorCode_NetworkProtocol,
-                             "DicomUserConnection - " + command +
-                             " to AET \"" + aet + "\": " + info);
-    }
-  }
-
-  void DicomUserConnection::PImpl::CheckIsOpen() const
-  {
-    if (!IsOpen())
-    {
-      throw OrthancException(ErrorCode_NetworkProtocol,
-                             "DicomUserConnection: First open the connection");
-    }
-  }
-
-
-  void DicomUserConnection::CheckIsOpen() const
-  {
-    pimpl_->CheckIsOpen();
-  }
-
-
-  static void RegisterStorageSOPClass(T_ASC_Parameters* params,
-                                      unsigned int& presentationContextId,
-                                      const std::string& sopClass,
-                                      const char* asPreferred[],
-                                      std::vector<const char*>& asFallback,
-                                      const std::string& aet)
-  {
-    Check(ASC_addPresentationContext(params, presentationContextId, 
-                                     sopClass.c_str(), asPreferred, 1),
-          aet, "initializing");
-    presentationContextId += 2;
-
-    if (asFallback.size() > 0)
-    {
-      Check(ASC_addPresentationContext(params, presentationContextId, 
-                                       sopClass.c_str(), &asFallback[0], asFallback.size()),
-            aet, "initializing");
-      presentationContextId += 2;
-    }
-  }
-  
-    
-  void DicomUserConnection::SetupPresentationContexts(Mode mode,
-                                                      const std::string& preferredTransferSyntax)
-  {
-    // Flatten an array with the preferred transfer syntax
-    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
-
-    // Setup the fallback transfer syntaxes
-    std::set<std::string> fallbackSyntaxes;
-    fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
-    fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
-    fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
-    fallbackSyntaxes.erase(preferredTransferSyntax);
-
-    // Flatten an array with the fallback transfer syntaxes
-    std::vector<const char*> asFallback;
-    asFallback.reserve(fallbackSyntaxes.size());
-    for (std::set<std::string>::const_iterator 
-           it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it)
-    {
-      asFallback.push_back(it->c_str());
-    }
-
-    CheckStorageSOPClassesInvariant();
-
-    switch (mode)
-    {
-      case Mode_Generic:
-      {
-        unsigned int presentationContextId = 1;
-
-        for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
-             it != reservedStorageSOPClasses_.end(); ++it)
-        {
-          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                                  *it, asPreferred, asFallback, remoteAet_);
-        }
-
-        for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
-             it != storageSOPClasses_.end(); ++it)
-        {
-          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                                  *it, asPreferred, asFallback, remoteAet_);
-        }
-
-        for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
-             it != defaultStorageSOPClasses_.end(); ++it)
-        {
-          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                                  *it, asPreferred, asFallback, remoteAet_);
-        }
-
-        break;
-      }
-
-      case Mode_RequestStorageCommitment:
-      case Mode_ReportStorageCommitment:
-      {
-        const char* as = UID_StorageCommitmentPushModelSOPClass;
-
-        std::vector<const char*> ts;
-        ts.push_back(UID_LittleEndianExplicitTransferSyntax);
-        ts.push_back(UID_LittleEndianImplicitTransferSyntax);
-
-        T_ASC_SC_ROLE role;
-        switch (mode)
-        {
-          case Mode_RequestStorageCommitment:
-            role = ASC_SC_ROLE_DEFAULT;
-            break;
-            
-          case Mode_ReportStorageCommitment:
-            role = ASC_SC_ROLE_SCP;
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_InternalError);
-        }
-        
-        Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/,
-                                         as, &ts[0], ts.size(), role),
-              remoteAet_, "initializing");
-              
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-  
-
-  static bool IsGenericTransferSyntax(const std::string& syntax)
-  {
-    return (syntax == UID_LittleEndianExplicitTransferSyntax ||
-            syntax == UID_BigEndianExplicitTransferSyntax ||
-            syntax == UID_LittleEndianImplicitTransferSyntax);
-  }
-
-
-  void DicomUserConnection::PImpl::Store(std::string& sopClassUidOut,
-                                         std::string& sopInstanceUidOut,
-                                         DcmInputStream& is, 
-                                         DicomUserConnection& connection,
-                                         const std::string& moveOriginatorAET,
-                                         uint16_t moveOriginatorID)
-  {
-    DcmFileFormat dcmff;
-    Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength),
-          connection.remoteAet_, "C-STORE");
-
-    // Determine the storage SOP class UID for this instance
-    OFString sopClassUid;
-    if (dcmff.getDataset()->findAndGetOFString(DCM_SOPClassUID, sopClassUid).good())
-    {
-      connection.AddStorageSOPClass(sopClassUid.c_str());
-    }
-
-    // Determine whether a new presentation context must be
-    // negotiated, depending on the transfer syntax of this instance
-    DcmXfer xfer(dcmff.getDataset()->getOriginalXfer());
-    const std::string syntax(xfer.getXferID());
-    bool isGeneric = IsGenericTransferSyntax(syntax);
-
-    bool renegotiate;
-
-    if (!IsOpen())
-    {
-      renegotiate = true;
-    }
-    else if (isGeneric)
-    {
-      // Are we making a generic-to-specific or specific-to-generic change of
-      // the transfer syntax? If this is the case, renegotiate the connection.
-      renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax());
-
-      if (renegotiate)
-      {
-        LOG(INFO) << "Use of non-generic transfer syntax: the C-Store associated must be renegotiated";
-      }
-    }
-    else
-    {
-      // We are using a specific transfer syntax. Renegotiate if the
-      // current connection does not match this transfer syntax.
-      renegotiate = (syntax != connection.GetPreferredTransferSyntax());
-
-      if (renegotiate)
-      {
-        LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
-      }
-    }
-
-    if (renegotiate)
-    {
-      if (isGeneric)
-      {
-        connection.ResetPreferredTransferSyntax();
-      }
-      else
-      {
-        connection.SetPreferredTransferSyntax(syntax);
-      }
-    }
-
-    if (!connection.IsOpen())
-    {
-      connection.Open();
-    }
-
-    // Figure out which SOP class and SOP instance is encapsulated in the file
-    DIC_UI sopClass;
-    DIC_UI sopInstance;
-
-#if DCMTK_VERSION_NUMBER >= 364
-    if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance)))
-#else
-    if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance))
-#endif
-    {
-      throw OrthancException(ErrorCode_NoSopClassOrInstance,
-                             "Unable to determine the SOP class/instance for C-STORE with AET " +
-                             connection.remoteAet_);
-    }
-
-    sopClassUidOut.assign(sopClass);
-    sopInstanceUidOut.assign(sopInstance);
-
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass);
-    if (presID == 0)
-    {
-      const char *modalityName = dcmSOPClassUIDToModality(sopClass);
-      if (modalityName == NULL) modalityName = dcmFindNameOfUID(sopClass);
-      if (modalityName == NULL) modalityName = "unknown SOP class";
-      throw OrthancException(ErrorCode_NoPresentationContext,
-                             "Unable to determine the accepted presentation contexts for C-STORE with AET " +
-                             connection.remoteAet_ + " (" + std::string(modalityName) + ")");
-    }
-
-    // Prepare the transmission of data
-    T_DIMSE_C_StoreRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = assoc_->nextMsgID++;
-    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-    strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN);
-
-    if (!moveOriginatorAET.empty())
-    {
-      strncpy(request.MoveOriginatorApplicationEntityTitle, 
-              moveOriginatorAET.c_str(), DIC_AE_LEN);
-      request.opts = O_STORE_MOVEORIGINATORAETITLE;
-
-      request.MoveOriginatorID = moveOriginatorID;  // The type DIC_US is an alias for uint16_t
-      request.opts |= O_STORE_MOVEORIGINATORID;
-    }
-
-    // Finally conduct transmission of data
-    T_DIMSE_C_StoreRSP response;
-    DcmDataset* statusDetail = NULL;
-    Check(DIMSE_storeUser(assoc_, presID, &request,
-                          NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL,
-                          /*opt_blockMode*/ (dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
-                          /*opt_dimse_timeout*/ dimseTimeout_,
-                          &response, &statusDetail, NULL),
-          connection.remoteAet_, "C-STORE");
-
-    if (statusDetail != NULL) 
-    {
-      delete statusDetail;
-    }
-    
-    
-    /**
-     * New in Orthanc 1.6.0: Deal with failures during C-STORE.
-     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1
-     **/
-    
-    if (response.DimseStatus != 0x0000 &&  // Success
-        response.DimseStatus != 0xB000 &&  // Warning - Coercion of Data Elements
-        response.DimseStatus != 0xB007 &&  // Warning - Data Set does not match SOP Class
-        response.DimseStatus != 0xB006)    // Warning - Elements Discarded
-    {
-      char buf[16];
-      sprintf(buf, "%04X", response.DimseStatus);
-      throw OrthancException(ErrorCode_NetworkProtocol,
-                             "C-STORE SCU to AET \"" + connection.remoteAet_ +
-                             "\" has failed with DIMSE status 0x" + buf);
-    }
-  }
-
-
-  namespace
-  {
-    struct FindPayload
-    {
-      DicomFindAnswers* answers;
-      const char*       level;
-      bool              isWorklist;
-    };
-  }
-
-
-  static void FindCallback(
-    /* in */
-    void *callbackData,
-    T_DIMSE_C_FindRQ *request,      /* original find request */
-    int responseCount,
-    T_DIMSE_C_FindRSP *response,    /* pending response received */
-    DcmDataset *responseIdentifiers /* pending response identifiers */
-    )
-  {
-    FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData);
-
-    if (responseIdentifiers != NULL)
-    {
-      if (payload.isWorklist)
-      {
-        ParsedDicomFile answer(*responseIdentifiers);
-        payload.answers->Add(answer);
-      }
-      else
-      {
-        DicomMap m;
-        FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers);
-        
-        if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
-        {
-          m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false);
-        }
-
-        payload.answers->Add(m);
-      }
-    }
-  }
-
-
-  static void NormalizeFindQuery(DicomMap& fixedQuery,
-                                 ResourceType level,
-                                 const DicomMap& fields)
-  {
-    std::set<DicomTag> allowedTags;
-
-    // WARNING: Do not add "break" or reorder items in this switch-case!
-    switch (level)
-    {
-      case ResourceType_Instance:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance);
-
-      case ResourceType_Series:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Series);
-
-      case ResourceType_Study:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Study);
-
-      case ResourceType_Patient:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
-        break;
-
-      case ResourceType_Study:
-        allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
-        allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY);
-        break;
-
-      case ResourceType_Series:
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
-        break;
-
-      default:
-        break;
-    }
-
-    allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-
-    DicomArray query(fields);
-    for (size_t i = 0; i < query.GetSize(); i++)
-    {
-      const DicomTag& tag = query.GetElement(i).GetTag();
-      if (allowedTags.find(tag) == allowedTags.end())
-      {
-        LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag;
-      }
-      else
-      {
-        fixedQuery.SetValue(tag, query.GetElement(i).GetValue());
-      }
-    }
-  }
-
-
-  static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields,
-                                             ModalityManufacturer manufacturer)
-  {
-    // Fix outgoing C-Find requests issue for Syngo.Via and its
-    // solution was reported by Emsy Chan by private mail on
-    // 2015-06-17. According to Robert van Ommen (2015-11-30), the
-    // same fix is required for Agfa Impax. This was generalized for
-    // generic manufacturer since it seems to affect PhilipsADW,
-    // GEWAServer as well:
-    // https://bitbucket.org/sjodogne/orthanc/issues/31/
-
-    switch (manufacturer)
-    {
-      case ModalityManufacturer_GenericNoWildcardInDates:
-      case ModalityManufacturer_GenericNoUniversalWildcard:
-      {
-        std::unique_ptr<DicomMap> fix(fields.Clone());
-
-        std::set<DicomTag> tags;
-        fix->GetTags(tags);
-
-        for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
-        {
-          // Replace a "*" wildcard query by an empty query ("") for
-          // "date" or "all" value representations depending on the
-          // type of manufacturer.
-          if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard ||
-              (manufacturer == ModalityManufacturer_GenericNoWildcardInDates &&
-               FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date))
-          {
-            const DicomValue* value = fix->TestAndGetValue(*it);
-
-            if (value != NULL && 
-                !value->IsNull() &&
-                value->GetContent() == "*")
-            {
-              fix->SetValue(*it, "", false);
-            }
-          }
-        }
-
-        return new ParsedDicomFile(*fix, GetDefaultDicomEncoding(), false /* be strict */);
-      }
-
-      default:
-        return new ParsedDicomFile(fields, GetDefaultDicomEncoding(), false /* be strict */);
-    }
-  }
-
-
-  static void ExecuteFind(DicomFindAnswers& answers,
-                          T_ASC_Association* association,
-                          DcmDataset* dataset,
-                          const char* sopClass,
-                          bool isWorklist,
-                          const char* level,
-                          uint32_t dimseTimeout,
-                          const std::string& remoteAet)
-  {
-    assert(isWorklist ^ (level != NULL));
-
-    FindPayload payload;
-    payload.answers = &answers;
-    payload.level = level;
-    payload.isWorklist = isWorklist;
-
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(association, sopClass);
-    if (presID == 0)
-    {
-      throw OrthancException(ErrorCode_DicomFindUnavailable,
-                             "Remote AET is " + remoteAet);
-    }
-
-    T_DIMSE_C_FindRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = association->nextMsgID++;
-    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-
-    T_DIMSE_C_FindRSP response;
-    DcmDataset* statusDetail = NULL;
-
-#if DCMTK_VERSION_NUMBER >= 364
-    int responseCount;
-#endif
-
-    OFCondition cond = DIMSE_findUser(association, presID, &request, dataset,
-#if DCMTK_VERSION_NUMBER >= 364
-				      responseCount,
-#endif
-                                      FindCallback, &payload,
-                                      /*opt_blockMode*/ (dimseTimeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
-                                      /*opt_dimse_timeout*/ dimseTimeout,
-                                      &response, &statusDetail);
-
-    if (statusDetail)
-    {
-      delete statusDetail;
-    }
-
-    Check(cond, remoteAet, "C-FIND");
-
-    
-    /**
-     * New in Orthanc 1.6.0: Deal with failures during C-FIND.
-     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1
-     **/
-    
-    if (response.DimseStatus != 0x0000 &&  // Success
-        response.DimseStatus != 0xFF00 &&  // Pending - Matches are continuing 
-        response.DimseStatus != 0xFF01)    // Pending - Matches are continuing 
-    {
-      char buf[16];
-      sprintf(buf, "%04X", response.DimseStatus);
-
-      if (response.DimseStatus == STATUS_FIND_Failed_UnableToProcess)
-      {
-        throw OrthancException(ErrorCode_NetworkProtocol,
-                               HttpStatus_422_UnprocessableEntity,
-                               "C-FIND SCU to AET \"" + remoteAet +
-                               "\" has failed with DIMSE status 0x" + buf +
-                               " (unable to process - invalid query ?)"
-                               );
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_NetworkProtocol,
-                               "C-FIND SCU to AET \"" + remoteAet +
-                               "\" has failed with DIMSE status 0x" + buf);
-      }
-    }
-
-  }
-
-
-  void DicomUserConnection::Find(DicomFindAnswers& result,
-                                 ResourceType level,
-                                 const DicomMap& originalFields,
-                                 bool normalize)
-  {
-    CheckIsOpen();
-
-    std::unique_ptr<ParsedDicomFile> query;
-
-    if (normalize)
-    {
-      DicomMap fields;
-      NormalizeFindQuery(fields, level, originalFields);
-      query.reset(ConvertQueryFields(fields, manufacturer_));
-    }
-    else
-    {
-      query.reset(new ParsedDicomFile(originalFields,
-                                      GetDefaultDicomEncoding(),
-                                      false /* be strict */));
-    }
-    
-    DcmDataset* dataset = query->GetDcmtkObject().getDataset();
-
-    const char* clevel = NULL;
-    const char* sopClass = NULL;
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        clevel = "PATIENT";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT");
-        sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
-        break;
-
-      case ResourceType_Study:
-        clevel = "STUDY";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY");
-        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
-        break;
-
-      case ResourceType_Series:
-        clevel = "SERIES";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES");
-        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
-        break;
-
-      case ResourceType_Instance:
-        clevel = "IMAGE";
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
-        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-
-    const char* universal;
-    if (manufacturer_ == ModalityManufacturer_GE)
-    {
-      universal = "*";
-    }
-    else
-    {
-      universal = "";
-    }      
-    
-
-    // Add the expected tags for this query level.
-    // WARNING: Do not reorder or add "break" in this switch-case!
-    switch (level)
-    {
-      case ResourceType_Instance:
-        if (!dataset->tagExists(DCM_SOPInstanceUID))
-        {
-          DU_putStringDOElement(dataset, DCM_SOPInstanceUID, universal);
-        }
-
-      case ResourceType_Series:
-        if (!dataset->tagExists(DCM_SeriesInstanceUID))
-        {
-          DU_putStringDOElement(dataset, DCM_SeriesInstanceUID, universal);
-        }
-
-      case ResourceType_Study:
-        if (!dataset->tagExists(DCM_AccessionNumber))
-        {
-          DU_putStringDOElement(dataset, DCM_AccessionNumber, universal);
-        }
-
-        if (!dataset->tagExists(DCM_StudyInstanceUID))
-        {
-          DU_putStringDOElement(dataset, DCM_StudyInstanceUID, universal);
-        }
-
-      case ResourceType_Patient:
-        if (!dataset->tagExists(DCM_PatientID))
-        {
-          DU_putStringDOElement(dataset, DCM_PatientID, universal);
-        }
-        
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(clevel != NULL && sopClass != NULL);
-    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel,
-                pimpl_->dimseTimeout_, remoteAet_);
-  }
-
-
-  void DicomUserConnection::MoveInternal(const std::string& targetAet,
-                                         ResourceType level,
-                                         const DicomMap& fields)
-  {
-    CheckIsOpen();
-
-    std::unique_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
-    DcmDataset* dataset = query->GetDcmtkObject().getDataset();
-
-    const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel;
-    switch (level)
-    {
-      case ResourceType_Patient:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT");
-        break;
-
-      case ResourceType_Study:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY");
-        break;
-
-      case ResourceType_Series:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES");
-        break;
-
-      case ResourceType_Instance:
-        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
-    if (presID == 0)
-    {
-      throw OrthancException(ErrorCode_DicomMoveUnavailable,
-                             "Remote AET is " + remoteAet_);
-    }
-
-    T_DIMSE_C_MoveRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = pimpl_->assoc_->nextMsgID++;
-    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-    strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN);
-
-    T_DIMSE_C_MoveRSP response;
-    DcmDataset* statusDetail = NULL;
-    DcmDataset* responseIdentifiers = NULL;
-    OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset,
-                                      NULL, NULL,
-                                      /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
-                                      /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
-                                      pimpl_->net_, NULL, NULL,
-                                      &response, &statusDetail, &responseIdentifiers);
-
-    if (statusDetail)
-    {
-      delete statusDetail;
-    }
-
-    if (responseIdentifiers)
-    {
-      delete responseIdentifiers;
-    }
-
-    Check(cond, remoteAet_, "C-MOVE");
-
-    
-    /**
-     * New in Orthanc 1.6.0: Deal with failures during C-MOVE.
-     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2
-     **/
-    
-    if (response.DimseStatus != 0x0000 &&  // Success
-        response.DimseStatus != 0xFF00)    // Pending - Sub-operations are continuing
-    {
-      char buf[16];
-      sprintf(buf, "%04X", response.DimseStatus);
-
-      if (response.DimseStatus == STATUS_MOVE_Failed_UnableToProcess)
-      {
-        throw OrthancException(ErrorCode_NetworkProtocol,
-                               HttpStatus_422_UnprocessableEntity,
-                               "C-MOVE SCU to AET \"" + remoteAet_ +
-                               "\" has failed with DIMSE status 0x" + buf +
-                               " (unable to process - resource not found ?)"
-                               );
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_NetworkProtocol,
-                               "C-MOVE SCU to AET \"" + remoteAet_ +
-                               "\" has failed with DIMSE status 0x" + buf);
-      }
-    }
-  }
-
-
-  void DicomUserConnection::ResetStorageSOPClasses()
-  {
-    CheckStorageSOPClassesInvariant();
-
-    storageSOPClasses_.clear();
-    defaultStorageSOPClasses_.clear();
-
-    // Copy the short list of storage SOP classes from DCMTK, making
-    // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**).
-
-    std::set<std::string> uncommon;
-    uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage);
-
-    // Add the storage syntaxes for C-STORE
-    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
-    {
-      if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end())
-      {
-        defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]);
-      }
-    }
-
-    CheckStorageSOPClassesInvariant();
-  }
-
-
-  void DicomUserConnection::DefaultSetup()
-  {
-    preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX;
-    localAet_ = "STORESCU";
-    remoteAet_ = "ANY-SCP";
-    remoteHost_ = "127.0.0.1";
-    remotePort_ = 104;
-    manufacturer_ = ModalityManufacturer_Generic;
-
-    SetTimeout(defaultTimeout_);
-    pimpl_->net_ = NULL;
-    pimpl_->params_ = NULL;
-    pimpl_->assoc_ = NULL;
-
-    // SOP classes for C-ECHO, C-FIND and C-MOVE (**)
-    reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
-    reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
-    reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
-    reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
-    reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel);
-
-    ResetStorageSOPClasses();
-  }
-   
-
-  DicomUserConnection::DicomUserConnection() : 
-    pimpl_(new PImpl)
-  {
-    DefaultSetup();
-  }
-  
-
-  DicomUserConnection::DicomUserConnection(const std::string& localAet,
-                                           const RemoteModalityParameters& remote) : 
-    pimpl_(new PImpl)
-  {
-    DefaultSetup();
-    SetLocalApplicationEntityTitle(localAet);
-    SetRemoteModality(remote);
-  }
-
-
-  DicomUserConnection::~DicomUserConnection()
-  {
-    Close();
-  }
-
-
-  void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters)
-  {
-    SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle());
-    SetRemoteHost(parameters.GetHost());
-    SetRemotePort(parameters.GetPortNumber());
-    SetRemoteManufacturer(parameters.GetManufacturer());
-  }
-
-
-  void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
-  {
-    if (localAet_ != aet)
-    {
-      Close();
-      localAet_ = aet;
-    }
-  }
-
-  void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet)
-  {
-    if (remoteAet_ != aet)
-    {
-      Close();
-      remoteAet_ = aet;
-    }
-  }
-
-  void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer)
-  {
-    if (manufacturer_ != manufacturer)
-    {
-      Close();
-      manufacturer_ = manufacturer;
-    }
-  }
-
-  void DicomUserConnection::ResetPreferredTransferSyntax()
-  {
-    SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX);
-  }
-
-  void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax)
-  {
-    if (preferredTransferSyntax_ != preferredTransferSyntax)
-    {
-      Close();
-      preferredTransferSyntax_ = preferredTransferSyntax;
-    }
-  }
-
-
-  void DicomUserConnection::SetRemoteHost(const std::string& host)
-  {
-    if (remoteHost_ != host)
-    {
-      if (host.size() > HOST_NAME_MAX - 10)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "Invalid host name (too long): " + host);
-      }
-
-      Close();
-      remoteHost_ = host;
-    }
-  }
-
-  void DicomUserConnection::SetRemotePort(uint16_t port)
-  {
-    if (remotePort_ != port)
-    {
-      Close();
-      remotePort_ = port;
-    }
-  }
-
-  void DicomUserConnection::OpenInternal(Mode mode)
-  {
-    if (IsOpen())
-    {
-      // Don't reopen the connection
-      return;
-    }
-
-    LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
-              << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host "
-              << GetRemoteHost() << ":" << GetRemotePort() 
-              << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")";
-
-    Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_), remoteAet_, "connecting");
-    Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU), remoteAet_, "connecting");
-
-    // Set this application's title and the called application's title in the params
-    Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL),
-          remoteAet_, "connecting");
-
-    // Set the network addresses of the local and remote entities
-    char localHost[HOST_NAME_MAX];
-    gethostname(localHost, HOST_NAME_MAX - 1);
-
-    char remoteHostAndPort[HOST_NAME_MAX];
-
-#ifdef _MSC_VER
-    _snprintf
-#else
-      snprintf
-#endif
-      (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_);
-
-    Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort),
-          remoteAet_, "connecting");
-
-    // Set various options
-    Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false),
-          remoteAet_, "connecting");
-
-    SetupPresentationContexts(mode, preferredTransferSyntax_);
-
-    // Do the association
-    Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_),
-          remoteAet_, "connecting");
-
-    if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0)
-    {
-      throw OrthancException(ErrorCode_NoPresentationContext,
-                             "Unable to negotiate a presentation context with AET " +
-                             remoteAet_);
-    }
-  }
-
-  void DicomUserConnection::Close()
-  {
-    if (pimpl_->assoc_ != NULL)
-    {
-      ASC_releaseAssociation(pimpl_->assoc_);
-      ASC_destroyAssociation(&pimpl_->assoc_);
-      pimpl_->assoc_ = NULL;
-      pimpl_->params_ = NULL;
-    }
-    else
-    {
-      if (pimpl_->params_ != NULL)
-      {
-        ASC_destroyAssociationParameters(&pimpl_->params_);
-        pimpl_->params_ = NULL;
-      }
-    }
-
-    if (pimpl_->net_ != NULL)
-    {
-      ASC_dropNetwork(&pimpl_->net_);
-      pimpl_->net_ = NULL;
-    }
-  }
-
-  bool DicomUserConnection::IsOpen() const
-  {
-    return pimpl_->IsOpen();
-  }
-
-  void DicomUserConnection::Store(std::string& sopClassUid /* out */,
-                                  std::string& sopInstanceUid /* out */,
-                                  const void* buffer, 
-                                  size_t size,
-                                  const std::string& moveOriginatorAET,
-                                  uint16_t moveOriginatorID)
-  {
-    // Prepare an input stream for the memory buffer
-    DcmInputBufferStream is;
-    if (size > 0)
-      is.setBuffer(buffer, size);
-    is.setEos();
-      
-    pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID);
-  }
-
-  void DicomUserConnection::Store(std::string& sopClassUid /* out */,
-                                  std::string& sopInstanceUid /* out */,
-                                  const std::string& buffer,
-                                  const std::string& moveOriginatorAET,
-                                  uint16_t moveOriginatorID)
-  {
-    if (buffer.size() > 0)
-      Store(sopClassUid, sopInstanceUid, &buffer[0], buffer.size(),
-            moveOriginatorAET, moveOriginatorID);
-    else
-      Store(sopClassUid, sopInstanceUid, NULL, 0, moveOriginatorAET, moveOriginatorID);
-  }
-
-  void DicomUserConnection::StoreFile(std::string& sopClassUid /* out */,
-                                      std::string& sopInstanceUid /* out */,
-                                      const std::string& path,
-                                      const std::string& moveOriginatorAET,
-                                      uint16_t moveOriginatorID)
-  {
-    // Prepare an input stream for the file
-    DcmInputFileStream is(path.c_str());
-    pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID);
-  }
-
-  bool DicomUserConnection::Echo()
-  {
-    CheckIsOpen();
-    DIC_US status;
-    Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, 
-                         /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
-                         /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
-                         &status, NULL), remoteAet_, "C-ECHO");
-    return status == STATUS_Success;
-  }
-
-
-  static void TestAndCopyTag(DicomMap& result,
-                             const DicomMap& source,
-                             const DicomTag& tag)
-  {
-    if (!source.HasTag(tag))
-    {
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-    else
-    {
-      result.SetValue(tag, source.GetValue(tag));
-    }
-  }
-
-
-  void DicomUserConnection::Move(const std::string& targetAet,
-                                 ResourceType level,
-                                 const DicomMap& findResult)
-  {
-    DicomMap move;
-    switch (level)
-    {
-      case ResourceType_Patient:
-        TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID);
-        break;
-
-      case ResourceType_Study:
-        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
-        break;
-
-      case ResourceType_Series:
-        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
-        TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID);
-        break;
-
-      case ResourceType_Instance:
-        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
-        TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID);
-        TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    MoveInternal(targetAet, level, move);
-  }
-
-
-  void DicomUserConnection::Move(const std::string& targetAet,
-                                 const DicomMap& findResult)
-  {
-    if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent();
-    ResourceType level = StringToResourceType(tmp.c_str());
-
-    Move(targetAet, level, findResult);
-  }
-
-
-  void DicomUserConnection::MovePatient(const std::string& targetAet,
-                                        const std::string& patientId)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false);
-    MoveInternal(targetAet, ResourceType_Patient, query);
-  }
-
-  void DicomUserConnection::MoveStudy(const std::string& targetAet,
-                                      const std::string& studyUid)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
-    MoveInternal(targetAet, ResourceType_Study, query);
-  }
-
-  void DicomUserConnection::MoveSeries(const std::string& targetAet,
-                                       const std::string& studyUid,
-                                       const std::string& seriesUid)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
-    query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false);
-    MoveInternal(targetAet, ResourceType_Series, query);
-  }
-
-  void DicomUserConnection::MoveInstance(const std::string& targetAet,
-                                         const std::string& studyUid,
-                                         const std::string& seriesUid,
-                                         const std::string& instanceUid)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
-    query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false);
-    query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false);
-    MoveInternal(targetAet, ResourceType_Instance, query);
-  }
-
-
-  void DicomUserConnection::SetTimeout(uint32_t seconds)
-  {
-    if (seconds == 0)
-    {
-      DisableTimeout();
-    }
-    else
-    {
-      dcmConnectionTimeout.set(seconds);
-      pimpl_->dimseTimeout_ = seconds;
-      pimpl_->acseTimeout_ = seconds;  // Timeout used during association negociation and ASC_releaseAssociation()
-    }
-  }
-
-
-  void DicomUserConnection::DisableTimeout()
-  {
-    /**
-     * Global timeout (seconds) for connecting to remote hosts.
-     * Default value is -1 which selects infinite timeout, i.e. blocking connect().
-     */
-    dcmConnectionTimeout.set(-1);
-    pimpl_->dimseTimeout_ = 0;
-    pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation and ASC_releaseAssociation()
-  }
-
-
-  void DicomUserConnection::CheckStorageSOPClassesInvariant() const
-  {
-    assert(storageSOPClasses_.size() + 
-           defaultStorageSOPClasses_.size() + 
-           reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES);
-  }
-
-  void DicomUserConnection::AddStorageSOPClass(const char* sop)
-  {
-    CheckStorageSOPClassesInvariant();
-
-    if (storageSOPClasses_.find(sop) != storageSOPClasses_.end())
-    {
-      // This storage SOP class is already explicitly registered. Do
-      // nothing.
-      return;
-    }
-
-    if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end())
-    {
-      // This storage SOP class is not explicitly registered, but is
-      // used by default. Just register it explicitly.
-      defaultStorageSOPClasses_.erase(sop);
-      storageSOPClasses_.insert(sop);
-
-      CheckStorageSOPClassesInvariant();
-      return;
-    }
-
-    // This storage SOP class is neither explicitly, nor implicitly
-    // registered. Close the connection and register it explicitly.
-
-    Close();
-
-    if (reservedStorageSOPClasses_.size() + 
-        storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)  // (*)
-    {
-      // The maximum number of SOP classes is reached
-      ResetStorageSOPClasses();
-      defaultStorageSOPClasses_.erase(sop);
-    }
-    else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + 
-             defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)
-    {
-      // Make room in the default storage syntaxes
-      assert(!defaultStorageSOPClasses_.empty());  // Necessarily true because condition (*) is false
-      defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin());
-    }
-
-    // Explicitly register the new storage syntax
-    storageSOPClasses_.insert(sop);
-
-    CheckStorageSOPClassesInvariant();
-  }
-
-
-  void DicomUserConnection::FindWorklist(DicomFindAnswers& result,
-                                         ParsedDicomFile& query)
-  {
-    CheckIsOpen();
-
-    DcmDataset* dataset = query.GetDcmtkObject().getDataset();
-    const char* sopClass = UID_FINDModalityWorklistInformationModel;
-
-    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true,
-                NULL, pimpl_->dimseTimeout_, remoteAet_);
-  }
-
-  
-  void DicomUserConnection::SetDefaultTimeout(uint32_t seconds)
-  {
-    LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " 
-              << seconds << " seconds (0 = no timeout)";
-    defaultTimeout_ = seconds;
-  }  
-
-
-  bool DicomUserConnection::IsSameAssociation(const std::string& localAet,
-                                              const RemoteModalityParameters& remote) const
-  {
-    return (localAet_ == localAet &&
-            remoteAet_ == remote.GetApplicationEntityTitle() &&
-            remoteHost_ == remote.GetHost() &&
-            remotePort_ == remote.GetPortNumber() &&
-            manufacturer_ == remote.GetManufacturer());
-  }
-
-
-  static void FillSopSequence(DcmDataset& dataset,
-                              const DcmTagKey& tag,
-                              const std::vector<std::string>& sopClassUids,
-                              const std::vector<std::string>& sopInstanceUids,
-                              const std::vector<StorageCommitmentFailureReason>& failureReasons,
-                              bool hasFailureReasons)
-  {
-    assert(sopClassUids.size() == sopInstanceUids.size() &&
-           (hasFailureReasons ?
-            failureReasons.size() == sopClassUids.size() :
-            failureReasons.empty()));
-
-    if (sopInstanceUids.empty())
-    {
-      // Add an empty sequence
-      if (!dataset.insertEmptyElement(tag).good())
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-    else
-    {
-      for (size_t i = 0; i < sopClassUids.size(); i++)
-      {
-        std::unique_ptr<DcmItem> item(new DcmItem);
-        if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() ||
-            !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() ||
-            (hasFailureReasons &&
-             !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) ||
-            !dataset.insertSequenceItem(tag, item.release()).good())
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-      }
-    }
-  }                              
-
-
-  
-
-  void DicomUserConnection::ReportStorageCommitment(
-    const std::string& transactionUid,
-    const std::vector<std::string>& sopClassUids,
-    const std::vector<std::string>& sopInstanceUids,
-    const std::vector<StorageCommitmentFailureReason>& failureReasons)
-  {
-    if (sopClassUids.size() != sopInstanceUids.size() ||
-        sopClassUids.size() != failureReasons.size())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    
-    if (IsOpen())
-    {
-      Close();
-    }
-
-    std::vector<std::string> successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids;
-    std::vector<StorageCommitmentFailureReason> failedReasons;
-
-    successSopClassUids.reserve(sopClassUids.size());
-    successSopInstanceUids.reserve(sopClassUids.size());
-    failedSopClassUids.reserve(sopClassUids.size());
-    failedSopInstanceUids.reserve(sopClassUids.size());
-    failedReasons.reserve(sopClassUids.size());
-
-    for (size_t i = 0; i < sopClassUids.size(); i++)
-    {
-      switch (failureReasons[i])
-      {
-        case StorageCommitmentFailureReason_Success:
-          successSopClassUids.push_back(sopClassUids[i]);
-          successSopInstanceUids.push_back(sopInstanceUids[i]);
-          break;
-
-        case StorageCommitmentFailureReason_ProcessingFailure:
-        case StorageCommitmentFailureReason_NoSuchObjectInstance:
-        case StorageCommitmentFailureReason_ResourceLimitation:
-        case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported:
-        case StorageCommitmentFailureReason_ClassInstanceConflict:
-        case StorageCommitmentFailureReason_DuplicateTransactionUID:
-          failedSopClassUids.push_back(sopClassUids[i]);
-          failedSopInstanceUids.push_back(sopInstanceUids[i]);
-          failedReasons.push_back(failureReasons[i]);
-          break;
-
-        default:
-        {
-          char buf[16];
-          sprintf(buf, "%04xH", failureReasons[i]);
-          throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                 "Unsupported failure reason for storage commitment: " + std::string(buf));
-        }
-      }
-    }
-    
-    try
-    {
-      OpenInternal(Mode_ReportStorageCommitment);
-
-      /**
-       * N-EVENT-REPORT
-       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
-       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1
-       *
-       * Status code:
-       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8
-       **/
-
-      /**
-       * Send the "EVENT_REPORT_RQ" request
-       **/
-
-      LOG(INFO) << "Reporting modality \"" << remoteAet_
-                << "\" about storage commitment transaction: " << transactionUid
-                << " (" << successSopClassUids.size() << " successes, " 
-                << failedSopClassUids.size() << " failures)";
-      const DIC_US messageId = pimpl_->assoc_->nextMsgID++;
-      
-      {
-        T_DIMSE_Message message;
-        memset(&message, 0, sizeof(message));
-        message.CommandField = DIMSE_N_EVENT_REPORT_RQ;
-
-        T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ;
-        content.MessageID = messageId;
-        strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
-        strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
-        content.DataSetType = DIMSE_DATASET_PRESENT;
-
-        DcmDataset dataset;
-        if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good())
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-
-        {
-          std::vector<StorageCommitmentFailureReason> empty;
-          FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids,
-                          successSopInstanceUids, empty, false);
-        }
-
-        // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
-        if (failedSopClassUids.empty())
-        {
-          content.EventTypeID = 1;  // "Storage Commitment Request Successful"
-        }
-        else
-        {
-          content.EventTypeID = 2;  // "Storage Commitment Request Complete - Failures Exist"
-
-          // Failure reason
-          // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1
-          FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids,
-                          failedSopInstanceUids, failedReasons, true);
-        }
-
-        int presID = ASC_findAcceptedPresentationContextID(
-          pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass);
-        if (presID == 0)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_);
-        }
-
-        if (!DIMSE_sendMessageUsingMemoryData(
-              pimpl_->assoc_, presID, &message, NULL /* status detail */,
-              &dataset, NULL /* callback */, NULL /* callback context */,
-              NULL /* commandSet */).good())
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol);
-        }
-      }
-
-      /**
-       * Read the "EVENT_REPORT_RSP" response
-       **/
-
-      {
-        T_ASC_PresentationContextID presID = 0;
-        T_DIMSE_Message message;
-
-        const int timeout = pimpl_->dimseTimeout_;
-        if (!DIMSE_receiveCommand(pimpl_->assoc_,
-                                  (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout,
-                                  &presID, &message, NULL /* no statusDetail */).good() ||
-            message.CommandField != DIMSE_N_EVENT_REPORT_RSP)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "Unable to read N-EVENT-REPORT response from AET: " + remoteAet_);
-        }
-
-        const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP;
-        if (content.MessageIDBeingRespondedTo != messageId ||
-            !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) ||
-            !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) ||
-            //(content.opts & O_NEVENTREPORT_EVENTTYPEID) ||  // Pedantic test - The "content.EventTypeID" is not used by Orthanc
-            std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
-            std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance ||
-            content.DataSetType != DIMSE_DATASET_NULL)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "Badly formatted N-EVENT-REPORT response from AET: " + remoteAet_);
-        }
-
-        if (content.DimseStatus != 0 /* success */)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "The request cannot be handled by remote AET: " + remoteAet_);
-        }
-      }
-
-      Close();
-    }
-    catch (OrthancException&)
-    {
-      Close();
-      throw;
-    }
-  }
-
-
-  
-  void DicomUserConnection::RequestStorageCommitment(
-    const std::string& transactionUid,
-    const std::vector<std::string>& sopClassUids,
-    const std::vector<std::string>& sopInstanceUids)
-  {
-    if (sopClassUids.size() != sopInstanceUids.size())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    for (size_t i = 0; i < sopClassUids.size(); i++)
-    {
-      if (sopClassUids[i].empty() ||
-          sopInstanceUids[i].empty())
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "The SOP class/instance UIDs cannot be empty, found: \"" +
-                               sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\"");
-      }
-    }
-
-    if (transactionUid.size() < 5 ||
-        transactionUid.substr(0, 5) != "2.25.")
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (IsOpen())
-    {
-      Close();
-    }
-
-    try
-    {
-      OpenInternal(Mode_RequestStorageCommitment);
-
-      /**
-       * N-ACTION
-       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html
-       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4
-       *
-       * Status code:
-       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8
-       **/
-
-      /**
-       * Send the "N_ACTION_RQ" request
-       **/
-
-      LOG(INFO) << "Request to modality \"" << remoteAet_
-                << "\" about storage commitment for " << sopClassUids.size()
-                << " instances, with transaction UID: " << transactionUid;
-      const DIC_US messageId = pimpl_->assoc_->nextMsgID++;
-      
-      {
-        T_DIMSE_Message message;
-        memset(&message, 0, sizeof(message));
-        message.CommandField = DIMSE_N_ACTION_RQ;
-
-        T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ;
-        content.MessageID = messageId;
-        strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
-        strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
-        content.ActionTypeID = 1;  // "Request Storage Commitment"
-        content.DataSetType = DIMSE_DATASET_PRESENT;
-
-        DcmDataset dataset;
-        if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good())
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-
-        {
-          std::vector<StorageCommitmentFailureReason> empty;
-          FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false);
-        }
-          
-        int presID = ASC_findAcceptedPresentationContextID(
-          pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass);
-        if (presID == 0)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "Unable to send N-ACTION request to AET: " + remoteAet_);
-        }
-
-        if (!DIMSE_sendMessageUsingMemoryData(
-              pimpl_->assoc_, presID, &message, NULL /* status detail */,
-              &dataset, NULL /* callback */, NULL /* callback context */,
-              NULL /* commandSet */).good())
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol);
-        }
-      }
-
-      /**
-       * Read the "N_ACTION_RSP" response
-       **/
-
-      {
-        T_ASC_PresentationContextID presID = 0;
-        T_DIMSE_Message message;
-        
-        const int timeout = pimpl_->dimseTimeout_;
-        if (!DIMSE_receiveCommand(pimpl_->assoc_,
-                                  (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout,
-                                  &presID, &message, NULL /* no statusDetail */).good() ||
-            message.CommandField != DIMSE_N_ACTION_RSP)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "Unable to read N-ACTION response from AET: " + remoteAet_);
-        }
-
-        const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP;
-        if (content.MessageIDBeingRespondedTo != messageId ||
-            !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) ||
-            !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) ||
-            //(content.opts & O_NACTION_ACTIONTYPEID) ||  // Pedantic test - The "content.ActionTypeID" is not used by Orthanc
-            std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
-            std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance ||
-            content.DataSetType != DIMSE_DATASET_NULL)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "Badly formatted N-ACTION response from AET: " + remoteAet_);
-        }
-
-        if (content.DimseStatus != 0 /* success */)
-        {
-          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
-                                 "The request cannot be handled by remote AET: " + remoteAet_);
-        }
-      }
-
-      Close();
-    }
-    catch (OrthancException&)
-    {
-      Close();
-      throw;
-    }
-  }
-}
--- a/Core/DicomNetworking/DicomUserConnection.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,253 +0,0 @@
-/**
- * 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 ORTHANC_ENABLE_DCMTK_NETWORKING != 1
-#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
-#endif
-
-#include "DicomFindAnswers.h"
-#include "../Enumerations.h"
-#include "RemoteModalityParameters.h"
-
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
-#include <list>
-
-namespace Orthanc
-{
-  class DicomUserConnection : public boost::noncopyable
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    enum Mode
-    {
-      Mode_Generic,
-      Mode_ReportStorageCommitment,
-      Mode_RequestStorageCommitment
-    };
-    
-    // Connection parameters
-    std::string preferredTransferSyntax_;
-    std::string localAet_;
-    std::string remoteAet_;
-    std::string remoteHost_;
-    uint16_t remotePort_;
-    ModalityManufacturer manufacturer_;
-    std::set<std::string> storageSOPClasses_;
-    std::list<std::string> reservedStorageSOPClasses_;
-    std::set<std::string> defaultStorageSOPClasses_;
-
-    void CheckIsOpen() const;
-
-    void SetupPresentationContexts(Mode mode,
-                                   const std::string& preferredTransferSyntax);
-
-    void MoveInternal(const std::string& targetAet,
-                      ResourceType level,
-                      const DicomMap& fields);
-
-    void ResetStorageSOPClasses();
-
-    void CheckStorageSOPClassesInvariant() const;
-
-    void DefaultSetup();
-
-    void OpenInternal(Mode mode);
-
-  public:
-    DicomUserConnection();
-
-    ~DicomUserConnection();
-
-    // This constructor corresponds to behavior of the old class
-    // "ReusableDicomUserConnection", without the call to "Open()"
-    DicomUserConnection(const std::string& localAet,
-                        const RemoteModalityParameters& remote);
-
-    void SetRemoteModality(const RemoteModalityParameters& parameters);
-
-    void SetLocalApplicationEntityTitle(const std::string& aet);
-
-    const std::string& GetLocalApplicationEntityTitle() const
-    {
-      return localAet_;
-    }
-
-    void SetRemoteApplicationEntityTitle(const std::string& aet);
-
-    const std::string& GetRemoteApplicationEntityTitle() const
-    {
-      return remoteAet_;
-    }
-
-    void SetRemoteHost(const std::string& host);
-
-    const std::string& GetRemoteHost() const
-    {
-      return remoteHost_;
-    }
-
-    void SetRemotePort(uint16_t port);
-
-    uint16_t GetRemotePort() const
-    {
-      return remotePort_;
-    }
-
-    void SetRemoteManufacturer(ModalityManufacturer manufacturer);
-
-    ModalityManufacturer GetRemoteManufacturer() const
-    {
-      return manufacturer_;
-    }
-
-    void ResetPreferredTransferSyntax();
-
-    void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax);
-
-    const std::string& GetPreferredTransferSyntax() const
-    {
-      return preferredTransferSyntax_;
-    }
-
-    void AddStorageSOPClass(const char* sop);
-
-    void Open()
-    {
-      OpenInternal(Mode_Generic);
-    }
-
-    void Close();
-
-    bool IsOpen() const;
-
-    bool Echo();
-
-    void Store(std::string& sopClassUid /* out */,
-               std::string& sopInstanceUid /* out */,
-               const void* buffer, 
-               size_t size,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-
-    void Store(std::string& sopClassUid /* out */,
-               std::string& sopInstanceUid /* out */,
-               const void* buffer, 
-               size_t size)
-    {
-      Store(sopClassUid, sopInstanceUid, buffer, size, "", 0);  // Not a C-Move
-    }
-
-    void Store(std::string& sopClassUid /* out */,
-               std::string& sopInstanceUid /* out */,
-               const std::string& buffer,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-
-    void Store(std::string& sopClassUid /* out */,
-               std::string& sopInstanceUid /* out */,
-               const std::string& buffer)
-    {
-      Store(sopClassUid, sopInstanceUid, buffer, "", 0);  // Not a C-Move
-    }
-
-    void StoreFile(std::string& sopClassUid /* out */,
-                   std::string& sopInstanceUid /* out */,
-                   const std::string& path,
-                   const std::string& moveOriginatorAET,
-                   uint16_t moveOriginatorID);
-
-    void StoreFile(std::string& sopClassUid /* out */,
-                   std::string& sopInstanceUid /* out */,
-                   const std::string& path)
-    {
-      StoreFile(sopClassUid, sopInstanceUid, path, "", 0);  // Not a C-Move
-    }
-
-    void Find(DicomFindAnswers& result,
-              ResourceType level,
-              const DicomMap& fields,
-              bool normalize);  // Whether to normalize the DICOM query
-
-    void Move(const std::string& targetAet,
-              ResourceType level,
-              const DicomMap& findResult);
-
-    void Move(const std::string& targetAet,
-              const DicomMap& findResult);
-
-    void MovePatient(const std::string& targetAet,
-                     const std::string& patientId);
-
-    void MoveStudy(const std::string& targetAet,
-                   const std::string& studyUid);
-
-    void MoveSeries(const std::string& targetAet,
-                    const std::string& studyUid,
-                    const std::string& seriesUid);
-
-    void MoveInstance(const std::string& targetAet,
-                      const std::string& studyUid,
-                      const std::string& seriesUid,
-                      const std::string& instanceUid);
-
-    void SetTimeout(uint32_t seconds);
-
-    void DisableTimeout();
-
-    void FindWorklist(DicomFindAnswers& result,
-                      ParsedDicomFile& query);
-
-    static void SetDefaultTimeout(uint32_t seconds);
-
-    bool IsSameAssociation(const std::string& localAet,
-                           const RemoteModalityParameters& remote) const;
-
-    void ReportStorageCommitment(
-      const std::string& transactionUid,
-      const std::vector<std::string>& sopClassUids,
-      const std::vector<std::string>& sopInstanceUids,
-      const std::vector<StorageCommitmentFailureReason>& failureReasons);
-
-    // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier()
-    void RequestStorageCommitment(
-      const std::string& transactionUid,
-      const std::vector<std::string>& sopClassUids,
-      const std::vector<std::string>& sopInstanceUids);
-  };
-}
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.cpp	Thu May 07 12:37:36 2020 +0200
@@ -51,6 +51,7 @@
 static const char* KEY_ALLOW_N_ACTION = "AllowNAction";
 static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport";
 static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment";
+static const char* KEY_ALLOW_TRANSCODING = "AllowTranscoding";
 static const char* KEY_HOST = "Host";
 static const char* KEY_MANUFACTURER = "Manufacturer";
 static const char* KEY_PORT = "Port";
@@ -71,6 +72,7 @@
     allowGet_ = true;
     allowNAction_ = true;  // For storage commitment
     allowNEventReport_ = true;  // For storage commitment
+    allowTranscoding_ = true;
   }
 
 
@@ -233,6 +235,11 @@
       allowNAction_ = allow;
       allowNEventReport_ = allow;
     }
+
+    if (serialized.isMember(KEY_ALLOW_TRANSCODING))
+    {
+      allowTranscoding_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_TRANSCODING);
+    }
   }
 
 
@@ -314,7 +321,8 @@
             !allowGet_ ||
             !allowMove_ ||
             !allowNAction_ ||
-            !allowNEventReport_);
+            !allowNEventReport_ ||
+            !allowTranscoding_);
   }
 
   
@@ -336,6 +344,7 @@
       target[KEY_ALLOW_MOVE] = allowMove_;
       target[KEY_ALLOW_N_ACTION] = allowNAction_;
       target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_;
+      target[KEY_ALLOW_TRANSCODING] = allowTranscoding_;
     }
     else
     {
--- a/Core/DicomNetworking/RemoteModalityParameters.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.h	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Thu May 07 12:37:36 2020 +0200
@@ -62,7 +62,7 @@
   }
 
   
-  DicomUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection()
+  DicomStoreUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection()
   {
     if (that_.connection_.get() == NULL)
     {
@@ -87,10 +87,12 @@
   void TimeoutDicomConnectionManager::OpenInternal(const std::string& localAet,
                                                    const RemoteModalityParameters& remote)
   {
+    DicomAssociationParameters other(localAet, remote);
+    
     if (connection_.get() == NULL ||
-        !connection_->IsSameAssociation(localAet, remote))
+        !connection_->GetParameters().IsEqual(other))
     {
-      connection_.reset(new DicomUserConnection(localAet, remote));
+      connection_.reset(new DicomStoreUserConnection(other));
     }
   }
 
@@ -101,7 +103,7 @@
     if (connection_.get() != NULL)
     {
       LOG(INFO) << "Closing inactive DICOM association with modality: "
-                << connection_->GetRemoteApplicationEntityTitle();
+                << connection_->GetParameters().GetRemoteModality().GetApplicationEntityTitle();
 
       connection_.reset(NULL);
     }
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Thu May 07 12:37:36 2020 +0200
@@ -43,7 +43,7 @@
 
 
 #include "../Compatibility.h"
-#include "DicomUserConnection.h"
+#include "DicomStoreUserConnection.h"
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/thread/mutex.hpp>
@@ -56,10 +56,10 @@
   class TimeoutDicomConnectionManager : public boost::noncopyable
   {
   private:
-    boost::mutex                          mutex_;
-    std::unique_ptr<DicomUserConnection>  connection_;
-    boost::posix_time::ptime              lastUse_;
-    boost::posix_time::time_duration      timeout_;
+    boost::mutex                               mutex_;
+    std::unique_ptr<DicomStoreUserConnection>  connection_;
+    boost::posix_time::ptime                   lastUse_;
+    boost::posix_time::time_duration           timeout_;
 
     // Mutex must be locked
     void TouchInternal();
@@ -85,7 +85,7 @@
       
       ~Lock();
 
-      DicomUserConnection& GetConnection();
+      DicomStoreUserConnection& GetConnection();
     };
 
     TimeoutDicomConnectionManager() :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DcmtkTranscoder.cpp	Thu May 07 12:37:36 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 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomParsing/FromDcmtkBridge.cpp	Thu May 07 12:37:36 2020 +0200
@@ -121,6 +121,13 @@
 #endif
 
 
+#include <dcmtk/dcmdata/dcrledrg.h>
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#  include <dcmtk/dcmdata/dcrleerg.h>
+#  include <dcmtk/dcmimage/diregist.h>  // include to support color images
+#endif
+
+
 namespace Orthanc
 {
   static bool IsBinaryTag(const DcmTag& key)
@@ -1198,51 +1205,30 @@
     }
   }
 
-  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmDataset& dataSet)
+
+  
+  static bool SaveToMemoryBufferInternal(std::string& buffer,
+                                         DcmFileFormat& dicom,
+                                         E_TransferSyntax xfer)
   {
-    // 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;
 
-    // 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);  // (*)
+      const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType);  // (*)
       buffer.resize(estimatedSize);
     }
 
     DcmOutputBufferStream ob(&buffer[0], buffer.size());
 
     // 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();
+    dicom.transferInit();
+    OFCondition c = dicom.write(ob, xfer, encodingType, NULL,
+                                /*opt_groupLength*/ EGL_recalcGL,
+                                /*opt_paddingType*/ EPD_noChange,
+                                /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0,
+                                EWM_updateMeta /* creates new SOP instance UID on lossy */);
+    dicom.transferEnd();
 
     if (c.good())
     {
@@ -1265,6 +1251,81 @@
       return false;
     }
   }
+  
+
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmDataset& dataSet)
+  {
+    // Determine the transfer syntax which shall be used to write the
+    // information to the file. If not possible, 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.getCurrentXfer();
+    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;
+    }
+
+    // Create the meta-header information
+    DcmFileFormat ff(&dataSet);
+    ff.validateMetaInfo(xfer);
+    ff.removeInvalidGroups();
+
+    return SaveToMemoryBufferInternal(buffer, ff, xfer);
+  }
+
+
+  bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom,
+                                  DicomTransferSyntax syntax,
+                                  const DcmRepresentationParameter* representation)
+  {
+    E_TransferSyntax xfer;
+    if (!LookupDcmtkTransferSyntax(xfer, syntax))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    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;
+      }
+      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;
+      }
+    }
+  }
 
 
   ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag)
@@ -1659,7 +1720,7 @@
         DcmTag key(tag.GetGroup(), tag.GetElement());
         if (key.getEVR() != EVR_SQ)
         {
-          throw OrthancException(ErrorCode_BadParameterType);
+          throw OrthancException(ErrorCode_BadParameterType, "Bad Parameter type for tag " + tag.Format());
         }
 
         DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key);
@@ -1703,7 +1764,7 @@
       }
 
       default:
-        throw OrthancException(ErrorCode_BadParameterType);
+        throw OrthancException(ErrorCode_BadParameterType, "Bad Parameter type for tag " + tag.Format());
     }
 
     return element.release();
@@ -1721,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;
     }
@@ -1974,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)
@@ -2073,6 +2115,12 @@
     DJEncoderRegistration::registerCodecs();
 # endif
 #endif
+
+    LOG(INFO) << "Registering RLE codecs in DCMTK";
+    DcmRLEDecoderRegistration::registerCodecs(); 
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DcmRLEEncoderRegistration::registerCodecs();
+#endif
   }
 
 
@@ -2093,6 +2141,11 @@
     DJEncoderRegistration::cleanup();
 # endif
 #endif
+
+    DcmRLEDecoderRegistration::cleanup(); 
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DcmRLEEncoderRegistration::cleanup();
+#endif
   }
 
 
@@ -2578,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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomParsing/FromDcmtkBridge.h	Thu May 07 12:37:36 2020 +0200
@@ -205,6 +205,10 @@
     static bool SaveToMemoryBuffer(std::string& buffer,
                                    DcmDataset& dataSet);
 
+    static bool Transcode(DcmFileFormat& dicom,
+                          DicomTransferSyntax syntax,
+                          const DcmRepresentationParameter* representation);
+
     static ValueRepresentation Convert(DcmEVR vr);
 
     static ValueRepresentation LookupValueRepresentation(const DicomTag& tag);
@@ -240,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);
@@ -276,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 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Thu May 07 12:37:36 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 12:37:36 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 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Core/Enumerations.h	Thu May 07 12:37:36 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) */,
@@ -753,7 +753,7 @@
     DicomAssociationRole_Scu,
     DicomAssociationRole_Scp
   };
-  
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
--- a/Core/SerializationToolbox.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Core/SerializationToolbox.cpp	Thu May 07 12:37:36 2020 +0200
@@ -98,6 +98,21 @@
     }
 
 
+    int ReadInteger(const Json::Value& value,
+                    const std::string& field,
+                    int defaultValue)
+    {
+      if (value.isMember(field.c_str()))
+      {
+        return ReadInteger(value, field);
+      }
+      else
+      {
+        return defaultValue;
+      }
+    }
+
+
     unsigned int ReadUnsignedInteger(const Json::Value& value,
                                      const std::string& field)
     {
--- a/Core/SerializationToolbox.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/SerializationToolbox.h	Thu May 07 12:37:36 2020 +0200
@@ -49,6 +49,10 @@
     int ReadInteger(const Json::Value& value,
                     const std::string& field);
 
+    int ReadInteger(const Json::Value& value,
+                    const std::string& field,
+                    int defaultValue);
+
     unsigned int ReadUnsignedInteger(const Json::Value& value,
                                      const std::string& field);
 
--- a/LinuxCompilation.txt	Wed May 06 08:40:48 2020 +0200
+++ b/LinuxCompilation.txt	Thu May 07 12:37:36 2020 +0200
@@ -44,7 +44,7 @@
 directory ("rm -rf ~/OrthancBuild") after installing this package,
 then run cmake again.
 
-Note 3- To build the documentation, you will have to install doxyen.
+Note 3- To build the documentation, you will have to install doxygen.
 
 
 Use system-wide libraries under GNU/Linux
--- a/NEWS	Wed May 06 08:40:48 2020 +0200
+++ b/NEWS	Thu May 07 12:37:36 2020 +0200
@@ -4,9 +4,16 @@
 REST API
 --------
 
+* API version has been upgraded to 7
 * Improved:
   - "/instances/../modify": it is now possible to "Keep" the "SOPInstanceUID".  
     Note that it was already possible to "Replace" it.
+  - added "Timeout" parameter to every DICOM operation
+  - "/queries/.../answers/../retrieve": "TargetAet" not mandatory anymore
+    (defaults to the local AET)
+* Changes:
+  - "/ordered-slices": reverted the change introduced in 1.5.8 and go-back 
+    to 1.5.7 behaviour.
 
 Maintenance
 -----------
--- a/OrthancServer/LuaScripting.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Thu May 07 12:37:36 2020 +0200
@@ -588,7 +588,7 @@
       }
 
       // This is not a C-MOVE: No need to call "StoreScuCommand::SetMoveOriginator()"
-      return lock.AddStoreScuOperation(localAet, modality);
+      return lock.AddStoreScuOperation(context_, localAet, modality);
     }
 
     if (operation == "store-peer")
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Thu May 07 12:37:36 2020 +0200
@@ -59,7 +59,7 @@
       RemoteModalityParameters remote_;
       std::string originatorAet_;
       uint16_t originatorId_;
-      std::unique_ptr<DicomUserConnection> connection_;
+      std::unique_ptr<DicomStoreUserConnection> connection_;
 
     public:
       SynchronousMove(ServerContext& context,
@@ -113,11 +113,15 @@
 
         if (connection_.get() == NULL)
         {
-          connection_.reset(new DicomUserConnection(localAet_, remote_));
+          DicomAssociationParameters params(localAet_, remote_);
+          connection_.reset(new DicomStoreUserConnection(params));
         }
 
         std::string sopClassUid, sopInstanceUid;  // Unused
-        connection_->Store(sopClassUid, sopInstanceUid, dicom, originatorAet_, originatorId_);
+
+        const void* data = dicom.empty() ? NULL : dicom.c_str();
+        connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(),
+                           true, originatorAet_, originatorId_);
 
         return Status_Success;
       }
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu May 07 12:37:36 2020 +0200
@@ -227,7 +227,7 @@
     toStore.SetParsedDicomFile(dicom);
 
     ServerContext& context = OrthancRestApi::GetContext(call);
-    StoreStatus status = context.Store(id, toStore);
+    StoreStatus status = context.Store(id, toStore, StoreInstanceMode_Default);
 
     if (status == StoreStatus_Failure)
     {
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Thu May 07 12:37:36 2020 +0200
@@ -140,7 +140,7 @@
     }    
 
     std::string publicId;
-    StoreStatus status = context.Store(publicId, toStore);
+    StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default);
 
     OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status);
   }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu May 07 12:37:36 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,35 +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);
+    DicomControlUserConnection connection(GetAssociationParameters(call));
 
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
-    try
+    if (connection.Echo())
     {
-      DicomControlUserConnection connection(localAet, remote);
-      
-      if (connection.Echo())
-      {
-        // Echo has succeeded
-        call.GetOutput().AnswerBuffer("{}", MimeType_Json);
-        return;
-      }
+      // 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);
   }
 
 
@@ -188,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);
@@ -197,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);
     }
 
@@ -216,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);
@@ -231,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);
     }
 
@@ -250,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);
@@ -266,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);
     }
 
@@ -285,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);
@@ -302,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);
     }
 
@@ -334,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);
@@ -343,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);
@@ -605,16 +609,25 @@
     ServerContext& context = OrthancRestApi::GetContext(call);
 
     std::string targetAet;
+    int timeout = -1;
     
     Json::Value body;
     if (call.ParseJsonRequest(body))
     {
-      targetAet = SerializationToolbox::ReadString(body, "TargetAet");
+      targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
+      timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1);
     }
     else
     {
       body = Json::objectValue;
-      call.BodyToString(targetAet);
+      if (call.GetBodySize() > 0)
+      {
+        call.BodyToString(targetAet);
+      }
+      else
+      {
+        targetAet = context.GetDefaultLocalApplicationEntityTitle();
+      }
     }
     
     std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
@@ -625,6 +638,12 @@
       job->SetLocalAet(query.GetHandler().GetLocalAet());
       job->SetRemoteModality(query.GetHandler().GetRemoteModality());
 
+      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()
                    << " to target modality " << targetAet;
@@ -948,7 +967,7 @@
     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
@@ -968,6 +987,12 @@
       job->EnableStorageCommitment(true);
     }
 
+    // New in Orthanc 1.7.0
+    if (request.isMember(KEY_TIMEOUT))
+    {
+      job->SetTimeout(SerializationToolbox::ReadUnsignedInteger(request, KEY_TIMEOUT));
+    }
+
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, request);
   }
@@ -975,18 +1000,12 @@
 
   static void DicomStoreStraight(RestApiPostCall& call)
   {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-    RemoteModalityParameters remote =
-      MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
-
-    DicomUserConnection connection(localAet, remote);
-    connection.Open();
+    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;
@@ -1020,15 +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());
+      (request, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
 
     const RemoteModalityParameters source =
       MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-    DicomControlUserConnection connection(localAet, source);
-    
+    DicomAssociationParameters params(localAet, source);
+    InjectAssociationTimeout(params, request);
+
+    DicomControlUserConnection connection(params);
+
     for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
     {
       DicomMap resource;
@@ -1294,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 */));
@@ -1310,7 +1326,7 @@
       DicomFindAnswers answers(true);
 
       {
-        DicomControlUserConnection connection(localAet, remote);
+        DicomControlUserConnection connection(GetAssociationParameters(call, json));
         connection.FindWorklist(answers, *query);
       }
 
--- a/OrthancServer/QueryRetrieveHandler.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Thu May 07 12:37:36 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"
@@ -242,7 +243,9 @@
     isJobsEngineUnserialized_(false),
     metricsRegistry_(new MetricsRegistry),
     isHttpServerSecure_(true),
-    isExecuteLuaEnabled_(false)
+    isExecuteLuaEnabled_(false),
+    overwriteInstances_(false),
+    dcmtkTranscoder_(new DcmtkTranscoder)
   {
     {
       OrthancConfiguration::ReaderLock lock;
@@ -263,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);
@@ -339,8 +345,28 @@
 
 
   StoreStatus ServerContext::Store(std::string& resultPublicId,
-                                   DicomInstanceToStore& dicom)
+                                   DicomInstanceToStore& dicom,
+                                   StoreInstanceMode mode)
   {
+    bool overwrite;
+    switch (mode)
+    {
+      case StoreInstanceMode_Default:
+        overwrite = overwriteInstances_;
+        break;
+        
+      case StoreInstanceMode_OverwriteDuplicate:
+        overwrite = true;
+        break;
+        
+      case StoreInstanceMode_IgnoreDuplicate:
+        overwrite = false;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }    
+    
     try
     {
       MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_store_dicom_duration_ms");
@@ -404,7 +430,8 @@
 
       typedef std::map<MetadataType, std::string>  InstanceMetadata;
       InstanceMetadata  instanceMetadata;
-      StoreStatus status = index_.Store(instanceMetadata, dicom, attachments);
+      StoreStatus status = index_.Store(
+        instanceMetadata, dicom, attachments, overwrite);
 
       // Only keep the metadata for the "instance" level
       dicom.GetMetadata().clear();
@@ -1086,4 +1113,66 @@
 
     return NULL;
   }
+
+
+  void ServerContext::StoreWithTranscoding(std::string& sopClassUid,
+                                           std::string& sopInstanceUid,
+                                           DicomStoreUserConnection& connection,
+                                           const std::string& dicom,
+                                           bool hasMoveOriginator,
+                                           const std::string& moveOriginatorAet,
+                                           uint16_t moveOriginatorId)
+  {
+    const void* data = dicom.empty() ? NULL : dicom.c_str();
+    
+    if (!transcodingEnabled_ ||
+        !connection.GetParameters().GetRemoteModality().IsTranscodingAllowed())
+    {
+      connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(),
+                       hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
+    }
+    else
+    {
+      IDicomTranscoder* transcoder = dcmtkTranscoder_.get();
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+      if (HasPlugins())
+      {
+        transcoder = &GetPlugins();
+      }
+#endif
+
+      if (transcoder == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      else
+      {
+        connection.Transcode(sopClassUid, sopInstanceUid, *transcoder, data, dicom.size(),
+                             hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
+      }
+    }
+  }
+
+
+  bool ServerContext::TranscodeMemoryBuffer(std::string& target,
+                                            bool& hasSopInstanceUidChanged,
+                                            const std::string& source,
+                                            const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                            bool allowNewSopInstanceUid)
+  {
+    const char* data = source.empty() ? NULL : source.c_str();
+    
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins())
+    {
+      return GetPlugins().TranscodeToBuffer(
+        target, hasSopInstanceUidChanged, data, source.size(), allowedSyntaxes, allowNewSopInstanceUid);
+    }
+#endif
+
+    assert(dcmtkTranscoder_.get() != NULL);
+    return dcmtkTranscoder_->TranscodeToBuffer(
+      target, hasSopInstanceUidChanged, data, source.size(), allowedSyntaxes, allowNewSopInstanceUid);
+  }
 }
--- a/OrthancServer/ServerContext.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerContext.h	Thu May 07 12:37:36 2020 +0200
@@ -40,6 +40,7 @@
 #include "ServerJobs/IStorageCommitmentFactory.h"
 
 #include "../Core/Cache/MemoryCache.h"
+#include "../Core/DicomParsing/IDicomTranscoder.h"
 
 
 namespace Orthanc
@@ -221,9 +222,13 @@
     std::unique_ptr<MetricsRegistry>  metricsRegistry_;
     bool isHttpServerSecure_;
     bool isExecuteLuaEnabled_;
+    bool overwriteInstances_;
 
     std::unique_ptr<StorageCommitmentReports>  storageCommitmentReports_;
 
+    bool transcodingEnabled_;
+    std::unique_ptr<IDicomTranscoder>  dcmtkTranscoder_;
+
   public:
     class DicomCacheLocker : public boost::noncopyable
     {
@@ -275,7 +280,8 @@
                        size_t size);
 
     StoreStatus Store(std::string& resultPublicId,
-                      DicomInstanceToStore& dicom);
+                      DicomInstanceToStore& dicom,
+                      StoreInstanceMode mode);
 
     void AnswerAttachment(RestApiOutput& output,
                           const std::string& resourceId,
@@ -426,6 +432,16 @@
       return isExecuteLuaEnabled_;
     }
 
+    void SetOverwriteInstances(bool overwrite)
+    {
+      overwriteInstances_ = overwrite;
+    }
+    
+    bool IsOverwriteInstances() const
+    {
+      return overwriteInstances_;
+    }
+    
     virtual IStorageCommitmentFactory::ILookupHandler*
     CreateStorageCommitment(const std::string& jobId,
                             const std::string& transactionUid,
@@ -438,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/ServerEnumerations.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerEnumerations.h	Thu May 07 12:37:36 2020 +0200
@@ -90,6 +90,13 @@
     FindStorageAccessMode_DiskOnLookupAndAnswer
   };
 
+  enum StoreInstanceMode
+  {
+    StoreInstanceMode_Default,
+    StoreInstanceMode_OverwriteDuplicate,
+    StoreInstanceMode_IgnoreDuplicate
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
--- a/OrthancServer/ServerIndex.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerIndex.cpp	Thu May 07 12:37:36 2020 +0200
@@ -675,7 +675,6 @@
     db_(db),
     maximumStorageSize_(0),
     maximumPatients_(0),
-    overwrite_(false),
     mainDicomTagsRegistry_(new MainDicomTagsRegistry)
   {
     listener_.reset(new Listener(context));
@@ -753,7 +752,8 @@
   
   StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
                                  DicomInstanceToStore& instanceToStore,
-                                 const Attachments& attachments)
+                                 const Attachments& attachments,
+                                 bool overwrite)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
@@ -784,7 +784,7 @@
       {
         // The instance already exists
         
-        if (overwrite_)
+        if (overwrite)
         {
           // Overwrite the old instance
           LOG(INFO) << "Overwriting instance: " << hashInstance;
@@ -1660,12 +1660,6 @@
     StandaloneRecycling();
   }
 
-  void ServerIndex::SetOverwriteInstances(bool overwrite)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-    overwrite_ = overwrite;
-  }
-
 
   void ServerIndex::StandaloneRecycling()
   {
--- a/OrthancServer/ServerIndex.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerIndex.h	Thu May 07 12:37:36 2020 +0200
@@ -71,7 +71,6 @@
 
     uint64_t     maximumStorageSize_;
     unsigned int maximumPatients_;
-    bool         overwrite_;
     std::unique_ptr<MainDicomTagsRegistry>  mainDicomTagsRegistry_;
 
     static void FlushThread(ServerIndex* that,
@@ -139,11 +138,10 @@
     // "count == 0" means no limit on the number of patients
     void SetMaximumPatientCount(unsigned int count);
 
-    void SetOverwriteInstances(bool overwrite);
-
     StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata,
                       DicomInstanceToStore& instance,
-                      const Attachments& attachments);
+                      const Attachments& attachments,
+                      bool overwrite);
 
     void GetGlobalStatistics(/* out */ uint64_t& diskSize,
                              /* out */ uint64_t& uncompressedSize,
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Thu May 07 12:37:36 2020 +0200
@@ -35,6 +35,7 @@
 #include "DicomModalityStoreJob.h"
 
 #include "../../Core/Compatibility.h"
+#include "../../Core/DicomNetworking/DicomAssociation.h"
 #include "../../Core/Logging.h"
 #include "../../Core/SerializationToolbox.h"
 #include "../ServerContext.h"
@@ -47,7 +48,7 @@
   {
     if (connection_.get() == NULL)
     {
-      connection_.reset(new DicomUserConnection(localAet_, remote_));
+      connection_.reset(new DicomStoreUserConnection(parameters_));
     }
   }
 
@@ -58,7 +59,7 @@
     OpenConnection();
 
     LOG(INFO) << "Sending instance " << instance << " to modality \"" 
-              << remote_.GetApplicationEntityTitle() << "\"";
+              << parameters_.GetRemoteModality().GetApplicationEntityTitle() << "\"";
 
     std::string dicom;
 
@@ -73,15 +74,8 @@
     }
     
     std::string sopClassUid, sopInstanceUid;
-
-    if (HasMoveOriginator())
-    {
-      connection_->Store(sopClassUid, sopInstanceUid, dicom, moveOriginatorAet_, moveOriginatorId_);
-    }
-    else
-    {
-      connection_->Store(sopClassUid, sopInstanceUid, dicom);
-    }
+    context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, *connection_, dicom,
+                                  HasMoveOriginator(), moveOriginatorAet_, moveOriginatorId_);
 
     if (storageCommitment_)
     {
@@ -96,7 +90,10 @@
       
       if (sopClassUids_.size() == GetInstancesCount())
       {
-        const std::string& remoteAet = remote_.GetApplicationEntityTitle();
+        assert(IsStarted());
+        connection_.reset(NULL);
+        
+        const std::string& remoteAet = parameters_.GetRemoteModality().GetApplicationEntityTitle();
         
         LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet;
 
@@ -105,12 +102,10 @@
         context_.GetStorageCommitmentReports().Store(
           transactionUid_, new StorageCommitmentReports::Report(remoteAet));
         
-        assert(IsStarted());
-        OpenConnection();
-
         std::vector<std::string> a(sopClassUids_.begin(), sopClassUids_.end());
         std::vector<std::string> b(sopInstanceUids_.begin(), sopInstanceUids_.end());
-        connection_->RequestStorageCommitment(transactionUid_, a, b);
+
+        DicomAssociation::RequestStorageCommitment(parameters_, transactionUid_, a, b);
       }
     }
 
@@ -128,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
   {
@@ -144,7 +138,7 @@
     }
     else
     {
-      localAet_ = aet;
+      parameters_.SetLocalApplicationEntityTitle(aet);
     }
   }
 
@@ -157,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())
@@ -249,8 +256,8 @@
   {
     SetOfInstancesJob::GetPublicContent(value);
     
-    value["LocalAet"] = localAet_;
-    value["RemoteAet"] = remote_.GetApplicationEntityTitle();
+    value["LocalAet"] = parameters_.GetLocalApplicationEntityTitle();
+    value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle();
 
     if (HasMoveOriginator())
     {
@@ -265,8 +272,6 @@
   }
 
 
-  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";
@@ -277,12 +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));
+
+    parameters_ = DicomAssociationParameters::UnserializeJob(serialized);
   }
 
 
@@ -294,8 +299,7 @@
     }
     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_;
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h	Thu May 07 12:37:36 2020 +0200
@@ -35,7 +35,9 @@
 
 #include "../../Core/Compatibility.h"
 #include "../../Core/JobsEngine/SetOfInstancesJob.h"
-#include "../../Core/DicomNetworking/DicomUserConnection.h"
+#include "../../Core/DicomNetworking/DicomStoreUserConnection.h"
+
+#include <list>
 
 namespace Orthanc
 {
@@ -44,13 +46,12 @@
   class DicomModalityStoreJob : public SetOfInstancesJob
   {
   private:
-    ServerContext&                        context_;
-    std::string                           localAet_;
-    RemoteModalityParameters              remote_;
-    std::string                           moveOriginatorAet_;
-    uint16_t                              moveOriginatorId_;
-    std::unique_ptr<DicomUserConnection>  connection_;
-    bool                                  storageCommitment_;
+    ServerContext&                             context_;
+    DicomAssociationParameters                 parameters_;
+    std::string                                moveOriginatorAet_;
+    uint16_t                                   moveOriginatorId_;
+    std::unique_ptr<DicomStoreUserConnection>  connection_;
+    bool                                       storageCommitment_;
 
     // For storage commitment
     std::string             transactionUid_;
@@ -72,19 +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 SetRemoteModality(const RemoteModalityParameters& remote);
+    void SetTimeout(uint32_t seconds);
 
     bool HasMoveOriginator() const
     {
@@ -112,5 +110,10 @@
     virtual void Reset() ORTHANC_OVERRIDE;
 
     void EnableStorageCommitment(bool enabled);
+
+    bool HasStorageCommitment() const
+    {
+      return storageCommitment_;
+    }
   };
 }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp	Thu May 07 12:37:36 2020 +0200
@@ -40,6 +40,7 @@
 static const char* const TARGET_AET = "TargetAet";
 static const char* const REMOTE = "Remote";
 static const char* const QUERY = "Query";
+static const char* const TIMEOUT = "Timeout";
 
 namespace Orthanc
 {
@@ -96,7 +97,7 @@
   {
     if (connection_.get() == NULL)
     {
-      connection_.reset(new DicomControlUserConnection(localAet_, remote_));
+      connection_.reset(new DicomControlUserConnection(parameters_));
     }
     
     connection_->Move(targetAet_, findAnswer);
@@ -152,7 +153,7 @@
     }
     else
     {
-      localAet_ = aet;
+      parameters_.SetLocalApplicationEntityTitle(aet);
     }
   }
 
@@ -178,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);
     }
   }
 
@@ -192,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_;
   }
 
@@ -205,9 +219,8 @@
     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)
@@ -225,10 +238,9 @@
     }
     else
     {
-      target[LOCAL_AET] = localAet_;
+      parameters_.SerializeJob(target);
       target[TARGET_AET] = targetAet_;
       target[QUERY] = query_;
-      remote_.Serialize(target[REMOTE], true /* force advanced format */);
       return true;
     }
   }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h	Thu May 07 12:37:36 2020 +0200
@@ -49,11 +49,10 @@
     class Command;
     class Unserializer;
     
-    ServerContext&            context_;
-    std::string               localAet_;
-    std::string               targetAet_;
-    RemoteModalityParameters  remote_;
-    Json::Value               query_;
+    ServerContext&              context_;
+    DicomAssociationParameters  parameters_;
+    std::string                 targetAet_;
+    Json::Value                 query_;
 
     std::unique_ptr<DicomControlUserConnection>  connection_;
     
@@ -73,28 +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);
-
     virtual void Stop(JobStopReason reason);
 
     virtual void GetJobType(std::string& target)
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Thu May 07 12:37:36 2020 +0200
@@ -200,11 +200,13 @@
   }
 
 
-  size_t LuaJobManager::Lock::AddStoreScuOperation(const std::string& localAet,
+  size_t LuaJobManager::Lock::AddStoreScuOperation(ServerContext& context,
+                                                   const std::string& localAet,
                                                    const RemoteModalityParameters& modality)
   {
     assert(jobLock_.get() != NULL);
-    return jobLock_->AddOperation(new StoreScuOperation(that_.connectionManager_, localAet, modality));    
+    return jobLock_->AddOperation(new StoreScuOperation(
+                                    context, that_.connectionManager_, localAet, modality));    
   }
 
 
--- a/OrthancServer/ServerJobs/LuaJobManager.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.h	Thu May 07 12:37:36 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/MergeStudyJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/MergeStudyJob.cpp	Thu May 07 12:37:36 2020 +0200
@@ -145,7 +145,8 @@
     toStore.SetParsedDicomFile(*modified);
 
     std::string modifiedInstance;
-    if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
+    if (context_.Store(modifiedInstance, toStore,
+                       StoreInstanceMode_Default) != StoreStatus_Success)
     {
       LOG(ERROR) << "Error while storing a modified instance " << instance;
       return false;
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Thu May 07 12:37:36 2020 +0200
@@ -113,7 +113,7 @@
       toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, instance.GetId());
 
       std::string modifiedId;
-      context_.Store(modifiedId, toStore);
+      context_.Store(modifiedId, toStore, StoreInstanceMode_Default);
 
       // Only chain with other commands if this command succeeds
       outputs.Append(new DicomInstanceOperationValue(instance.GetServerContext(), modifiedId));
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Thu May 07 12:37:36 2020 +0200
@@ -35,6 +35,7 @@
 #include "StoreScuOperation.h"
 
 #include "DicomInstanceOperationValue.h"
+#include "../../ServerContext.h"
 
 #include "../../../Core/Logging.h"
 #include "../../../Core/OrthancException.h"
@@ -64,7 +65,8 @@
       instance.ReadDicom(dicom);
 
       std::string sopClassUid, sopInstanceUid;  // Unused
-      lock.GetConnection().Store(sopClassUid, sopInstanceUid, dicom);
+      context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, lock.GetConnection(), dicom,
+                                    false /* Not a C-MOVE */, "", 0);
     }
     catch (OrthancException& e)
     {
@@ -85,8 +87,10 @@
   }
 
 
-  StoreScuOperation::StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+  StoreScuOperation::StoreScuOperation(ServerContext& context,
+                                       TimeoutDicomConnectionManager& connectionManager,
                                        const Json::Value& serialized) :
+    context_(context),
     connectionManager_(connectionManager)
   {
     if (SerializationToolbox::ReadString(serialized, "Type") != "StoreScu" ||
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Thu May 07 12:37:36 2020 +0200
@@ -38,24 +38,30 @@
 
 namespace Orthanc
 {
+  class ServerContext;
+  
   class StoreScuOperation : public IJobOperation
   {
   private:
+    ServerContext&                  context_;
     TimeoutDicomConnectionManager&  connectionManager_;
     std::string                     localAet_;
     RemoteModalityParameters        modality_;
     
   public:
-    StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+    StoreScuOperation(ServerContext& context,
+                      TimeoutDicomConnectionManager& connectionManager,
                       const std::string& localAet,
                       const RemoteModalityParameters& modality) :
+      context_(context),
       connectionManager_(connectionManager),
       localAet_(localAet),
       modality_(modality)
     {
     }
 
-    StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+    StoreScuOperation(ServerContext& context,
+                      TimeoutDicomConnectionManager& connectionManager,
                       const Json::Value& serialized);
 
     const std::string& GetLocalAet() const
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Thu May 07 12:37:36 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/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Thu May 07 12:37:36 2020 +0200
@@ -211,7 +211,8 @@
      **/
 
     std::string modifiedInstance;
-    if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
+    if (context_.Store(modifiedInstance, toStore,
+                       StoreInstanceMode_Default) != StoreStatus_Success)
     {
       throw OrthancException(ErrorCode_CannotStoreInstance,
                              "Error while storing a modified instance " + instance);
--- a/OrthancServer/ServerJobs/SplitStudyJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp	Thu May 07 12:37:36 2020 +0200
@@ -138,7 +138,8 @@
     toStore.SetParsedDicomFile(*modified);
 
     std::string modifiedInstance;
-    if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
+    if (context_.Store(modifiedInstance, toStore,
+                       StoreInstanceMode_Default) != StoreStatus_Success)
     {
       LOG(ERROR) << "Error while storing a modified instance " << instance;
       return false;
--- a/OrthancServer/SliceOrdering.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/SliceOrdering.cpp	Thu May 07 12:37:36 2020 +0200
@@ -320,7 +320,6 @@
     if (instances_.size() <= 1)
     {
       // One single instance: It is sorted by default
-      sortedInstances_ = instances_;
       return true;
     }
 
@@ -329,32 +328,25 @@
       return false;
     }
 
-    sortedInstances_.clear();
-
-    // consider only the instances with a position and correctly oriented (if they have a normal)
     for (size_t i = 0; i < instances_.size(); i++)
     {
       assert(instances_[i] != NULL);
-      if (instances_[i]->HasPosition() &&
-          (!instances_[i]->HasNormal() ||
-           IsParallelOrOpposite(instances_[i]->GetNormal(), normal_)))
+
+      if (!instances_[i]->HasPosition() ||
+          (instances_[i]->HasNormal() &&
+           !IsParallelOrOpposite(instances_[i]->GetNormal(), normal_)))
       {
-        sortedInstances_.push_back(instances_[i]);
+        return false;
       }
     }
 
-    if (sortedInstances_.size() == 0)
-    {
-      return false;
-    }
+    PositionComparator comparator(normal_);
+    std::sort(instances_.begin(), instances_.end(), comparator);
 
-    PositionComparator comparator(normal_);
-    std::sort(sortedInstances_.begin(), sortedInstances_.end(), comparator);
-
-    float a = sortedInstances_[0]->ComputeRelativePosition(normal_);
-    for (size_t i = 1; i < sortedInstances_.size(); i++)
+    float a = instances_[0]->ComputeRelativePosition(normal_);
+    for (size_t i = 1; i < instances_.size(); i++)
     {
-      float b = sortedInstances_[i]->ComputeRelativePosition(normal_);
+      float b = instances_[i]->ComputeRelativePosition(normal_);
 
       if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon())
       {
@@ -376,38 +368,27 @@
     if (instances_.size() <= 1)
     {
       // One single instance: It is sorted by default
-      sortedInstances_ = instances_;
       return true;
     }
 
-    sortedInstances_.clear();
-
-    // consider only the instances with an index
     for (size_t i = 0; i < instances_.size(); i++)
     {
       assert(instances_[i] != NULL);
-      if (instances_[i]->HasIndexInSeries())
+      if (!instances_[i]->HasIndexInSeries())
       {
-        sortedInstances_.push_back(instances_[i]);
+        return false;
       }
     }
 
-    if (sortedInstances_.size() == 0) // if we were not able to sort instances because none of them had an index, return all instances in a "random" order
-    {
-      sortedInstances_ = instances_;
-    }
-    else
+    std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator);
+    
+    for (size_t i = 1; i < instances_.size(); i++)
     {
-      std::sort(sortedInstances_.begin(), sortedInstances_.end(), IndexInSeriesComparator);
-
-      for (size_t i = 1; i < sortedInstances_.size(); i++)
+      if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries())
       {
-        if (sortedInstances_[i - 1]->GetIndexInSeries() == sortedInstances_[i]->GetIndexInSeries())
-        {
-          // The current "IndexInSeries" occurs 2 times: Not a proper ordering
-          LOG(WARNING) << "This series contains 2 slices with the same index, trying to display it anyway";
-          break;
-        }
+        // The current "IndexInSeries" occurs 2 times: Not a proper ordering
+        LOG(WARNING) << "This series contains 2 slices with the same index, trying to display it anyway";
+        break;
       }
     }
 
@@ -446,28 +427,28 @@
   }
 
 
-  const std::string& SliceOrdering::GetSortedInstanceId(size_t index) const
+  const std::string& SliceOrdering::GetInstanceId(size_t index) const
   {
-    if (index >= sortedInstances_.size())
+    if (index >= instances_.size())
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
     else
     {
-      return sortedInstances_[index]->GetIdentifier();
+      return instances_[index]->GetIdentifier();
     }
   }
 
 
-  unsigned int SliceOrdering::GetSortedInstanceFramesCount(size_t index) const
+  unsigned int SliceOrdering::GetFramesCount(size_t index) const
   {
-    if (index >= sortedInstances_.size())
+    if (index >= instances_.size())
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
     else
     {
-      return sortedInstances_[index]->GetFramesCount();
+      return instances_[index]->GetFramesCount();
     }
   }
 
@@ -478,9 +459,9 @@
     result["Type"] = (isVolume_ ? "Volume" : "Sequence");
     
     Json::Value tmp = Json::arrayValue;
-    for (size_t i = 0; i < GetSortedInstancesCount(); i++)
+    for (size_t i = 0; i < GetInstancesCount(); i++)
     {
-      tmp.append(GetBasePath(ResourceType_Instance, GetSortedInstanceId(i)) + "/file");
+      tmp.append(GetBasePath(ResourceType_Instance, GetInstanceId(i)) + "/file");
     }
 
     result["Dicom"] = tmp;
@@ -488,18 +469,18 @@
     Json::Value slicesShort = Json::arrayValue;
 
     tmp.clear();
-    for (size_t i = 0; i < GetSortedInstancesCount(); i++)
+    for (size_t i = 0; i < GetInstancesCount(); i++)
     {
-      std::string base = GetBasePath(ResourceType_Instance, GetSortedInstanceId(i));
-      for (size_t j = 0; j < GetSortedInstanceFramesCount(i); j++)
+      std::string base = GetBasePath(ResourceType_Instance, GetInstanceId(i));
+      for (size_t j = 0; j < GetFramesCount(i); j++)
       {
         tmp.append(base + "/frames/" + boost::lexical_cast<std::string>(j));
       }
 
       Json::Value tmp2 = Json::arrayValue;
-      tmp2.append(GetSortedInstanceId(i));
+      tmp2.append(GetInstanceId(i));
       tmp2.append(0);
-      tmp2.append(GetSortedInstanceFramesCount(i));
+      tmp2.append(GetFramesCount(i));
       
       slicesShort.append(tmp2);
     }
--- a/OrthancServer/SliceOrdering.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/SliceOrdering.h	Thu May 07 12:37:36 2020 +0200
@@ -51,8 +51,7 @@
     std::string              seriesId_;
     bool                     hasNormal_;
     Vector                   normal_;
-    std::vector<Instance*>   instances_;        // this vector owns the instances
-    std::vector<Instance*>   sortedInstances_;  // this vectore references the instances of instances_
+    std::vector<Instance*>   instances_;
     bool                     isVolume_;
 
     static bool ComputeNormal(Vector& normal,
@@ -78,14 +77,14 @@
 
     ~SliceOrdering();
 
-    size_t  GetSortedInstancesCount() const
+    size_t  GetInstancesCount() const
     {
-      return sortedInstances_.size();
+      return instances_.size();
     }
 
-    const std::string& GetSortedInstanceId(size_t index) const;
+    const std::string& GetInstanceId(size_t index) const;
 
-    unsigned int GetSortedInstanceFramesCount(size_t index) const;
+    unsigned int GetFramesCount(size_t index) const;
 
     void Format(Json::Value& result) const;
   };
--- a/OrthancServer/main.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/main.cpp	Thu May 07 12:37:36 2020 +0200
@@ -88,7 +88,7 @@
       toStore.SetJson(dicomJson);
 
       std::string id;
-      context_.Store(id, toStore);
+      context_.Store(id, toStore, StoreInstanceMode_Default);
     }
   }
 };
@@ -1289,7 +1289,6 @@
     
     HttpClient::SetDefaultProxy(lock.GetConfiguration().GetStringParameter("HttpProxy", ""));
     
-    DicomUserConnection::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScuTimeout", 10));
     DicomAssociationParameters::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScuTimeout", 10));
 
     maxCompletedJobs = lock.GetConfiguration().GetUnsignedIntegerParameter("JobsHistorySize", 10);
@@ -1309,7 +1308,7 @@
     context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true));
 
     // New option in Orthanc 1.4.2
-    context.GetIndex().SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false));
+    context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false));
 
     try
     {
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Thu May 07 12:37:36 2020 +0200
@@ -4793,4 +4793,16 @@
     
     return NULL;
   }
+
+
+  bool OrthancPlugins::Transcode(std::string& target,
+                                 bool& hasSopInstanceUidChanged /* out */,
+                                 const void* buffer,
+                                 size_t size,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid)
+  {
+    // TODO
+    return false;
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Wed May 06 08:40:48 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Thu May 07 12:37:36 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/DcmtkConfiguration.cmake	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Thu May 07 12:37:36 2020 +0200
@@ -36,6 +36,14 @@
       )
   endif()
 
+  if (ENABLE_DCMTK_TRANSCODING)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimgle/libsrc DCMTK_SOURCES)
+    AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimage/libsrc DCMTK_SOURCES)
+    include_directories(
+      ${DCMTK_SOURCES_DIR}/dcmimage/include
+      )
+  endif()
+  
   if (ENABLE_DCMTK_JPEG)
     AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES)
     AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES)
@@ -56,17 +64,21 @@
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8/jaricom.c
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12/jaricom.c
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg24/jaricom.c
+      )
 
-      # Disable support for encoding JPEG (modification in Orthanc 1.0.1)
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc
-      ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc
-      )
+    if (NOT ENABLE_DCMTK_TRANSCODING)
+      list(REMOVE_ITEM DCMTK_SOURCES 
+        # Disable support for encoding JPEG (modification in Orthanc 1.0.1)
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc
+        ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc
+        )
+    endif()
   endif()
 
 
@@ -78,15 +90,18 @@
       ${DCMTK_SOURCES_DIR}/dcmjpls/include
       ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls
       )
-    list(REMOVE_ITEM DCMTK_SOURCES 
-      ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc
-
-      # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1)
-      ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc
-      )
     list(APPEND DCMTK_SOURCES 
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc
       )
+
+    if (NOT ENABLE_DCMTK_TRANSCODING)
+      list(REMOVE_ITEM DCMTK_SOURCES 
+        ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc
+
+        # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1)
+        ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc
+        )
+    endif()
   endif()
 
   
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu May 07 12:37:36 2020 +0200
@@ -484,10 +484,9 @@
       ${ORTHANC_ROOT}/Core/DicomNetworking/DicomAssociation.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/DicomAssociationParameters.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/DicomControlUserConnection.cpp
-      ${ORTHANC_ROOT}/Core/DicomNetworking/DicomStoreUserConnection.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/DicomFindAnswers.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/DicomServer.cpp
-      ${ORTHANC_ROOT}/Core/DicomNetworking/DicomUserConnection.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/DicomStoreUserConnection.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp
@@ -502,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/CMake/OrthancFrameworkParameters.cmake	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/CMake/OrthancFrameworkParameters.cmake	Thu May 07 12:37:36 2020 +0200
@@ -17,7 +17,7 @@
 # Version of the Orthanc API, can be retrieved from "/system" URI in
 # order to check whether new URI endpoints are available even if using
 # the mainline version of Orthanc
-set(ORTHANC_API_VERSION "6")
+set(ORTHANC_API_VERSION "7")
 
 
 #####################################################################
--- a/Resources/Configuration.json	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/Configuration.json	Thu May 07 12:37:36 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	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/DicomTransferSyntaxes.json	Thu May 07 12:37:36 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",
--- a/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "CallSystemCommand.h"
-
-#include "../../Core/Logging.h"
-#include "../../Core/Toolbox.h"
-#include "../../Core/TemporaryFile.h"
-
-namespace Orthanc
-{
-  CallSystemCommand::CallSystemCommand(ServerContext& context,
-                                       const std::string& command,
-                                       const std::vector<std::string>& arguments) : 
-    context_(context),
-    command_(command),
-    arguments_(arguments)
-  {
-  }
-
-  bool CallSystemCommand::Apply(ListOfStrings& outputs,
-                                const ListOfStrings& inputs)
-  {
-    for (ListOfStrings::const_iterator
-           it = inputs.begin(); it != inputs.end(); ++it)
-    {
-      LOG(INFO) << "Calling system command " << command_ << " on instance " << *it;
-
-      try
-      {
-        std::string dicom;
-        context_.ReadDicom(dicom, *it);
-
-        TemporaryFile tmp;
-        tmp.Write(dicom);
-
-        std::vector<std::string> args = arguments_;
-        args.push_back(tmp.GetPath());
-
-        SystemToolbox::ExecuteSystemCommand(command_, args);
-
-        // Only chain with other commands if this command succeeds
-        outputs.push_back(*it);
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Unable to call system command " << command_ 
-                   << " on instance " << *it << " in a Lua script: " << e.What();
-      }
-    }
-
-    return true;
-  }
-}
--- a/Resources/Graveyard/OldScheduler/CallSystemCommand.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * 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 "IServerCommand.h"
-#include "../ServerContext.h"
-
-namespace Orthanc
-{
-  class CallSystemCommand : public IServerCommand
-  {
-  private:
-    ServerContext& context_;
-    std::string command_;
-    std::vector<std::string> arguments_;
-
-  public:
-    CallSystemCommand(ServerContext& context,
-                      const std::string& command,
-                      const std::vector<std::string>& arguments);
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs);
-  };
-}
--- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "DeleteInstanceCommand.h"
-
-#include "../../Core/Logging.h"
-
-namespace Orthanc
-{
-  bool DeleteInstanceCommand::Apply(ListOfStrings& outputs,
-                                    const ListOfStrings& inputs)
-  {
-    for (ListOfStrings::const_iterator
-           it = inputs.begin(); it != inputs.end(); ++it)
-    {
-      LOG(INFO) << "Deleting instance " << *it;
-
-      try
-      {
-        Json::Value tmp;
-        context_.DeleteResource(tmp, *it, ResourceType_Instance);
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Unable to delete instance " << *it << ": " << e.What();
-      }
-    }
-
-    return true;
-  }
-}
--- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * 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 "IServerCommand.h"
-#include "../ServerContext.h"
-
-namespace Orthanc
-{
-  class DeleteInstanceCommand : public IServerCommand
-  {
-  private:
-    ServerContext& context_;
-
-  public:
-    DeleteInstanceCommand(ServerContext& context) : context_(context)
-    {
-    }
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs);
-  };
-}
--- a/Resources/Graveyard/OldScheduler/IServerCommand.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * 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 <list>
-#include <string>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class IServerCommand : public boost::noncopyable
-  {
-  public:
-    typedef std::list<std::string>  ListOfStrings;
-
-    virtual ~IServerCommand()
-    {
-    }
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs) = 0;
-  };
-}
--- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "ModifyInstanceCommand.h"
-
-#include "../../Core/Logging.h"
-
-namespace Orthanc
-{
-  ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context,
-                                               RequestOrigin origin,
-                                               DicomModification* modification) :
-    context_(context),
-    origin_(origin),
-    modification_(modification)
-  {
-    modification_->SetAllowManualIdentifiers(true);
-
-    if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID))
-    {
-      modification_->SetLevel(ResourceType_Patient);
-    }
-    else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      modification_->SetLevel(ResourceType_Study);
-    }
-    else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      modification_->SetLevel(ResourceType_Series);
-    }
-    else
-    {
-      modification_->SetLevel(ResourceType_Instance);
-    }
-
-    if (origin_ != RequestOrigin_Lua)
-    {
-      // TODO If issued from HTTP, "remoteIp" and "username" must be provided
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  ModifyInstanceCommand::~ModifyInstanceCommand()
-  {
-    if (modification_)
-    {
-      delete modification_;
-    }
-  }
-
-
-  bool ModifyInstanceCommand::Apply(ListOfStrings& outputs,
-                                    const ListOfStrings& inputs)
-  {
-    for (ListOfStrings::const_iterator
-           it = inputs.begin(); it != inputs.end(); ++it)
-    {
-      LOG(INFO) << "Modifying resource " << *it;
-
-      try
-      {
-        std::auto_ptr<ParsedDicomFile> modified;
-
-        {
-          ServerContext::DicomCacheLocker lock(context_, *it);
-          modified.reset(lock.GetDicom().Clone(true));
-        }
-
-        modification_->Apply(*modified);
-
-        DicomInstanceToStore toStore;
-        assert(origin_ == RequestOrigin_Lua);
-        toStore.SetLuaOrigin();
-        toStore.SetParsedDicomFile(*modified);
-        // TODO other metadata
-        toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it);
-
-        std::string modifiedId;
-        context_.Store(modifiedId, toStore);
-
-        // Only chain with other commands if this command succeeds
-        outputs.push_back(modifiedId);
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Unable to modify instance " << *it << ": " << e.What();
-      }
-    }
-
-    return true;
-  }
-}
--- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * 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 "IServerCommand.h"
-#include "../ServerContext.h"
-#include "../../Core/DicomParsing/DicomModification.h"
-
-namespace Orthanc
-{
-  class ModifyInstanceCommand : public IServerCommand
-  {
-  private:
-    ServerContext& context_;
-    RequestOrigin origin_;
-    DicomModification* modification_;
-
-  public:
-    ModifyInstanceCommand(ServerContext& context,
-                          RequestOrigin origin,
-                          DicomModification* modification);  // takes the ownership
-
-    virtual ~ModifyInstanceCommand();
-
-    const DicomModification& GetModification() const
-    {
-      return *modification_;
-    }
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs);
-  };
-}
--- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/**
- * 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 "ReusableDicomUserConnection.h"
-
-#include "../Logging.h"
-#include "../OrthancException.h"
-
-namespace Orthanc
-{
-  static boost::posix_time::ptime Now()
-  {
-    return boost::posix_time::microsec_clock::local_time();
-  }
-
-  void ReusableDicomUserConnection::Open(const std::string& localAet,
-                                         const RemoteModalityParameters& remote)
-  {
-    if (connection_ != NULL &&
-        connection_->GetLocalApplicationEntityTitle() == localAet &&
-        connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() &&
-        connection_->GetRemoteHost() == remote.GetHost() &&
-        connection_->GetRemotePort() == remote.GetPort() &&
-        connection_->GetRemoteManufacturer() == remote.GetManufacturer())
-    {
-      // The current connection can be reused
-      LOG(INFO) << "Reusing the previous SCU connection";
-      return;
-    }
-
-    Close();
-
-    connection_ = new DicomUserConnection();
-    connection_->SetLocalApplicationEntityTitle(localAet);
-    connection_->SetRemoteModality(remote);
-    connection_->Open();
-  }
-    
-  void ReusableDicomUserConnection::Close()
-  {
-    if (connection_ != NULL)
-    {
-      delete connection_;
-      connection_ = NULL;
-    }
-  }
-
-  void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that)
-  {
-    for (;;)
-    {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-      if (!that->continue_)
-      {
-        //LOG(INFO) << "Finishing the thread watching the global SCU connection";
-        return;
-      }
-
-      {
-        boost::mutex::scoped_lock lock(that->mutex_);
-        if (that->connection_ != NULL &&
-            Now() >= that->lastUse_ + that->timeBeforeClose_)
-        {
-          LOG(INFO) << "Closing the global SCU connection after timeout";
-          that->Close();
-        }
-      }
-    }
-  }
-    
-
-  ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that,
-                                              const std::string& localAet,
-                                              const RemoteModalityParameters& remote) :
-    ::Orthanc::Locker(that)
-  {
-    that.Open(localAet, remote);
-    connection_ = that.connection_;    
-  }
-
-
-  DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection()
-  {
-    if (connection_ == NULL)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    return *connection_;
-  }      
-
-  ReusableDicomUserConnection::ReusableDicomUserConnection() : 
-    connection_(NULL), 
-    timeBeforeClose_(boost::posix_time::seconds(5))  // By default, close connection after 5 seconds
-  {
-    lastUse_ = Now();
-    continue_ = true;
-    closeThread_ = boost::thread(CloseThread, this);
-  }
-
-  ReusableDicomUserConnection::~ReusableDicomUserConnection()
-  {
-    if (continue_)
-    {
-      LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!";
-      Finalize();
-    }
-  }
-
-  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (ms == 0)
-    {
-      ms = 1;
-    }
-
-    timeBeforeClose_ = boost::posix_time::milliseconds(ms);
-  }
-
-  void ReusableDicomUserConnection::Lock()
-  {
-    mutex_.lock();
-  }
-
-  void ReusableDicomUserConnection::Unlock()
-  {
-    if (connection_ != NULL &&
-        connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp)
-    {
-      // "storescp" from DCMTK has problems when reusing a
-      // connection. Always close.
-      Close();
-    }
-
-    lastUse_ = Now();
-    mutex_.unlock();
-  }
-
-  
-  void ReusableDicomUserConnection::Finalize()
-  {
-    if (continue_)
-    {
-      continue_ = false;
-
-      if (closeThread_.joinable())
-      {
-        closeThread_.join();
-      }
-
-      Close();
-    }
-  }
-}
-
--- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/**
- * 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 "DicomUserConnection.h"
-#include "../../Core/MultiThreading/Locker.h"
-
-#include <boost/thread.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-namespace Orthanc
-{
-  class ReusableDicomUserConnection : public ILockable
-  {
-  private:
-    boost::mutex mutex_;
-    DicomUserConnection* connection_;
-    bool continue_;
-    boost::posix_time::time_duration timeBeforeClose_;
-    boost::posix_time::ptime lastUse_;
-    boost::thread closeThread_;
-
-    void Open(const std::string& localAet,
-              const RemoteModalityParameters& remote);
-    
-    void Close();
-
-    static void CloseThread(ReusableDicomUserConnection* that);
-
-  protected:
-    virtual void Lock();
-
-    virtual void Unlock();
-    
-  public:
-    class Locker : public ::Orthanc::Locker
-    {
-    private:
-      DicomUserConnection* connection_;
-
-    public:
-      Locker(ReusableDicomUserConnection& that,
-             const std::string& localAet,
-             const RemoteModalityParameters& remote);
-
-      DicomUserConnection& GetConnection();
-    };
-
-    ReusableDicomUserConnection();
-
-    virtual ~ReusableDicomUserConnection();
-
-    void SetMillisecondsBeforeClose(uint64_t ms);
-
-    void Finalize();
-  };
-}
-
--- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "ServerCommandInstance.h"
-
-#include "../../Core/OrthancException.h"
-
-namespace Orthanc
-{
-  bool ServerCommandInstance::Execute(IListener& listener)
-  {
-    ListOfStrings outputs;
-
-    bool success = false;
-
-    try
-    {
-      if (command_->Apply(outputs, inputs_))
-      {
-        success = true;
-      }
-    }
-    catch (OrthancException&)
-    {
-    }
-
-    if (!success)
-    {
-      listener.SignalFailure(jobId_);
-      return true;
-    }
-
-    for (std::list<ServerCommandInstance*>::iterator
-           it = next_.begin(); it != next_.end(); ++it)
-    {
-      for (ListOfStrings::const_iterator
-             output = outputs.begin(); output != outputs.end(); ++output)
-      {
-        (*it)->AddInput(*output);
-      }
-    }
-
-    listener.SignalSuccess(jobId_);
-    return true;
-  }
-
-
-  ServerCommandInstance::ServerCommandInstance(IServerCommand *command,
-                                               const std::string& jobId) : 
-    command_(command), 
-    jobId_(jobId),
-    connectedToSink_(false)
-  {
-    if (command_ == NULL)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  ServerCommandInstance::~ServerCommandInstance()
-  {
-    if (command_ != NULL)
-    {
-      delete command_;
-    }
-  }
-}
--- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/**
- * 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 "../../Core/IDynamicObject.h"
-#include "IServerCommand.h"
-
-namespace Orthanc
-{
-  class ServerCommandInstance : public IDynamicObject
-  {
-    friend class ServerScheduler;
-
-  public:
-    class IListener
-    {
-    public:
-      virtual ~IListener()
-      {
-      }
-
-      virtual void SignalSuccess(const std::string& jobId) = 0;
-
-      virtual void SignalFailure(const std::string& jobId) = 0;
-    };
-
-  private:
-    typedef IServerCommand::ListOfStrings  ListOfStrings;
-
-    IServerCommand *command_;
-    std::string jobId_;
-    ListOfStrings inputs_;
-    std::list<ServerCommandInstance*> next_;
-    bool connectedToSink_;
-
-    bool Execute(IListener& listener);
-
-  public:
-    ServerCommandInstance(IServerCommand *command,
-                          const std::string& jobId);
-
-    virtual ~ServerCommandInstance();
-
-    const std::string& GetJobId() const
-    {
-      return jobId_;
-    }
-
-    void AddInput(const std::string& input)
-    {
-      inputs_.push_back(input);
-    }
-
-    void ConnectOutput(ServerCommandInstance& next)
-    {
-      next_.push_back(&next);
-    }
-
-    void SetConnectedToSink(bool connected = true)
-    {
-      connectedToSink_ = connected;
-    }
-
-    bool IsConnectedToSink() const
-    {
-      return connectedToSink_;
-    }
-
-    const std::list<ServerCommandInstance*>& GetNextCommands() const
-    {
-      return next_;
-    }
-  };
-}
--- a/Resources/Graveyard/OldScheduler/ServerJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "ServerJob.h"
-
-#include "../../Core/OrthancException.h"
-#include "../../Core/Toolbox.h"
-
-namespace Orthanc
-{
-  void ServerJob::CheckOrdering()
-  {
-    std::map<ServerCommandInstance*, unsigned int> index;
-
-    unsigned int count = 0;
-    for (std::list<ServerCommandInstance*>::const_iterator
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      index[*it] = count++;
-    }
-
-    for (std::list<ServerCommandInstance*>::const_iterator
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands();
-
-      for (std::list<ServerCommandInstance*>::const_iterator
-             next = nextCommands.begin(); next != nextCommands.end(); ++next)
-      {
-        if (index.find(*next) == index.end() ||
-            index[*next] <= index[*it])
-        {
-          // You must reorder your calls to "ServerJob::AddCommand"
-          throw OrthancException(ErrorCode_BadJobOrdering);
-        }
-      }
-    }
-  }
-
-
-  size_t ServerJob::Submit(SharedMessageQueue& target,
-                           ServerCommandInstance::IListener& listener)
-  {
-    if (submitted_)
-    {
-      // This job has already been submitted
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    CheckOrdering();
-
-    size_t size = filters_.size();
-
-    for (std::list<ServerCommandInstance*>::iterator 
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      target.Enqueue(*it);
-    }
-
-    filters_.clear();
-    submitted_ = true;
-
-    return size;
-  }
-
-
-  ServerJob::ServerJob() :
-    jobId_(Toolbox::GenerateUuid()),
-    submitted_(false),
-    description_("no description")
-  {
-  }
-
-
-  ServerJob::~ServerJob()
-  {
-    for (std::list<ServerCommandInstance*>::iterator
-           it = filters_.begin(); it != filters_.end(); ++it)
-    {
-      delete *it;
-    }
-
-    for (std::list<IDynamicObject*>::iterator
-           it = payloads_.begin(); it != payloads_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter)
-  {
-    if (submitted_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    filters_.push_back(new ServerCommandInstance(filter, jobId_));
-      
-    return *filters_.back();
-  }
-
-
-  IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload)
-  {
-    if (submitted_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    payloads_.push_back(payload);
-      
-    return *filters_.back();
-  }
-
-}
--- a/Resources/Graveyard/OldScheduler/ServerJob.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/**
- * 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 "ServerCommandInstance.h"
-#include "../../Core/MultiThreading/SharedMessageQueue.h"
-
-namespace Orthanc
-{
-  class ServerJob
-  {
-    friend class ServerScheduler;
-
-  private:
-    std::list<ServerCommandInstance*> filters_;
-    std::list<IDynamicObject*> payloads_;
-    std::string jobId_;
-    bool submitted_;
-    std::string description_;
-
-    void CheckOrdering();
-
-    size_t Submit(SharedMessageQueue& target,
-                  ServerCommandInstance::IListener& listener);
-
-  public:
-    ServerJob();
-
-    ~ServerJob();
-
-    const std::string& GetId() const
-    {
-      return jobId_;
-    }
-
-    void SetDescription(const std::string& description)
-    {
-      description_ = description;
-    }
-
-    const std::string& GetDescription() const
-    {
-      return description_;
-    }
-
-    ServerCommandInstance& AddCommand(IServerCommand* filter);
-
-    // Take the ownership of a payload to a job. This payload will be
-    // automatically freed when the job succeeds or fails.
-    IDynamicObject& AddPayload(IDynamicObject* payload);
-  };
-}
--- a/Resources/Graveyard/OldScheduler/ServerScheduler.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,359 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "ServerScheduler.h"
-
-#include "../../Core/OrthancException.h"
-#include "../../Core/Logging.h"
-
-namespace Orthanc
-{
-  namespace
-  {
-    // Anonymous namespace to avoid clashes between compilation modules
-    class Sink : public IServerCommand
-    {
-    private:
-      ListOfStrings& target_;
-
-    public:
-      explicit Sink(ListOfStrings& target) : target_(target)
-      {
-      }
-
-      virtual bool Apply(ListOfStrings& outputs,
-                         const ListOfStrings& inputs)
-      {
-        for (ListOfStrings::const_iterator 
-               it = inputs.begin(); it != inputs.end(); ++it)
-        {
-          target_.push_back(*it);
-        }
-
-        return true;
-      }    
-    };
-  }
-
-
-  ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId)
-  {
-    Jobs::iterator info = jobs_.find(jobId);
-
-    if (info == jobs_.end())
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    return info->second;
-  }
-
-
-  void ServerScheduler::SignalSuccess(const std::string& jobId)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    JobInfo& info = GetJobInfo(jobId);
-    info.success_++;
-
-    assert(info.failures_ == 0);
-
-    if (info.success_ >= info.size_)
-    {
-      if (info.watched_)
-      {
-        watchedJobStatus_[jobId] = JobStatus_Success;
-        watchedJobFinished_.notify_all();
-      }
-
-      LOG(INFO) << "Job successfully finished (" << info.description_ << ")";
-      jobs_.erase(jobId);
-
-      availableJob_.Release();
-    }
-  }
-
-
-  void ServerScheduler::SignalFailure(const std::string& jobId)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    JobInfo& info = GetJobInfo(jobId);
-    info.failures_++;
-
-    if (info.success_ + info.failures_ >= info.size_)
-    {
-      if (info.watched_)
-      {
-        watchedJobStatus_[jobId] = JobStatus_Failure;
-        watchedJobFinished_.notify_all();
-      }
-
-      LOG(ERROR) << "Job has failed (" << info.description_ << ")";
-      jobs_.erase(jobId);
-
-      availableJob_.Release();
-    }
-  }
-
-
-  void ServerScheduler::Worker(ServerScheduler* that)
-  {
-    static const int32_t TIMEOUT = 100;
-
-    LOG(WARNING) << "The server scheduler has started";
-
-    while (!that->finish_)
-    {
-      std::auto_ptr<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT));
-      if (object.get() != NULL)
-      {
-        ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*object);
-
-        // Skip the execution of this filter if its parent job has
-        // previously failed.
-        bool jobHasFailed;
-        {
-          boost::mutex::scoped_lock lock(that->mutex_);
-          JobInfo& info = that->GetJobInfo(filter.GetJobId());
-          jobHasFailed = (info.failures_ > 0 || info.cancel_); 
-        }
-
-        if (jobHasFailed)
-        {
-          that->SignalFailure(filter.GetJobId());
-        }
-        else
-        {
-          filter.Execute(*that);
-        }
-      }
-    }
-  }
-
-
-  void ServerScheduler::SubmitInternal(ServerJob& job,
-                                       bool watched)
-  {
-    availableJob_.Acquire();
-
-    boost::mutex::scoped_lock lock(mutex_);
-
-    JobInfo info;
-    info.size_ = job.Submit(queue_, *this);
-    info.cancel_ = false;
-    info.success_ = 0;
-    info.failures_ = 0;
-    info.description_ = job.GetDescription();
-    info.watched_ = watched;
-
-    assert(info.size_ > 0);
-
-    if (watched)
-    {
-      watchedJobStatus_[job.GetId()] = JobStatus_Running;
-    }
-
-    jobs_[job.GetId()] = info;
-
-    LOG(INFO) << "New job submitted (" << job.description_ << ")";
-  }
-
-
-  ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs)
-  {
-    if (maxJobs == 0)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    finish_ = false;
-    worker_ = boost::thread(Worker, this);
-  }
-
-
-  ServerScheduler::~ServerScheduler()
-  {
-    if (!finish_)
-    {
-      LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!";
-      Stop();
-    }
-  }
-
-
-  void ServerScheduler::Stop()
-  {
-    if (!finish_)
-    {
-      finish_ = true;
-
-      if (worker_.joinable())
-      {
-        worker_.join();
-      }
-    }
-  }
-
-
-  void ServerScheduler::Submit(ServerJob& job)
-  {
-    if (job.filters_.empty())
-    {
-      return;
-    }
-
-    SubmitInternal(job, false);
-  }
-
-
-  bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs,
-                                      ServerJob& job)
-  {
-    std::string jobId = job.GetId();
-
-    outputs.clear();
-
-    if (job.filters_.empty())
-    {
-      return true;
-    }
-
-    // Add a sink filter to collect all the results of the filters
-    // that have no next filter.
-    ServerCommandInstance& sink = job.AddCommand(new Sink(outputs));
-
-    for (std::list<ServerCommandInstance*>::iterator
-           it = job.filters_.begin(); it != job.filters_.end(); ++it)
-    {
-      if ((*it) != &sink &&
-          (*it)->IsConnectedToSink())
-      {
-        (*it)->ConnectOutput(sink);
-      }
-    }
-
-    // Submit the job
-    SubmitInternal(job, true);
-
-    // Wait for the job to complete (either success or failure)
-    JobStatus status;
-
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end());
-        
-      while (watchedJobStatus_[jobId] == JobStatus_Running)
-      {
-        watchedJobFinished_.wait(lock);
-      }
-
-      status = watchedJobStatus_[jobId];
-      watchedJobStatus_.erase(jobId);
-    }
-
-    return (status == JobStatus_Success);
-  }
-
-
-  bool ServerScheduler::SubmitAndWait(ServerJob& job)
-  {
-    ListOfStrings ignoredSink;
-    return SubmitAndWait(ignoredSink, job);
-  }
-
-
-  bool ServerScheduler::IsRunning(const std::string& jobId)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-    return jobs_.find(jobId) != jobs_.end();
-  }
-
-
-  void ServerScheduler::Cancel(const std::string& jobId)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    Jobs::iterator job = jobs_.find(jobId);
-
-    if (job != jobs_.end())
-    {
-      job->second.cancel_ = true;
-      LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")";
-    }
-  }
-
-
-  float ServerScheduler::GetProgress(const std::string& jobId) 
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    Jobs::iterator job = jobs_.find(jobId);
-
-    if (job == jobs_.end() || 
-        job->second.size_ == 0  /* should never happen */)
-    {
-      // This job is not running
-      return 1;
-    }
-
-    if (job->second.failures_ != 0)
-    {
-      return 1;
-    }
-
-    if (job->second.size_ == 1)
-    {
-      return static_cast<float>(job->second.success_);
-    }
-
-    return (static_cast<float>(job->second.success_) / 
-            static_cast<float>(job->second.size_ - 1));
-  }
-
-
-  void ServerScheduler::GetListOfJobs(ListOfStrings& jobs)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    jobs.clear();
-
-    for (Jobs::const_iterator 
-           it = jobs_.begin(); it != jobs_.end(); ++it)
-    {
-      jobs.push_back(it->first);
-    }
-  }
-}
--- a/Resources/Graveyard/OldScheduler/ServerScheduler.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-/**
- * 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 "ServerJob.h"
-
-#include "../../Core/MultiThreading/Semaphore.h"
-
-namespace Orthanc
-{
-  class ServerScheduler : public ServerCommandInstance::IListener
-  {
-  private:
-    struct JobInfo
-    {
-      bool watched_;
-      bool cancel_;
-      size_t size_;
-      size_t success_;
-      size_t failures_;
-      std::string description_;
-    };
-
-    enum JobStatus
-    {
-      JobStatus_Running = 1,
-      JobStatus_Success = 2,
-      JobStatus_Failure = 3
-    };
-
-    typedef IServerCommand::ListOfStrings  ListOfStrings;
-    typedef std::map<std::string, JobInfo> Jobs;
-
-    boost::mutex mutex_;
-    boost::condition_variable watchedJobFinished_;
-    Jobs jobs_;
-    SharedMessageQueue queue_;
-    bool finish_;
-    boost::thread worker_;
-    std::map<std::string, JobStatus> watchedJobStatus_;
-    Semaphore availableJob_;
-
-    JobInfo& GetJobInfo(const std::string& jobId);
-
-    virtual void SignalSuccess(const std::string& jobId);
-
-    virtual void SignalFailure(const std::string& jobId);
-
-    static void Worker(ServerScheduler* that);
-
-    void SubmitInternal(ServerJob& job,
-                        bool watched);
-
-  public:
-    explicit ServerScheduler(unsigned int maxjobs);
-
-    ~ServerScheduler();
-
-    void Stop();
-
-    void Submit(ServerJob& job);
-
-    bool SubmitAndWait(ListOfStrings& outputs,
-                       ServerJob& job);
-
-    bool SubmitAndWait(ServerJob& job);
-
-    bool IsRunning(const std::string& jobId);
-
-    void Cancel(const std::string& jobId);
-
-    // Returns a number between 0 and 1
-    float GetProgress(const std::string& jobId);
-
-    bool IsRunning(const ServerJob& job)
-    {
-      return IsRunning(job.GetId());
-    }
-
-    void Cancel(const ServerJob& job) 
-    {
-      Cancel(job.GetId());
-    }
-
-    float GetProgress(const ServerJob& job) 
-    {
-      return GetProgress(job.GetId());
-    }
-
-    void GetListOfJobs(ListOfStrings& jobs);
-  };
-}
--- a/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "StorePeerCommand.h"
-
-#include "../../Core/Logging.h"
-#include "../../Core/HttpClient.h"
-
-namespace Orthanc
-{
-  StorePeerCommand::StorePeerCommand(ServerContext& context,
-                                     const WebServiceParameters& peer,
-                                     bool ignoreExceptions) : 
-    context_(context),
-    peer_(peer),
-    ignoreExceptions_(ignoreExceptions)
-  {
-  }
-
-  bool StorePeerCommand::Apply(ListOfStrings& outputs,
-                               const ListOfStrings& inputs)
-  {
-    // Configure the HTTP client
-    HttpClient client(peer_, "instances");
-    client.SetMethod(HttpMethod_Post);
-
-    for (ListOfStrings::const_iterator
-           it = inputs.begin(); it != inputs.end(); ++it)
-    {
-      LOG(INFO) << "Sending resource " << *it << " to peer \"" 
-                << peer_.GetUrl() << "\"";
-
-      try
-      {
-        context_.ReadDicom(client.GetBody(), *it);
-
-        std::string answer;
-        if (!client.Apply(answer))
-        {
-          LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\"";
-          throw OrthancException(ErrorCode_NetworkProtocol);
-        }
-
-        // Only chain with other commands if this command succeeds
-        outputs.push_back(*it);
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Unable to forward to an Orthanc peer in (instance "
-                   << *it << ", peer " << peer_.GetUrl() << "): " << e.What();
-
-        if (!ignoreExceptions_)
-        {
-          throw;
-        }
-      }
-    }
-
-    return true;
-  }
-}
--- a/Resources/Graveyard/OldScheduler/StorePeerCommand.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * 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 "IServerCommand.h"
-#include "../ServerContext.h"
-#include "../OrthancInitialization.h"
-
-namespace Orthanc
-{
-  class StorePeerCommand : public IServerCommand
-  {
-  private:
-    ServerContext& context_;
-    WebServiceParameters peer_;
-    bool ignoreExceptions_;
-
-  public:
-    StorePeerCommand(ServerContext& context,
-                     const WebServiceParameters& peer,
-                     bool ignoreExceptions);
-    
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs);
-  };
-}
--- a/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/**
- * 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 "../PrecompiledHeadersServer.h"
-#include "StoreScuCommand.h"
-
-#include "../../Core/Logging.h"
-
-namespace Orthanc
-{
-  StoreScuCommand::StoreScuCommand(ServerContext& context,
-                                   const std::string& localAet,
-                                   const RemoteModalityParameters& modality,
-                                   bool ignoreExceptions) : 
-    context_(context),
-    modality_(modality),
-    ignoreExceptions_(ignoreExceptions),
-    localAet_(localAet),
-    moveOriginatorID_(0)
-  {
-  }
-
-
-  void StoreScuCommand::SetMoveOriginator(const std::string& aet,
-                                          uint16_t id)
-  {
-    moveOriginatorAET_ = aet;
-    moveOriginatorID_ = id;
-  }
-
-
-  bool StoreScuCommand::Apply(ListOfStrings& outputs,
-                             const ListOfStrings& inputs)
-  {
-    ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_);
-
-    for (ListOfStrings::const_iterator
-           it = inputs.begin(); it != inputs.end(); ++it)
-    {
-      LOG(INFO) << "Sending resource " << *it << " to modality \"" 
-                << modality_.GetApplicationEntityTitle() << "\"";
-
-      try
-      {
-        std::string dicom;
-        context_.ReadDicom(dicom, *it);
-
-        locker.GetConnection().Store(dicom, moveOriginatorAET_, moveOriginatorID_);
-
-        // Only chain with other commands if this command succeeds
-        outputs.push_back(*it);
-      }
-      catch (OrthancException& e)
-      {
-        // Ignore transmission errors (e.g. if the remote modality is
-        // powered off)
-        LOG(ERROR) << "Unable to forward to a modality in (instance "
-                   << *it << "): " << e.What();
-
-        if (!ignoreExceptions_)
-        {
-          throw;
-        }
-      }
-    }
-
-    return true;
-  }
-}
--- a/Resources/Graveyard/OldScheduler/StoreScuCommand.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/**
- * 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 "IServerCommand.h"
-#include "../ServerContext.h"
-
-namespace Orthanc
-{
-  class StoreScuCommand : public IServerCommand
-  {
-  private:
-    ServerContext& context_;
-    RemoteModalityParameters modality_;
-    bool ignoreExceptions_;
-    std::string localAet_;
-    std::string moveOriginatorAET_;
-    uint16_t moveOriginatorID_;
-
-  public:
-    StoreScuCommand(ServerContext& context,
-                    const std::string& localAet,
-                    const RemoteModalityParameters& modality,
-                    bool ignoreExceptions);
-
-    void SetMoveOriginator(const std::string& aet,
-                           uint16_t id);
-
-    virtual bool Apply(ListOfStrings& outputs,
-                       const ListOfStrings& inputs);
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/TestTranscoding.cpp	Thu May 07 12:37:36 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/UnitTestsSources/FromDcmtkTests.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu May 07 12:37:36 2020 +0200
@@ -1924,349 +1924,97 @@
 
 #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>
-
-
-namespace Orthanc
-{
-  class IDicomTranscoder : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomTranscoder()
-    {
-    }
+#include "../Core/DicomNetworking/DicomStoreUserConnection.h"
+#include "../Core/DicomParsing/DcmtkTranscoder.h"
 
-    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;
-
-    virtual IDicomTranscoder* Transcode(std::set<DicomTransferSyntax> syntaxes,
-                                        bool allowNewSopInstanceUid) = 0;
-  };
-
+TEST(Toto, DISABLED_Transcode3)
+{
+  DicomAssociationParameters p;
+  p.SetRemotePort(2000);
 
-  class DcmtkTranscoder : public IDicomTranscoder
-  {
-  private:
-    std::unique_ptr<DcmFileFormat>    dicom_;
-    std::unique_ptr<DicomFrameIndex>  index_;
-    DicomTransferSyntax               transferSyntax_;
-    std::string                       sopClassUid_;
-    std::string                       sopInstanceUid_;
-
-    void Setup(DcmFileFormat* dicom)
-    {
-      dicom_.reset(dicom);
-      
-      if (dicom == NULL ||
-          dicom_->getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_NullPointer);
-      }
-
-      DcmDataset& dataset = *dicom_->getDataset();
-      index_.reset(new DicomFrameIndex(dataset));
+  DicomStoreUserConnection scu(p);
+  scu.SetCommonClassesProposed(false);
+  scu.SetRetiredBigEndianProposed(true);
 
-      E_TransferSyntax xfer = dataset.getOriginalXfer();
-      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");
-        }
-      }
+  DcmtkTranscoder transcoder;
 
-      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
-      {
-        throw OrthancException(
-          ErrorCode_BadFileFormat,
-          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
-      }
-
-      const char* a = NULL;
-      const char* b = NULL;
+  for (int j = 0; j < 2; j++)
+    for (int i = 0; i <= DicomTransferSyntax_XML; i++)
+    {
+      DicomTransferSyntax a = (DicomTransferSyntax) i;
 
-      if (!dataset.findAndGetString(DCM_SOPClassUID, a).good() ||
-          !dataset.findAndGetString(DCM_SOPInstanceUID, b).good() ||
-          a == NULL ||
-          b == NULL)
+      std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
+                          std::string(GetTransferSyntaxUid(a)) + ".dcm");
+      if (Orthanc::SystemToolbox::IsRegularFile(path))
       {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing SOP class/instance UID in DICOM instance");
-      }
-
-      sopClassUid_.assign(a);
-      sopInstanceUid_.assign(b);
-    }
-    
-  public:
-    DcmtkTranscoder(DcmFileFormat* dicom)  // Takes ownership
-    {
-      Setup(dicom);
-    }
+        printf("\n======= %s\n", GetTransferSyntaxUid(a));
 
-    DcmtkTranscoder(const void* dicom,
-                    size_t size)
-    {
-      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
-    }
-
-    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
-    {
-      return transferSyntax_;
-    }
-
-    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
-    {
-      return sopClassUid_;
-    }
-    
-    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
-    {
-      return sopInstanceUid_;
-    }
+        std::string source;
+        Orthanc::SystemToolbox::ReadFile(source, path);
 
-    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
-    {
-      return index_->GetFramesCount();
-    }
-
-    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
-    {
-#if 1
-      index_->GetRawFrame(target, frame);
-      printf("%d: %d\n", frame, target.size());
-#endif
-
-#if 1
-      assert(dicom_->getDataset() != NULL);
-      DcmDataset& dataset = *dicom_->getDataset();
-      
-      DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
-
-      if (pixelSequence != NULL &&
-          frame == 0 &&
-          pixelSequence->card() != GetFramesCount() + 1)
-      {
-        printf("COMPRESSED\n");
-        
-        // Check out "djcodecd.cc"
-        
-        printf("%d fragments\n", pixelSequence->card());
-        
-        // Skip the first fragment, that is the offset table
-        for (unsigned long i = 1; ;i++)
+        std::string c, i;
+        try
         {
-          DcmPixelItem *fragment = NULL;
-          if (pixelSequence->getItem(fragment, i).good())
+          scu.Transcode(c, i, transcoder, source.c_str(), source.size(), false, "", 0);
+        }
+        catch (OrthancException& e)
+        {
+          if (e.GetErrorCode() == ErrorCode_NotImplemented)
           {
-            printf("fragment %d %d\n", i, fragment->getLength());
+            LOG(ERROR) << "cannot transcode " << GetTransferSyntaxUid(a);
           }
           else
           {
-            break;
+            throw e;
           }
         }
       }
-#endif
     }
-
-    virtual IDicomTranscoder* Transcode(std::set<DicomTransferSyntax> syntaxes,
-                                        bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  };
 }
 
 
-
-
-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;
-
-  // 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());
-
-  // 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();
-
-  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
-  {
-    // Error
-    buffer.clear();
-    return false;
-  }
-}
+  std::string source;
+  Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
 
-#include "dcmtk/dcmjpeg/djrploss.h"  /* for DJ_RPLossy */
-#include "dcmtk/dcmjpeg/djrplol.h"   /* for DJ_RPLossless */
-
-#include <boost/filesystem.hpp>
-
-
-static void TestFile(const std::string& path)
-{
-  printf("** %s\n", path.c_str());
-
-  std::string s;
-  SystemToolbox::ReadFile(s, path);
-
-  Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size());
+  std::unique_ptr<DcmFileFormat> toto(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size()));
+  DicomTransferSyntax sourceSyntax;
+  ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
 
-  printf("[%s] [%s] [%s] %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
-         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
-         transcoder.GetFramesCount());
-
-  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
-  {
-    std::string f;
-    transcoder.GetCompressedFrame(f, i);
-
-    if (i == 0)
-    {
-      static unsigned int i = 0;
-      char buf[1024];
-      sprintf(buf, "/tmp/frame-%06d.dcm", i++);
-      printf(">> %s\n", buf);
-      Orthanc::SystemToolbox::WriteFile(f, buf);
-    }
-  }
+  DcmtkTranscoder transcoder;
 
-  printf("\n");
-}
-
-TEST(Toto, Transcode)
-{
-  if (0)
+  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
   {
-    OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
-
-    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");
-
-    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 1
-    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));
+    DicomTransferSyntax a = (DicomTransferSyntax) i;
+    std::set<DicomTransferSyntax> s;
+    s.insert(a);
 
     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)
+    bool hasSopInstanceUidChanged;
+                                   
+    if (!transcoder.TranscodeToBuffer(t, hasSopInstanceUidChanged, source.c_str(), source.size(), s, true))
+    {
+      printf("**************** CANNOT: [%s] => [%s]\n",
+             GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
+    }
+    else
     {
-      if (boost::filesystem::is_regular_file(it->status()))
+      bool lossy = (a == DicomTransferSyntax_JPEGProcess1 ||
+                    a == DicomTransferSyntax_JPEGProcess2_4 ||
+                    a == DicomTransferSyntax_JPEGLSLossy);
+      
+      printf("SIZE: %lu\n", t.size());
+      if (hasSopInstanceUidChanged)
       {
-        TestFile(it->path().string());
+        ASSERT_TRUE(lossy);
+      }
+      else
+      {
+        ASSERT_FALSE(lossy);
       }
     }
-
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
-    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
   }
 }
 
--- a/UnitTestsSources/MultiThreadingTests.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Thu May 07 12:37:36 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"
@@ -1314,7 +1315,7 @@
       DicomInstanceToStore toStore;
       toStore.SetParsedDicomFile(dicom);
 
-      return (context_->Store(id, toStore) == StoreStatus_Success);
+      return (context_->Store(id, toStore, StoreInstanceMode_Default) == StoreStatus_Success);
     }
   };
 }
@@ -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/ServerIndexTests.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/UnitTestsSources/ServerIndexTests.cpp	Thu May 07 12:37:36 2020 +0200
@@ -726,7 +726,8 @@
     std::map<MetadataType, std::string> instanceMetadata;
     DicomInstanceToStore toStore;
     toStore.SetSummary(instance);
-    ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments));
+    ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments,
+                                               false /* don't overwrite */));
     ASSERT_EQ(5u, instanceMetadata.size());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_RemoteAet) != instanceMetadata.end());
     ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end());
@@ -803,7 +804,7 @@
 
     DicomInstanceHasher hasher(instance);
     std::string id = hasher.HashInstance();
-    context.GetIndex().SetOverwriteInstances(overwrite);
+    context.SetOverwriteInstances(overwrite);
 
     uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances;
     context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients, 
@@ -819,7 +820,7 @@
       ASSERT_EQ(id, toStore.GetHasher().HashInstance());
 
       std::string id2;
-      ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore));
+      ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore, StoreInstanceMode_Default));
       ASSERT_EQ(id, id2);
     }
 
@@ -854,7 +855,8 @@
       toStore.SetOrigin(DicomInstanceOrigin::FromPlugins());
 
       std::string id2;
-      ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, context.Store(id2, toStore));
+      ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored,
+                context.Store(id2, toStore, StoreInstanceMode_Default));
       ASSERT_EQ(id, id2);
     }