changeset 3956:6e14f2da7c7e

integration transcoding->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 May 2020 16:42:44 +0200
parents fe0e4ef52a72 (current diff) 5fe8c6d3212e (diff)
children 596912ebab5f 4d36d6e64c6d
files Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h OrthancServer/DefaultDicomImageDecoder.h Plugins/Samples/GdcmDecoder/CMakeLists.txt Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h Plugins/Samples/GdcmDecoder/Plugin.cpp Plugins/Samples/GdcmDecoder/README Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Resources/Graveyard/OldScheduler/CallSystemCommand.h Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Resources/Graveyard/OldScheduler/IServerCommand.h Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Resources/Graveyard/OldScheduler/ServerCommandInstance.h Resources/Graveyard/OldScheduler/ServerJob.cpp Resources/Graveyard/OldScheduler/ServerJob.h Resources/Graveyard/OldScheduler/ServerScheduler.cpp Resources/Graveyard/OldScheduler/ServerScheduler.h Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Resources/Graveyard/OldScheduler/StorePeerCommand.h Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Resources/Graveyard/OldScheduler/StoreScuCommand.h
diffstat 125 files changed, 6327 insertions(+), 6397 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed May 06 08:40:48 2020 +0200
+++ b/.hgignore	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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)
 
 
@@ -92,6 +90,7 @@
   OrthancServer/ServerEnumerations.cpp
   OrthancServer/ServerIndex.cpp
   OrthancServer/ServerJobs/ArchiveJob.cpp
+  OrthancServer/ServerJobs/CleaningInstancesJob.cpp
   OrthancServer/ServerJobs/DicomModalityStoreJob.cpp
   OrthancServer/ServerJobs/DicomMoveScuJob.cpp
   OrthancServer/ServerJobs/LuaJobManager.cpp
--- a/Core/Compression/HierarchicalZipWriter.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/Compression/HierarchicalZipWriter.h	Wed May 20 16:42:44 2020 +0200
@@ -140,7 +140,7 @@
       return indexer_.GetCurrentDirectoryPath();
     }
 
-    void Write(const char* data, size_t length)
+    void Write(const void* data, size_t length)
     {
       writer_.Write(data, length);
     }
--- a/Core/Compression/ZipWriter.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Core/Compression/ZipWriter.cpp	Wed May 20 16:42:44 2020 +0200
@@ -227,7 +227,7 @@
   }
 
 
-  void ZipWriter::Write(const char* data, size_t length)
+  void ZipWriter::Write(const void* data, size_t length)
   {
     if (!hasFileInZip_)
     {
@@ -236,17 +236,19 @@
 
     const size_t maxBytesInAStep = std::numeric_limits<int32_t>::max();
 
+    const char* p = reinterpret_cast<const char*>(data);
+    
     while (length > 0)
     {
       int bytes = static_cast<int32_t>(length <= maxBytesInAStep ? length : maxBytesInAStep);
 
-      if (zipWriteInFileInZip(pimpl_->file_, data, bytes))
+      if (zipWriteInFileInZip(pimpl_->file_, p, bytes))
       {
         throw OrthancException(ErrorCode_CannotWriteFile,
                                "Cannot write data to ZIP archive: " + path_);
       }
       
-      data += bytes;
+      p += bytes;
       length -= bytes;
     }
   }
--- a/Core/Compression/ZipWriter.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/Compression/ZipWriter.h	Wed May 20 16:42:44 2020 +0200
@@ -101,7 +101,7 @@
 
     void OpenFile(const char* path);
 
-    void Write(const char* data, size_t length);
+    void Write(const void* data, size_t length);
 
     void Write(const std::string& data);
   };
--- a/Core/DicomNetworking/DicomAssociation.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomAssociation.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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,127 @@
 
   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);
+      }
+
+      IDicomTranscoder::DicomImage source;
+      source.AcquireParsed(dicom.release());
+      source.SetExternalBuffer(buffer, size);
+
+      const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed());
+      
+      IDicomTranscoder::DicomImage transcoded;
+      if (transcoder.Transcode(transcoded, source, uncompressedSyntaxes, false))
+      {
+        if (sourceUid != IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed()))
+        {
+          throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
+                                 "instance UID while transcoding to an uncompressed transfer syntax");
+        }
+        else
+        {
+          DicomTransferSyntax transcodedSyntax;
+          
+          // Sanity check
+          if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) ||
+              accepted.find(transcodedSyntax) == accepted.end())
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+          else
+          {
+            Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(),
+                  hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
+          }
+        }
+      }
+    }
   }
 }
--- a/Core/DicomNetworking/DicomStoreUserConnection.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.h	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,330 @@
+/**
+ * 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 bool GetBitsStored(uint16_t& bitsStored,
+                            DcmDataset& dataset)
+  {
+    return dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good();
+  }
+
+  
+  void DcmtkTranscoder::SetLossyQuality(unsigned int quality)
+  {
+    if (quality <= 0 ||
+        quality > 100)
+    {
+      throw OrthancException(
+        ErrorCode_ParameterOutOfRange,
+        "The quality for lossy transcoding must be an integer between 1 and 100, received: " +
+        boost::lexical_cast<std::string>(quality));
+    }
+    else
+    {
+      LOG(INFO) << "Quality for lossy transcoding using DCMTK is set to: " << quality;
+      lossyQuality_ = quality;
+    }
+  }
+
+    
+  bool DcmtkTranscoder::InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */,
+                                         DcmFileFormat& dicom,
+                                         const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                         bool allowNewSopInstanceUid) 
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    DicomTransferSyntax syntax;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Cannot determine the transfer syntax");
+    }
+
+    uint16_t bitsStored;
+    bool hasBitsStored = GetBitsStored(bitsStored, *dicom.getDataset());
+    
+    std::string sourceSopInstanceUid = IDicomTranscoder::GetSopInstanceUid(dicom);
+    
+    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))
+    {
+      selectedSyntax = DicomTransferSyntax_LittleEndianImplicit;
+      return true;
+    }
+
+    if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
+    {
+      selectedSyntax = DicomTransferSyntax_LittleEndianExplicit;
+      return true;
+    }
+      
+    if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
+    {
+      selectedSyntax = DicomTransferSyntax_BigEndianExplicit;
+      return true;
+    }
+
+    if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
+    {
+      selectedSyntax = DicomTransferSyntax_DeflatedLittleEndianExplicit;
+      return true;
+    }
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() &&
+        allowNewSopInstanceUid &&
+        (!hasBitsStored || bitsStored == 8))
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossy parameters(lossyQuality_);
+        
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, &parameters))
+      {
+        selectedSyntax = DicomTransferSyntax_JPEGProcess1;
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() &&
+        allowNewSopInstanceUid &&
+        (!hasBitsStored || bitsStored <= 12))
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossy parameters(lossyQuality_);
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, &parameters))
+      {
+        selectedSyntax = DicomTransferSyntax_JPEGProcess2_4;
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossless parameters(6 /* opt_selection_value */,
+                               0 /* opt_point_transform */);
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14, &parameters))
+      {
+        selectedSyntax = DicomTransferSyntax_JPEGProcess14;
+        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))
+      {
+        selectedSyntax = DicomTransferSyntax_JPEGProcess14SV1;
+        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))
+      {
+        selectedSyntax = DicomTransferSyntax_JPEGLSLossless;
+        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))
+      {
+        selectedSyntax = DicomTransferSyntax_JPEGLSLossy;
+        return true;
+      }
+    }
+#endif
+
+    return false;
+  }
+
+    
+  bool DcmtkTranscoder::IsSupported(DicomTransferSyntax syntax)
+  {
+    if (syntax == DicomTransferSyntax_LittleEndianImplicit ||
+        syntax == DicomTransferSyntax_LittleEndianExplicit ||
+        syntax == DicomTransferSyntax_BigEndianExplicit ||
+        syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit)
+    {
+      return true;
+    }
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (syntax == DicomTransferSyntax_JPEGProcess1 ||
+        syntax == DicomTransferSyntax_JPEGProcess2_4 ||
+        syntax == DicomTransferSyntax_JPEGProcess14 ||
+        syntax == DicomTransferSyntax_JPEGProcess14SV1)
+    {
+      return true;
+    }
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    if (syntax == DicomTransferSyntax_JPEGLSLossless ||
+        syntax == DicomTransferSyntax_JPEGLSLossy)
+    {
+      return true;
+    }
+#endif
+    
+    return false;
+  }
+
+
+  bool DcmtkTranscoder::Transcode(DicomImage& target,
+                                  DicomImage& source /* in, "GetParsed()" possibly modified */,
+                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                  bool allowNewSopInstanceUid)
+  {
+    target.Clear();
+    
+    DicomTransferSyntax sourceSyntax;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, source.GetParsed()))
+    {
+      LOG(ERROR) << "Unsupport transfer syntax for transcoding";
+      return false;
+    }
+
+#if !defined(NDEBUG)
+    const std::string sourceSopInstanceUid = GetSopInstanceUid(source.GetParsed());
+#endif
+
+    DicomTransferSyntax targetSyntax;
+    if (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end())
+    {
+      // No transcoding is needed
+      target.AcquireParsed(source);
+      target.AcquireBuffer(source);
+      return true;
+    }
+    else if (InplaceTranscode(targetSyntax, source.GetParsed(),
+                              allowedSyntaxes, allowNewSopInstanceUid))
+    {   
+      // Sanity check
+      DicomTransferSyntax targetSyntax2;
+      if (FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax2, source.GetParsed()) &&
+          targetSyntax == targetSyntax2 &&
+          allowedSyntaxes.find(targetSyntax2) != allowedSyntaxes.end())
+      {
+        target.AcquireParsed(source);
+        source.Clear();
+        
+#if !defined(NDEBUG)
+        // Only run the sanity check in debug mode
+        CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid,
+                         allowedSyntaxes, allowNewSopInstanceUid);
+#endif
+        
+        return true;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }  
+    }
+    else
+    {
+      // Cannot transcode
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DcmtkTranscoder.h	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,78 @@
+/**
+ * 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_;
+    
+    bool InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */,
+                          DcmFileFormat& dicom,
+                          const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                          bool allowNewSopInstanceUid);
+    
+  public:
+    DcmtkTranscoder() :
+      lossyQuality_(90)
+    {
+    }
+
+    void SetLossyQuality(unsigned int quality);
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+    
+    static bool IsSupported(DicomTransferSyntax syntax);
+
+    virtual bool Transcode(DicomImage& target,
+                           DicomImage& source /* in, "GetParsed()" possibly modified */,
+                           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	Wed May 20 16:42:44 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.chooseRepresentation(xfer, representation).good() ||
+          !dicom.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();
@@ -1719,9 +1780,13 @@
     }
 
     DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
+
+    E_TransferSyntax repType;
+    const DcmRepresentationParameter *repParam = NULL;
+    pixelData.getCurrentRepresentationKey(repType, repParam);
+    
     DcmPixelSequence* pixelSequence = NULL;
-    if (!pixelData.getEncapsulatedRepresentation
-        (dataset.getOriginalXfer(), NULL, pixelSequence).good())
+    if (!pixelData.getEncapsulatedRepresentation(repType, repParam, pixelSequence).good())
     {
       return NULL;
     }
@@ -1974,25 +2039,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 +2119,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 +2145,11 @@
     DJEncoderRegistration::cleanup();
 # endif
 #endif
+
+    DcmRLEDecoderRegistration::cleanup(); 
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DcmRLEEncoderRegistration::cleanup();
+#endif
   }
 
 
@@ -2578,6 +2635,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	Wed May 20 16:42:44 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.cpp	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,438 @@
+/**
+ * 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 "IDicomTranscoder.h"
+
+#include "../OrthancException.h"
+#include "FromDcmtkBridge.h"
+#include "ParsedDicomFile.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+
+namespace Orthanc
+{
+  IDicomTranscoder::TranscodingType IDicomTranscoder::GetTranscodingType(DicomTransferSyntax target,
+                                                                         DicomTransferSyntax source)
+  {
+    if (target == source)
+    {
+      return TranscodingType_Lossless;
+    }
+    else if (target == DicomTransferSyntax_LittleEndianImplicit ||
+             target == DicomTransferSyntax_LittleEndianExplicit ||
+             target == DicomTransferSyntax_BigEndianExplicit ||
+             target == DicomTransferSyntax_DeflatedLittleEndianExplicit ||
+             target == DicomTransferSyntax_JPEGProcess14 ||
+             target == DicomTransferSyntax_JPEGProcess14SV1 ||
+             target == DicomTransferSyntax_JPEGLSLossless ||
+             target == DicomTransferSyntax_JPEG2000LosslessOnly ||
+             target == DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly)
+    {
+      return TranscodingType_Lossless;
+    }
+    else if (target == DicomTransferSyntax_JPEGProcess1 ||
+             target == DicomTransferSyntax_JPEGProcess2_4 ||
+             target == DicomTransferSyntax_JPEGLSLossy ||
+             target == DicomTransferSyntax_JPEG2000 ||
+             target == DicomTransferSyntax_JPEG2000Multicomponent)
+    {
+      return TranscodingType_Lossy;
+    }
+    else
+    {
+      return TranscodingType_Unknown;
+    }
+  }
+
+
+  std::string IDicomTranscoder::GetSopInstanceUid(DcmFileFormat& dicom)
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    DcmDataset& dataset = *dicom.getDataset();
+    
+    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");
+    }
+  }
+
+
+  void IDicomTranscoder::CheckTranscoding(IDicomTranscoder::DicomImage& transcoded,
+                                          DicomTransferSyntax sourceSyntax,
+                                          const std::string& sourceSopInstanceUid,
+                                          const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                          bool allowNewSopInstanceUid)
+  {
+    DcmFileFormat& parsed = transcoded.GetParsed();
+    
+    if (parsed.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    std::string targetSopInstanceUid = GetSopInstanceUid(parsed);
+
+    if (parsed.getDataset()->tagExists(DCM_PixelData))
+    {
+      if (!allowNewSopInstanceUid && (targetSopInstanceUid != sourceSopInstanceUid))
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+    else
+    {
+      if (targetSopInstanceUid != sourceSopInstanceUid)
+      {
+        throw OrthancException(ErrorCode_InternalError,
+                               "No pixel data: Transcoding must not change the SOP instance UID");
+      }
+    }
+
+    DicomTransferSyntax targetSyntax;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, parsed))
+    {
+      return;  // Unknown transfer syntax, cannot do further test
+    }
+
+    if (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end())
+    {
+      // No transcoding should have happened
+      if (targetSopInstanceUid != sourceSopInstanceUid)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+        
+    if (allowedSyntaxes.find(targetSyntax) == allowedSyntaxes.end())
+    {
+      throw OrthancException(ErrorCode_InternalError, "An incorrect output transfer syntax was chosen");
+    }
+    
+    if (parsed.getDataset()->tagExists(DCM_PixelData))
+    {
+      switch (GetTranscodingType(targetSyntax, sourceSyntax))
+      {
+        case TranscodingType_Lossy:
+          if (targetSopInstanceUid == sourceSopInstanceUid)
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+          break;
+
+        case TranscodingType_Lossless:
+          if (targetSopInstanceUid != sourceSopInstanceUid)
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+          break;
+
+        default:
+          break;
+      }
+    }
+  }
+    
+
+  void IDicomTranscoder::DicomImage::Parse()
+  {
+    if (parsed_.get() != NULL)
+    {
+      // Already parsed
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (buffer_.get() != NULL)
+    {
+      if (isExternalBuffer_)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      else
+      {
+        parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(
+                        buffer_->empty() ? NULL : buffer_->c_str(), buffer_->size()));
+        
+        if (parsed_.get() == NULL)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat);
+        }      
+      }
+    }
+    else if (isExternalBuffer_)
+    {
+      parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(externalBuffer_, externalSize_));
+      
+      if (parsed_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }      
+    }
+    else
+    {
+      // No buffer is available
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+  
+  void IDicomTranscoder::DicomImage::Serialize()
+  {
+    if (parsed_.get() == NULL ||
+        buffer_.get() != NULL ||
+        isExternalBuffer_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (parsed_->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      buffer_.reset(new std::string);
+      FromDcmtkBridge::SaveToMemoryBuffer(*buffer_, *parsed_->getDataset());
+    }
+  }
+
+  
+  IDicomTranscoder::DicomImage::DicomImage() :
+    isExternalBuffer_(false)
+  {
+  }
+
+
+  void IDicomTranscoder::DicomImage::Clear()
+  {
+    parsed_.reset(NULL);
+    buffer_.reset(NULL);
+    isExternalBuffer_ = false;
+  }
+
+  
+  void IDicomTranscoder::DicomImage::AcquireParsed(ParsedDicomFile& parsed)
+  {
+    AcquireParsed(parsed.ReleaseDcmtkObject());
+  }
+  
+      
+  void IDicomTranscoder::DicomImage::AcquireParsed(DcmFileFormat* parsed)
+  {
+    if (parsed == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    else if (parsed->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else if (parsed_.get() != NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parsed_.reset(parsed);
+    }
+  }
+  
+
+  void IDicomTranscoder::DicomImage::AcquireParsed(DicomImage& other)
+  {
+    AcquireParsed(other.ReleaseParsed());
+  }
+  
+
+  void IDicomTranscoder::DicomImage::AcquireBuffer(std::string& buffer /* will be swapped */)
+  {
+    if (buffer_.get() != NULL ||
+        isExternalBuffer_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      buffer_.reset(new std::string);
+      buffer_->swap(buffer);
+    }
+  }
+
+
+  void IDicomTranscoder::DicomImage::AcquireBuffer(DicomImage& other)
+  {
+    if (buffer_.get() != NULL ||
+        isExternalBuffer_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (other.isExternalBuffer_)
+    {
+      assert(other.buffer_.get() == NULL);
+      isExternalBuffer_ = true;
+      externalBuffer_ = other.externalBuffer_;
+      externalSize_ = other.externalSize_;
+    }
+    else if (other.buffer_.get() != NULL)
+    {
+      buffer_.reset(other.buffer_.release());
+    }
+    else
+    {
+      buffer_.reset(NULL);
+    }    
+  }
+
+  
+  void IDicomTranscoder::DicomImage::SetExternalBuffer(const void* buffer,
+                                                       size_t size)
+  {
+    if (buffer_.get() != NULL ||
+        isExternalBuffer_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      isExternalBuffer_ = true;
+      externalBuffer_ = buffer;
+      externalSize_ = size;
+    }
+  }
+
+
+  void IDicomTranscoder::DicomImage::SetExternalBuffer(const std::string& buffer)
+  {
+    SetExternalBuffer(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
+  }
+
+
+  DcmFileFormat& IDicomTranscoder::DicomImage::GetParsed()
+  {
+    if (parsed_.get() != NULL)
+    {
+      return *parsed_;
+    }
+    else if (buffer_.get() != NULL ||
+             isExternalBuffer_)
+    {
+      Parse();
+      return *parsed_;
+    }
+    else
+    {
+      throw OrthancException(
+        ErrorCode_BadSequenceOfCalls,
+        "AcquireParsed(), AcquireBuffer() or SetExternalBuffer() should have been called");
+    }
+  }
+  
+
+  DcmFileFormat* IDicomTranscoder::DicomImage::ReleaseParsed()
+  {
+    if (parsed_.get() != NULL)
+    {
+      buffer_.reset(NULL);
+      return parsed_.release();
+    }
+    else if (buffer_.get() != NULL ||
+             isExternalBuffer_)
+    {
+      Parse();
+      buffer_.reset(NULL);
+      return parsed_.release();
+    }
+    else
+    {
+      throw OrthancException(
+        ErrorCode_BadSequenceOfCalls,
+        "AcquireParsed(), AcquireBuffer() or SetExternalBuffer() should have been called");
+    }
+  }
+
+
+  ParsedDicomFile* IDicomTranscoder::DicomImage::ReleaseAsParsedDicomFile()
+  {
+    return ParsedDicomFile::AcquireDcmtkObject(ReleaseParsed());
+  }
+
+  
+  const void* IDicomTranscoder::DicomImage::GetBufferData()
+  {
+    if (isExternalBuffer_)
+    {
+      assert(buffer_.get() == NULL);
+      return externalBuffer_;
+    }
+    else
+    {    
+      if (buffer_.get() == NULL)
+      {
+        Serialize();
+      }
+
+      assert(buffer_.get() != NULL);
+      return buffer_->empty() ? NULL : buffer_->c_str();
+    }
+  }
+
+  
+  size_t IDicomTranscoder::DicomImage::GetBufferSize()
+  {
+    if (isExternalBuffer_)
+    {
+      assert(buffer_.get() == NULL);
+      return externalSize_;
+    }
+    else
+    {    
+      if (buffer_.get() == NULL)
+      {
+        Serialize();
+      }
+
+      assert(buffer_.get() != NULL);
+      return buffer_->size();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/IDicomTranscoder.h	Wed May 20 16:42:44 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/>.
+ **/
+
+
+#pragma once
+
+#include "../Compatibility.h"
+#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 ParsedDicomFile;
+  
+  class IDicomTranscoder : public boost::noncopyable
+  {
+  public:
+    class DicomImage : public boost::noncopyable
+    {
+    private:
+      std::unique_ptr<DcmFileFormat>  parsed_;
+      std::unique_ptr<std::string>    buffer_;
+      bool                            isExternalBuffer_;
+      const void*                     externalBuffer_;
+      size_t                          externalSize_;
+
+      void Parse();
+
+      void Serialize();
+
+      DcmFileFormat* ReleaseParsed();
+
+    public:
+      DicomImage();
+      
+      void Clear();
+      
+      // Calling this method will invalidate the "ParsedDicomFile" object
+      void AcquireParsed(ParsedDicomFile& parsed);
+      
+      void AcquireParsed(DcmFileFormat* parsed);
+
+      void AcquireParsed(DicomImage& other);
+
+      void AcquireBuffer(std::string& buffer /* will be swapped */);
+
+      void AcquireBuffer(DicomImage& other);
+
+      void SetExternalBuffer(const void* buffer,
+                             size_t size);
+
+      void SetExternalBuffer(const std::string& buffer);
+
+      DcmFileFormat& GetParsed();
+
+      ParsedDicomFile* ReleaseAsParsedDicomFile();
+
+      const void* GetBufferData();
+
+      size_t GetBufferSize();
+    };
+
+
+  protected:
+    enum TranscodingType
+    {
+      TranscodingType_Lossy,
+      TranscodingType_Lossless,
+      TranscodingType_Unknown
+    };
+
+    static TranscodingType GetTranscodingType(DicomTransferSyntax target,
+                                              DicomTransferSyntax source);
+
+    static void CheckTranscoding(DicomImage& transcoded,
+                                 DicomTransferSyntax sourceSyntax,
+                                 const std::string& sourceSopInstanceUid,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid);
+    
+  public:    
+    virtual ~IDicomTranscoder()
+    {
+    }
+
+    virtual bool Transcode(DicomImage& target,
+                           DicomImage& source /* in, "GetParsed()" possibly modified */,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) = 0;
+
+    static std::string GetSopInstanceUid(DcmFileFormat& dicom);
+  };
+}
--- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Wed May 20 16:42:44 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.
@@ -810,7 +810,7 @@
      **/
     
     {
-      LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian";
+      LOG(INFO) << "Trying to decode a compressed image by transcoding it to Little Endian Explicit";
 
       std::unique_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone()));
       converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
@@ -821,8 +821,18 @@
       }
     }
 
-    throw OrthancException(ErrorCode_BadFileFormat,
-                           "Cannot decode a DICOM image with the built-in decoder");
+    DicomTransferSyntax s;
+    if (FromDcmtkBridge::LookupOrthancTransferSyntax(s, dataset.getCurrentXfer()))
+    {
+      throw OrthancException(ErrorCode_NotImplemented,
+                             "The built-in DCMTK decoder cannot decode some DICOM instance "
+                             "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)));
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented,
+                             "The built-in DCMTK decoder cannot decode some DICOM instance");
+    }
   }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,109 @@
+/**
+ * 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"
+
+#if !defined(NDEBUG)  // For debugging
+#  include "ParsedDicomFile.h"
+#endif
+
+namespace Orthanc
+{
+  static void CheckTargetSyntax(const std::string& transcoded,
+                                const std::set<DicomTransferSyntax>& allowedSyntaxes)
+  {
+#if !defined(NDEBUG)
+    // Debug mode
+    ParsedDicomFile parsed(transcoded);
+
+    std::string s;
+    DicomTransferSyntax a, b;
+    if (!parsed.LookupTransferSyntax(s) ||
+        !FromDcmtkBridge::LookupOrthancTransferSyntax(a, parsed.GetDcmtkObject()) ||
+        !LookupTransferSyntax(b, s) ||
+        a != b ||
+        allowedSyntaxes.find(a) == allowedSyntaxes.end())
+    {
+      throw OrthancException(
+        ErrorCode_Plugin,
+        "DEBUG - The transcoding plugin has not written to one of the allowed transfer syntaxes");
+    }
+#endif
+  }
+    
+
+  bool MemoryBufferTranscoder::Transcode(DicomImage& target,
+                                         DicomImage& source,
+                                         const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                         bool allowNewSopInstanceUid)
+  {
+    target.Clear();
+    
+#if !defined(NDEBUG)
+    // Don't run this code in release mode, as it implies parsing the DICOM file
+    DicomTransferSyntax sourceSyntax;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, source.GetParsed()))
+    {
+      LOG(ERROR) << "Unsupport transfer syntax for transcoding";
+      return false;
+    }
+    
+    const std::string sourceSopInstanceUid = GetSopInstanceUid(source.GetParsed());
+#endif
+
+    std::string buffer;
+    if (TranscodeBuffer(buffer, source.GetBufferData(), source.GetBufferSize(),
+                        allowedSyntaxes, allowNewSopInstanceUid))
+    {
+      CheckTargetSyntax(buffer, allowedSyntaxes);  // For debug only
+
+      target.AcquireBuffer(buffer);
+      
+#if !defined(NDEBUG)
+      // Only run the sanity check in debug mode
+      CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid,
+                       allowedSyntaxes, allowNewSopInstanceUid);
+#endif
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/MemoryBufferTranscoder.h	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,56 @@
+/**
+ * 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 "IDicomTranscoder.h"
+
+namespace Orthanc
+{
+  // This is the basis class for transcoding plugins
+  class MemoryBufferTranscoder : public IDicomTranscoder
+  {
+  protected:
+    virtual bool TranscodeBuffer(std::string& target,
+                                 const void* buffer,
+                                 size_t size,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid) = 0;
+    
+  public:
+    virtual bool Transcode(DicomImage& target /* out */,
+                           DicomImage& source,
+                           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	Wed May 20 16:42:44 2020 +0200
@@ -455,8 +455,8 @@
   void ParsedDicomFile::SendPathValue(RestApiOutput& output,
                                       const UriComponents& uri)
   {
-    DcmItem* dicom = pimpl_->file_->getDataset();
-    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+    DcmItem* dicom = GetDcmtkObject().getDataset();
+    E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer();
 
     // Special case: Accessing the pixel data
     if (uri.size() == 1 || 
@@ -516,7 +516,7 @@
     InvalidateCache();
 
     DcmTagKey key(tag.GetGroup(), tag.GetElement());
-    DcmElement* element = pimpl_->file_->getDataset()->remove(key);
+    DcmElement* element = GetDcmtkObject().getDataset()->remove(key);
     if (element != NULL)
     {
       delete element;
@@ -536,7 +536,7 @@
 
     InvalidateCache();
 
-    DcmItem* dicom = pimpl_->file_->getDataset();
+    DcmItem* dicom = GetDcmtkObject().getDataset();
     DcmTagKey key(tag.GetGroup(), tag.GetElement());
 
     if (onlyIfExists &&
@@ -558,7 +558,7 @@
   {
     InvalidateCache();
 
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
+    DcmDataset& dataset = *GetDcmtkObject().getDataset();
 
     // Loop over the dataset to detect its private tags
     typedef std::list<DcmElement*> Tags;
@@ -629,7 +629,7 @@
       return;
     }
 
-    if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
+    if (GetDcmtkObject().getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
     {
       throw OrthancException(ErrorCode_AlreadyExistingTag);
     }
@@ -650,7 +650,7 @@
     bool hasCodeExtensions;
     Encoding encoding = DetectEncoding(hasCodeExtensions);
     std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
-    InsertInternal(*pimpl_->file_->getDataset(), element.release());
+    InsertInternal(*GetDcmtkObject().getDataset(), element.release());
   }
 
 
@@ -782,7 +782,7 @@
 
     InvalidateCache();
 
-    DcmDataset& dicom = *pimpl_->file_->getDataset();
+    DcmDataset& dicom = *GetDcmtkObject().getDataset();
     if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
     {
       // Either the tag was previously existing (and now removed), or
@@ -828,7 +828,7 @@
 
     InvalidateCache();
 
-    DcmDataset& dicom = *pimpl_->file_->getDataset();
+    DcmDataset& dicom = *GetDcmtkObject().getDataset();
     if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
     {
       // Either the tag was previously existing (and now removed), or
@@ -867,9 +867,9 @@
   void ParsedDicomFile::Answer(RestApiOutput& output)
   {
     std::string serialized;
-    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset()))
+    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *GetDcmtkObject().getDataset()))
     {
-      output.AnswerBuffer(serialized, MimeType_Binary);
+      output.AnswerBuffer(serialized, MimeType_Dicom);
     }
   }
 #endif
@@ -879,7 +879,7 @@
                                     const DicomTag& tag)
   {
     DcmTagKey k(tag.GetGroup(), tag.GetElement());
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
+    DcmDataset& dataset = *GetDcmtkObject().getDataset();
 
     if (tag.IsPrivate() ||
         FromDcmtkBridge::IsUnknownTag(tag) ||
@@ -970,7 +970,7 @@
 
   void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
   {
-    FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset());
+    FromDcmtkBridge::SaveToMemoryBuffer(buffer, *GetDcmtkObject().getDataset());
   }
 
 
@@ -1004,6 +1004,7 @@
                                            bool permissive)
   {
     pimpl_->file_.reset(new DcmFileFormat);
+    pimpl_->frameIndex_.reset(NULL);
 
     const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET);
 
@@ -1091,7 +1092,7 @@
                                    bool keepSopInstanceUid) : 
     pimpl_(new PImpl)
   {
-    pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
+    pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.GetDcmtkObject().clone()));
 
     if (!keepSopInstanceUid)
     {
@@ -1113,9 +1114,38 @@
   }
 
 
+  ParsedDicomFile::ParsedDicomFile(DcmFileFormat* dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(dicom);  // No cloning
+  }
+
+
   DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
   {
-    return *pimpl_->file_.get();
+    if (pimpl_->file_.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "ReleaseDcmtkObject() was called");
+    }
+    else
+    {
+      return *pimpl_->file_;
+    }
+  }
+
+
+  DcmFileFormat* ParsedDicomFile::ReleaseDcmtkObject()
+  {
+    if (pimpl_->file_.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "ReleaseDcmtkObject() was called");
+    }
+    else
+    {
+      pimpl_->frameIndex_.reset(NULL);
+      return pimpl_->file_.release();
+    }
   }
 
 
@@ -1348,7 +1378,7 @@
       }
     }
 
-    if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good())
+    if (!GetDcmtkObject().getDataset()->insert(pixels.release(), false, false).good())
     {
       throw OrthancException(ErrorCode_InternalError);
     }    
@@ -1358,7 +1388,7 @@
   Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const
   {
     return FromDcmtkBridge::DetectEncoding(hasCodeExtensions,
-                                           *pimpl_->file_->getDataset(),
+                                           *GetDcmtkObject().getDataset(),
                                            GetDefaultDicomEncoding());
   }
 
@@ -1382,7 +1412,7 @@
                                       unsigned int maxStringLength)
   {
     std::set<DicomTag> ignoreTagLength;
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(),
+    FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(),
                                         format, flags, maxStringLength,
                                         GetDefaultDicomEncoding(), ignoreTagLength);
   }
@@ -1394,7 +1424,7 @@
                                       unsigned int maxStringLength,
                                       const std::set<DicomTag>& ignoreTagLength)
   {
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(),
+    FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(),
                                         format, flags, maxStringLength,
                                         GetDefaultDicomEncoding(), ignoreTagLength);
   }
@@ -1403,28 +1433,28 @@
   void ParsedDicomFile::DatasetToJson(Json::Value& target,
                                       const std::set<DicomTag>& ignoreTagLength)
   {
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), ignoreTagLength);
+    FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength);
   }
 
 
   void ParsedDicomFile::DatasetToJson(Json::Value& target)
   {
     const std::set<DicomTag> ignoreTagLength;
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), ignoreTagLength);
+    FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength);
   }
 
 
   void ParsedDicomFile::HeaderToJson(Json::Value& target, 
                                      DicomToJsonFormat format)
   {
-    FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0);
+    FromDcmtkBridge::ExtractHeaderAsJson(target, *GetDcmtkObject().getMetaInfo(), format, DicomToJsonFlags_None, 0);
   }
 
 
   bool ParsedDicomFile::HasTag(const DicomTag& tag) const
   {
     DcmTag key(tag.GetGroup(), tag.GetElement());
-    return pimpl_->file_->getDataset()->tagExists(key);
+    return GetDcmtkObject().getDataset()->tagExists(key);
   }
 
 
@@ -1466,7 +1496,7 @@
     memcpy(bytes, pdf.c_str(), pdf.size());
       
     DcmPolymorphOBOW* obj = element.release();
-    result = pimpl_->file_->getDataset()->insert(obj);
+    result = GetDcmtkObject().getDataset()->insert(obj);
 
     if (!result.good())
     {
@@ -1558,13 +1588,13 @@
     if (pimpl_->frameIndex_.get() == NULL)
     {
       assert(pimpl_->file_ != NULL &&
-             pimpl_->file_->getDataset() != NULL);
-      pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_->getDataset()));
+             GetDcmtkObject().getDataset() != NULL);
+      pimpl_->frameIndex_.reset(new DicomFrameIndex(*GetDcmtkObject().getDataset()));
     }
 
     pimpl_->frameIndex_->GetRawFrame(target, frameId);
 
-    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+    E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer();
     switch (transferSyntax)
     {
       case EXS_JPEGProcess1:
@@ -1592,8 +1622,8 @@
   unsigned int ParsedDicomFile::GetFramesCount() const
   {
     assert(pimpl_->file_ != NULL &&
-           pimpl_->file_->getDataset() != NULL);
-    return DicomFrameIndex::GetFramesCount(*pimpl_->file_->getDataset());
+           GetDcmtkObject().getDataset() != NULL);
+    return DicomFrameIndex::GetFramesCount(*GetDcmtkObject().getDataset());
   }
 
 
@@ -1605,27 +1635,56 @@
     if (source != target)  // Avoid unnecessary conversion
     {
       ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target));
-      FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, hasCodeExtensions, target);
+      FromDcmtkBridge::ChangeStringEncoding(*GetDcmtkObject().getDataset(), source, hasCodeExtensions, target);
     }
   }
 
 
   void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const
   {
-    FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset());
+    FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset());
   }
 
 
   void ParsedDicomFile::ExtractDicomSummary(DicomMap& target,
                                             const std::set<DicomTag>& ignoreTagLength) const
   {
-    FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset(), ignoreTagLength);
+    FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset(), ignoreTagLength);
   }
 
 
   bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
   {
-    return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
+#if 0
+    // This was the implementation in Orthanc <= 1.6.1
+
+    // TODO - Shouldn't "dataset.getCurrentXfer()" be used instead of
+    // using the meta header?
+    const char* value = NULL;
+
+    if (GetDcmtkObject().getMetaInfo() != NULL &&
+        GetDcmtkObject().getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
+        value != NULL)
+    {
+      result.assign(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+#else
+    DicomTransferSyntax s;
+    if (FromDcmtkBridge::LookupOrthancTransferSyntax(s, GetDcmtkObject()))
+    {
+      result.assign(GetTransferSyntaxUid(s));
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+#endif
   }
 
 
@@ -1634,7 +1693,7 @@
     DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(),
                 DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement());
 
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
+    DcmDataset& dataset = *GetDcmtkObject().getDataset();
 
     const char *c = NULL;
     if (dataset.findAndGetString(k, c).good() &&
@@ -1652,6 +1711,6 @@
 
   void ParsedDicomFile::Apply(ITagVisitor& visitor)
   {
-    FromDcmtkBridge::Apply(*pimpl_->file_->getDataset(), visitor, GetDefaultDicomEncoding());
+    FromDcmtkBridge::Apply(*GetDcmtkObject().getDataset(), visitor, GetDefaultDicomEncoding());
   }
 }
--- a/Core/DicomParsing/ParsedDicomFile.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/DicomParsing/ParsedDicomFile.h	Wed May 20 16:42:44 2020 +0200
@@ -102,6 +102,8 @@
 
     bool EmbedContentInternal(const std::string& dataUriScheme);
 
+    ParsedDicomFile(DcmFileFormat* dicom);  // This takes ownership (no clone)
+
   public:
     ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
 
@@ -114,12 +116,20 @@
 
     ParsedDicomFile(const std::string& content);
 
-    ParsedDicomFile(DcmDataset& dicom);
+    ParsedDicomFile(DcmDataset& dicom);  // This clones the DCMTK object
+
+    ParsedDicomFile(DcmFileFormat& dicom);  // This clones the DCMTK object
 
-    ParsedDicomFile(DcmFileFormat& dicom);
+    static ParsedDicomFile* AcquireDcmtkObject(DcmFileFormat* dicom)  // No clone here
+    {
+      return new ParsedDicomFile(dicom);
+    }
 
     DcmFileFormat& GetDcmtkObject() const;
 
+    // The "ParsedDicomFile" object cannot be used after calling this method
+    DcmFileFormat* ReleaseDcmtkObject();
+
     ParsedDicomFile* Clone(bool keepSopInstanceUid);
 
 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
--- a/Core/Enumerations.h	Wed May 06 08:40:48 2020 +0200
+++ b/Core/Enumerations.h	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -1,19 +1,65 @@
 Pending changes in the mainline
 ===============================
 
+General
+-------
+
+* DICOM transcoding over the REST API
+* Transcoding from compressed to uncompressed transfer syntaxes over DICOM
+  C-STORE SCU (if the remote modality doesn't support compressed syntaxes)
+* New configuration options related to transcoding:
+  "TranscodeDicomProtocol", "BuiltinDecoderTranscoderOrder",
+  "IngestTranscoding" and "DicomLossyTranscodingQuality"
+
 REST API
 --------
 
+* API version 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:
+  - "/{patients|studies|series}/.../modify": New option "KeepSource"
+  - "/{patients|studies|series|instances}/.../modify": New option "Transcode"
+  - "/peers/{id}/store": New option "Transcode"
+  - ".../archive", ".../media", "/tools/create-media" and
+    "/tools/create-archive": New option "Transcode"
+  - "/ordered-slices": reverted the change introduced in 1.5.8 and go-back 
+    to 1.5.7 behaviour.
+
+Plugins
+-------
+
+* New functions in the SDK:
+  - OrthancPluginCreateDicomInstance()
+  - OrthancPluginCreateMemoryBuffer()
+  - OrthancPluginEncodeDicomWebJson2()
+  - OrthancPluginEncodeDicomWebXml2()
+  - OrthancPluginFreeDicomInstance()
+  - OrthancPluginGetInstanceAdvancedJson()
+  - OrthancPluginGetInstanceDecodedFrame()
+  - OrthancPluginGetInstanceDicomWebJson()
+  - OrthancPluginGetInstanceDicomWebXml()
+  - OrthancPluginGetInstanceFramesCount()
+  - OrthancPluginGetInstanceRawFrame()
+  - OrthancPluginRegisterTranscoderCallback()
+  - OrthancPluginSerializeDicomInstance()
+  - OrthancPluginTranscodeDicomInstance()
+* "OrthancPluginDicomInstance" structure wrapped in "OrthancPluginCppWrapper.h"
+* Allow concurrent calls to the custom image decoders provided by the plugins
 
 Maintenance
 -----------
 
+* Moved the GDCM sample plugin out of the Orthanc repository as a separate plugin
 * Fix missing body in "OrthancPluginHttpPost()" and "OrthancPluginHttpPut()"
+* Fix issue #169 (TransferSyntaxUID change from Explicit to Implicit during C-STORE SCU)
 * Upgraded dependencies for static builds (notably on Windows and LSB):
   - openssl 1.1.1g
+* Fix issue #179 (deadlock in Python plugins)
 
 
 Version 1.6.1 (2020-04-21)
--- a/OrthancServer/DefaultDicomImageDecoder.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +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 "IDicomImageDecoder.h"
-#include "../Core/DicomParsing/ParsedDicomFile.h"
-#include "../Core/DicomParsing/Internals/DicomImageDecoder.h"
-
-namespace Orthanc
-{
-  class DefaultDicomImageDecoder : public IDicomImageDecoder
-  {
-  public:
-    virtual ImageAccessor* Decode(const void* dicom,
-                                  size_t size,
-                                  unsigned int frame)
-    {
-      ParsedDicomFile parsed(dicom, size);
-      return DicomImageDecoder::Decode(parsed, frame);
-    }
-  };
-}
--- a/OrthancServer/DicomInstanceToStore.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/DicomInstanceToStore.cpp	Wed May 20 16:42:44 2020 +0200
@@ -381,14 +381,14 @@
     }
 
 
-    bool HasPixelData()
+    ParsedDicomFile& GetParsedDicomFile()
     {
       ComputeMissingInformation();
       ParseDicomFile();
       
       if (parsed_.HasContent())
       {
-        return parsed_.GetContent().HasTag(DICOM_TAG_PIXEL_DATA);
+        return parsed_.GetContent();
       }
       else
       {
@@ -498,6 +498,11 @@
 
   bool DicomInstanceToStore::HasPixelData() const
   {
-    return const_cast<PImpl&>(*pimpl_).HasPixelData();
+    return const_cast<PImpl&>(*pimpl_).GetParsedDicomFile().HasTag(DICOM_TAG_PIXEL_DATA);
+  }
+
+  ParsedDicomFile& DicomInstanceToStore::GetParsedDicomFile() const
+  {
+    return const_cast<PImpl&>(*pimpl_).GetParsedDicomFile();
   }
 }
--- a/OrthancServer/DicomInstanceToStore.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/DicomInstanceToStore.h	Wed May 20 16:42:44 2020 +0200
@@ -92,5 +92,7 @@
     DicomInstanceHasher& GetHasher();
 
     bool HasPixelData() const;
+
+    ParsedDicomFile& GetParsedDicomFile() const;
   };
 }
--- a/OrthancServer/IServerListener.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/IServerListener.h	Wed May 20 16:42:44 2020 +0200
@@ -48,7 +48,7 @@
     }
 
     virtual void SignalStoredInstance(const std::string& publicId,
-                                      DicomInstanceToStore& instance,
+                                      const DicomInstanceToStore& instance,
                                       const Json::Value& simplifiedTags) = 0;
     
     virtual void SignalChange(const ServerIndexChange& change) = 0;
--- a/OrthancServer/LuaScripting.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Wed May 20 16:42:44 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")
@@ -821,7 +821,7 @@
 
 
   void LuaScripting::SignalStoredInstance(const std::string& publicId,
-                                          DicomInstanceToStore& instance,
+                                          const DicomInstanceToStore& instance,
                                           const Json::Value& simplifiedTags)
   {
     Json::Value metadata = Json::objectValue;
--- a/OrthancServer/LuaScripting.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/LuaScripting.h	Wed May 20 16:42:44 2020 +0200
@@ -121,7 +121,7 @@
     void Stop();
     
     void SignalStoredInstance(const std::string& publicId,
-                              DicomInstanceToStore& instance,
+                              const DicomInstanceToStore& instance,
                               const Json::Value& simplifiedTags);
 
     void SignalChange(const ServerIndexChange& change);
--- a/OrthancServer/OrthancConfiguration.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancConfiguration.cpp	Wed May 20 16:42:44 2020 +0200
@@ -422,8 +422,8 @@
   }
 
 
-  std::string OrthancConfiguration::GetStringParameter(const std::string& parameter,
-                                                       const std::string& defaultValue) const
+  bool OrthancConfiguration::LookupStringParameter(std::string& target,
+                                                   const std::string& parameter) const
   {
     if (json_.isMember(parameter))
     {
@@ -434,11 +434,27 @@
       }
       else
       {
-        return json_[parameter].asString();
+        target = json_[parameter].asString();
+        return true;
       }
     }
     else
     {
+      return false;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetStringParameter(const std::string& parameter,
+                                                       const std::string& defaultValue) const
+  {
+    std::string value;
+    if (LookupStringParameter(value, parameter))
+    {
+      return value;
+    }
+    else
+    {
       return defaultValue;
     }
   }
--- a/OrthancServer/OrthancConfiguration.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancConfiguration.h	Wed May 20 16:42:44 2020 +0200
@@ -163,6 +163,9 @@
       fontRegistry_.AddFromResource(resource);
     }
 
+    bool LookupStringParameter(std::string& target,
+                               const std::string& parameter) const;
+
     std::string GetStringParameter(const std::string& parameter,
                                    const std::string& defaultValue) const;
     
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -112,19 +112,48 @@
 
 
   static void AnonymizeOrModifyInstance(DicomModification& modification,
-                                        RestApiPostCall& call)
+                                        RestApiPostCall& call,
+                                        bool transcode,
+                                        DicomTransferSyntax targetSyntax)
   {
+    ServerContext& context = OrthancRestApi::GetContext(call);
     std::string id = call.GetUriComponent("id", "");
 
     std::unique_ptr<ParsedDicomFile> modified;
 
     {
-      ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id);
+      ServerContext::DicomCacheLocker locker(context, id);
       modified.reset(locker.GetDicom().Clone(true));
     }
     
     modification.Apply(*modified);
-    modified->Answer(call.GetOutput());
+
+    if (transcode)
+    {
+      IDicomTranscoder::DicomImage source;
+      source.AcquireParsed(*modified);  // "modified" is invalid below this point
+      
+      IDicomTranscoder::DicomImage transcoded;
+
+      std::set<DicomTransferSyntax> s;
+      s.insert(targetSyntax);
+      
+      if (context.Transcode(transcoded, source, s, true))
+      {      
+        call.GetOutput().AnswerBuffer(transcoded.GetBufferData(),
+                                      transcoded.GetBufferSize(), MimeType_Dicom);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError,
+                               "Cannot transcode to transfer syntax: " +
+                               std::string(GetTransferSyntaxUid(targetSyntax)));
+      }
+    }
+    else
+    {
+      modified->Answer(call.GetOutput());
+    }
   }
 
 
@@ -153,7 +182,26 @@
       modification.SetLevel(ResourceType_Instance);
     }
 
-    AnonymizeOrModifyInstance(modification, call);
+    static const char* TRANSCODE = "Transcode";
+    if (request.isMember(TRANSCODE))
+    {
+      std::string s = SerializationToolbox::ReadString(request, TRANSCODE);
+      
+      DicomTransferSyntax syntax;
+      if (LookupTransferSyntax(syntax, s))
+      {
+        AnonymizeOrModifyInstance(modification, call, true, syntax);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange, "Unknown transfer syntax: " + s);
+      }
+    }
+    else
+    {
+      AnonymizeOrModifyInstance(modification, call, false /* no transcoding */,
+                                DicomTransferSyntax_LittleEndianImplicit /* unused */);
+    }
   }
 
 
@@ -165,7 +213,19 @@
     Json::Value request;
     ParseAnonymizationRequest(request, modification, call);
 
-    AnonymizeOrModifyInstance(modification, call);
+    AnonymizeOrModifyInstance(modification, call, false /* no transcoding */,
+                              DicomTransferSyntax_LittleEndianImplicit /* unused */);
+  }
+
+
+  static void SetKeepSource(CleaningInstancesJob& job,
+                            const Json::Value& body)
+  {
+    static const char* KEEP_SOURCE = "KeepSource";
+    if (body.isMember(KEEP_SOURCE))
+    {
+      job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE));
+    }
   }
 
 
@@ -178,11 +238,19 @@
     ServerContext& context = OrthancRestApi::GetContext(call);
 
     std::unique_ptr<ResourceModificationJob> job(new ResourceModificationJob(context));
-    
+
     job->SetModification(modification.release(), level, isAnonymization);
     job->SetOrigin(call);
+    SetKeepSource(*job, body);
+
+    static const char* TRANSCODE = "Transcode";
+    if (body.isMember(TRANSCODE))
+    {
+      job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE));
+    }
     
     context.AddChildInstances(*job, call.GetUriComponent("id", ""));
+    job->AddTrailingStep();
 
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, body);
@@ -227,7 +295,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)
     {
@@ -236,7 +304,7 @@
 
     if (sendAnswer)
     {
-      OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status);
+      OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status, id);
     }
   }
 
@@ -678,14 +746,10 @@
     {
       job->AddSourceSeries(series[i]);
     }
-
+    
     job->AddTrailingStep();
 
-    static const char* KEEP_SOURCE = "KeepSource";
-    if (request.isMember(KEEP_SOURCE))
-    {
-      job->SetKeepSource(SerializationToolbox::ReadBoolean(request, KEEP_SOURCE));
-    }
+    SetKeepSource(*job, request);
 
     static const char* REMOVE = "Remove";
     if (request.isMember(REMOVE))
@@ -764,11 +828,7 @@
 
     job->AddTrailingStep();
 
-    static const char* KEEP_SOURCE = "KeepSource";
-    if (request.isMember(KEEP_SOURCE))
-    {
-      job->SetKeepSource(SerializationToolbox::ReadBoolean(request, KEEP_SOURCE));
-    }
+    SetKeepSource(*job, request);
 
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, request);
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed May 20 16:42:44 2020 +0200
@@ -63,11 +63,11 @@
 
   void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call,
                                             DicomInstanceToStore& instance,
-                                            StoreStatus status) const
+                                            StoreStatus status,
+                                            const std::string& instanceId) const
   {
     Json::Value result;
-    SetupResourceAnswer(result, instance.GetHasher().HashInstance(), 
-                        ResourceType_Instance, status);
+    SetupResourceAnswer(result, instanceId, ResourceType_Instance, status);
 
     result["ParentPatient"] = instance.GetHasher().HashPatient();
     result["ParentStudy"] = instance.GetHasher().HashStudy();
@@ -140,9 +140,9 @@
     }    
 
     std::string publicId;
-    StoreStatus status = context.Store(publicId, toStore);
+    StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default);
 
-    OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status);
+    OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status, publicId);
   }
 
 
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed May 20 16:42:44 2020 +0200
@@ -107,9 +107,12 @@
 
     static ServerIndex& GetIndex(RestApiCall& call);
 
+    // WARNING: "instanceId" can be different from
+    // "instance.GetHasher().HashInstance()" if transcoding is enabled
     void AnswerStoredInstance(RestApiPostCall& call,
                               DicomInstanceToStore& instance,
-                              StoreStatus status) const;
+                              StoreStatus status,
+                              const std::string& instanceId) const;
 
     void AnswerStoredResource(RestApiPostCall& call,
                               const std::string& publicId,
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed May 20 16:42:44 2020 +0200
@@ -46,6 +46,7 @@
 {
   static const char* const KEY_RESOURCES = "Resources";
   static const char* const KEY_EXTENDED = "Extended";
+  static const char* const KEY_TRANSCODE = "Transcode";
   
   static void AddResourcesOfInterestFromArray(ArchiveJob& job,
                                               const Json::Value& resources)
@@ -98,11 +99,28 @@
   }
 
 
-  static void GetJobParameters(bool& synchronous,         /* out */
-                               bool& extended,            /* out */
-                               int& priority,             /* out */
-                               const Json::Value& body,   /* in */
-                               const bool defaultExtended /* in */)
+  static DicomTransferSyntax GetTransferSyntax(const std::string& value)
+  {
+    DicomTransferSyntax syntax;
+    if (LookupTransferSyntax(syntax, value))
+    {
+      return syntax;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Unknown transfer syntax: " + value);
+    }
+  }
+  
+  
+  static void GetJobParameters(bool& synchronous,            /* out */
+                               bool& extended,               /* out */
+                               bool& transcode,              /* out */
+                               DicomTransferSyntax& syntax,  /* out */
+                               int& priority,                /* out */
+                               const Json::Value& body,      /* in */
+                               const bool defaultExtended    /* in */)
   {
     synchronous = OrthancRestApi::IsSynchronousJobRequest
       (true /* synchronous by default */, body);
@@ -118,6 +136,17 @@
     {
       extended = defaultExtended;
     }
+
+    if (body.type() == Json::objectValue &&
+        body.isMember(KEY_TRANSCODE))
+    {
+      transcode = true;
+      syntax = GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE));
+    }
+    else
+    {
+      transcode = false;
+    }
   }
 
 
@@ -175,12 +204,20 @@
     Json::Value body;
     if (call.ParseJsonRequest(body))
     {
-      bool synchronous, extended;
+      bool synchronous, extended, transcode;
+      DicomTransferSyntax transferSyntax;
       int priority;
-      GetJobParameters(synchronous, extended, priority, body, DEFAULT_IS_EXTENDED);
+      GetJobParameters(synchronous, extended, transcode, transferSyntax,
+                       priority, body, DEFAULT_IS_EXTENDED);
       
       std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
       AddResourcesOfInterest(*job, body);
+
+      if (transcode)
+      {
+        job->SetTranscode(transferSyntax);
+      }
+      
       SubmitJob(call.GetOutput(), context, job, priority, synchronous, "Archive.zip");
     }
     else
@@ -208,10 +245,16 @@
     {
       extended = false;
     }
-    
+
     std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
     job->AddResource(id);
 
+    static const char* const TRANSCODE = "transcode";
+    if (call.HasArgument(TRANSCODE))
+    {
+      job->SetTranscode(GetTransferSyntax(call.GetArgument(TRANSCODE, "")));
+    }
+
     SubmitJob(call.GetOutput(), context, job, 0 /* priority */,
               true /* synchronous */, id + ".zip");
   }
@@ -228,12 +271,20 @@
     Json::Value body;
     if (call.ParseJsonRequest(body))
     {
-      bool synchronous, extended;
+      bool synchronous, extended, transcode;
+      DicomTransferSyntax transferSyntax;
       int priority;
-      GetJobParameters(synchronous, extended, priority, body, DEFAULT_IS_EXTENDED);
+      GetJobParameters(synchronous, extended, transcode, transferSyntax,
+                       priority, body, DEFAULT_IS_EXTENDED);
       
       std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
       job->AddResource(id);
+
+      if (transcode)
+      {
+        job->SetTranscode(transferSyntax);
+      }
+
       SubmitJob(call.GetOutput(), context, job, priority, synchronous, id + ".zip");
     }
     else
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed May 20 16:42:44 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;
@@ -1114,21 +1136,31 @@
     std::unique_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context));
 
     GetInstancesToExport(request, *job, remote, call);
+
+    static const char* TRANSCODE = "Transcode";
+    if (request.type() == Json::objectValue &&
+        request.isMember(TRANSCODE))
+    {
+      job->SetTranscode(SerializationToolbox::ReadString(request, TRANSCODE));
+    }
     
-    OrthancConfiguration::ReaderLock lock;
-
-    WebServiceParameters peer;
-    if (lock.GetConfiguration().LookupOrthancPeer(peer, remote))
     {
-      job->SetPeer(peer);    
-      OrthancRestApi::GetApi(call).SubmitCommandsJob
-        (call, job.release(), true /* synchronous by default */, request);
+      OrthancConfiguration::ReaderLock lock;
+      
+      WebServiceParameters peer;
+      if (lock.GetConfiguration().LookupOrthancPeer(peer, remote))
+      {
+        job->SetPeer(peer);    
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_UnknownResource,
+                               "No peer with symbolic name: " + remote);
+      }
     }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownResource,
-                             "No peer with symbolic name: " + remote);
-    }
+
+    OrthancRestApi::GetApi(call).SubmitCommandsJob
+      (call, job.release(), true /* synchronous by default */, request);
   }
 
   static void PeerSystem(RestApiGetCall& call)
@@ -1294,15 +1326,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 +1336,7 @@
       DicomFindAnswers answers(true);
 
       {
-        DicomControlUserConnection connection(localAet, remote);
+        DicomControlUserConnection connection(GetAssociationParameters(call, json));
         connection.FindWorklist(answers, *query);
       }
 
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed May 20 16:42:44 2020 +0200
@@ -43,7 +43,7 @@
 #include "../../Core/Images/Image.h"
 #include "../../Core/Images/ImageProcessing.h"
 #include "../../Core/Logging.h"
-#include "../DefaultDicomImageDecoder.h"
+#include "../../Core/MultiThreading/Semaphore.h"
 #include "../OrthancConfiguration.h"
 #include "../Search/DatabaseLookup.h"
 #include "../ServerContext.h"
@@ -56,6 +56,14 @@
 #include <boost/math/special_functions/round.hpp>
 
 
+/**
+ * This semaphore is used to limit the number of concurrent HTTP
+ * requests on CPU-intensive routes of the REST API, in order to
+ * prevent exhaustion of resources (new in Orthanc 1.7.0).
+ **/
+static Orthanc::Semaphore throttlingSemaphore_(4);  // TODO => PARAMETER?
+
+
 namespace Orthanc
 {
   static void AnswerDicomAsJson(RestApiCall& call,
@@ -547,44 +555,23 @@
         {
           std::string publicId = call.GetUriComponent("id", "");
 
-#if ORTHANC_ENABLE_PLUGINS == 1
-          if (context.GetPlugins().HasCustomImageDecoder())
-          {
-            // TODO create a cache of file
-            std::string dicomContent;
-            context.ReadDicom(dicomContent, publicId);
-            decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame));
-
-            /**
-             * Note that we call "DecodeUnsafe()": We do not fallback to
-             * the builtin decoder if no installed decoder plugin is able
-             * to decode the image. This allows us to take advantage of
-             * the cache below.
-             **/
-
-            if (handler.RequiresDicomTags() &&
-                decoded.get() != NULL)
-            {
-              // TODO Optimize this lookup for photometric interpretation:
-              // It should be implemented by the plugin to avoid parsing
-              // twice the DICOM file
-              ParsedDicomFile parsed(dicomContent);
-              parsed.ExtractDicomSummary(dicom);
-            }
-          }
-#endif
+          decoded.reset(context.DecodeDicomFrame(publicId, frame));
 
           if (decoded.get() == NULL)
           {
-            // Use Orthanc's built-in decoder, using the cache to speed-up
-            // things on multi-frame images
-            ServerContext::DicomCacheLocker locker(context, publicId);        
-            decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame));
-
-            if (handler.RequiresDicomTags())
-            {
-              locker.GetDicom().ExtractDicomSummary(dicom);
-            }
+            throw OrthancException(ErrorCode_NotImplemented,
+                                   "Cannot decode DICOM instance with ID: " + publicId);
+          }
+          
+          if (handler.RequiresDicomTags())
+          {
+            /**
+             * Retrieve a summary of the DICOM tags, which is
+             * necessary to deal with MONOCHROME1 photometric
+             * interpretation, and with windowing parameters.
+             **/ 
+            ServerContext::DicomCacheLocker locker(context, publicId);
+            locker.GetDicom().ExtractDicomSummary(dicom);
           }
         }
         catch (OrthancException& e)
@@ -938,6 +925,8 @@
   template <enum ImageExtractionMode mode>
   static void GetImage(RestApiGetCall& call)
   {
+    Semaphore::Locker locker(throttlingSemaphore_);
+        
     GetImageHandler handler(mode);
     IDecodedFrameHandler::Apply(call, handler);
   }
@@ -945,6 +934,8 @@
 
   static void GetRenderedFrame(RestApiGetCall& call)
   {
+    Semaphore::Locker locker(throttlingSemaphore_);
+        
     RenderedFrameHandler handler;
     IDecodedFrameHandler::Apply(call, handler);
   }
@@ -952,6 +943,8 @@
 
   static void GetMatlabImage(RestApiGetCall& call)
   {
+    Semaphore::Locker locker(throttlingSemaphore_);
+        
     ServerContext& context = OrthancRestApi::GetContext(call);
 
     std::string frameId = call.GetUriComponent("frame", "0");
@@ -967,21 +960,19 @@
     }
 
     std::string publicId = call.GetUriComponent("id", "");
-    std::string dicomContent;
-    context.ReadDicom(dicomContent, publicId);
+    std::unique_ptr<ImageAccessor> decoded(context.DecodeDicomFrame(publicId, frame));
 
-#if ORTHANC_ENABLE_PLUGINS == 1
-    IDicomImageDecoder& decoder = context.GetPlugins();
-#else
-    DefaultDicomImageDecoder decoder;  // This is Orthanc's built-in decoder
-#endif
-
-    std::unique_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame));
-
-    std::string result;
-    decoded->ToMatlabString(result);
-
-    call.GetOutput().AnswerBuffer(result, MimeType_PlainText);
+    if (decoded.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_NotImplemented,
+                             "Cannot decode DICOM instance with ID: " + publicId);
+    }
+    else
+    {
+      std::string result;
+      decoded->ToMatlabString(result);
+      call.GetOutput().AnswerBuffer(result, MimeType_PlainText);
+    }
   }
 
 
--- a/OrthancServer/QueryRetrieveHandler.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -34,7 +34,9 @@
 #include "PrecompiledHeadersServer.h"
 #include "ServerContext.h"
 
+#include "../Core/DicomParsing/Internals/DicomImageDecoder.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"
@@ -82,7 +84,7 @@
       {
         const ServerIndexChange& change = dynamic_cast<const ServerIndexChange&>(*obj.get());
 
-        boost::recursive_mutex::scoped_lock lock(that->listenersMutex_);
+        boost::shared_lock<boost::shared_mutex> lock(that->listenersMutex_);
         for (ServerListeners::iterator it = that->listeners_.begin(); 
              it != that->listeners_.end(); ++it)
         {
@@ -242,33 +244,74 @@
     isJobsEngineUnserialized_(false),
     metricsRegistry_(new MetricsRegistry),
     isHttpServerSecure_(true),
-    isExecuteLuaEnabled_(false)
+    isExecuteLuaEnabled_(false),
+    overwriteInstances_(false),
+    dcmtkTranscoder_(new DcmtkTranscoder),
+    isIngestTranscoding_(false)
   {
+    try
     {
-      OrthancConfiguration::ReaderLock lock;
+      unsigned int lossyQuality;
+
+      {
+        OrthancConfiguration::ReaderLock lock;
 
-      queryRetrieveArchive_.reset(
-        new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("QueryRetrieveSize", 100)));
-      mediaArchive_.reset(
-        new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("MediaArchiveSize", 1)));
-      defaultLocalAet_ = lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC");
-      jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
-      saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
-      metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
+        queryRetrieveArchive_.reset(
+          new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("QueryRetrieveSize", 100)));
+        mediaArchive_.reset(
+          new SharedArchive(lock.GetConfiguration().GetUnsignedIntegerParameter("MediaArchiveSize", 1)));
+        defaultLocalAet_ = lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC");
+        jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
+        saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
+        metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
+
+        // New configuration options in Orthanc 1.5.1
+        findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
+        limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
+        limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
+
+        // New configuration option in Orthanc 1.6.0
+        storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
+
+        // New options in Orthanc 1.7.0
+        transcodeDicomProtocol_ = lock.GetConfiguration().GetBooleanParameter("TranscodeDicomProtocol", true);
+        builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After"));
+        lossyQuality = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomLossyTranscodingQuality", 90);
 
-      // New configuration options in Orthanc 1.5.1
-      findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
-      limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
-      limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
+        std::string s;
+        if (lock.GetConfiguration().LookupStringParameter(s, "IngestTranscoding"))
+        {
+          if (LookupTransferSyntax(ingestTransferSyntax_, s))
+          {
+            isIngestTranscoding_ = true;
+            LOG(WARNING) << "Incoming DICOM instances will automatically be transcoded to "
+                         << "transfer syntax: " << GetTransferSyntaxUid(ingestTransferSyntax_);
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Unknown transfer syntax for ingest transcoding: " + s);
+          }
+        }
+        else
+        {
+          isIngestTranscoding_ = false;
+          LOG(INFO) << "Automated transcoding of incoming DICOM instances is disabled";
+        }
+      }
 
-      // New configuration option in Orthanc 1.6.0
-      storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
-    }
+      jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
 
-    jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
-
-    listeners_.push_back(ServerListener(luaListener_, "Lua"));
-    changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100));
+      listeners_.push_back(ServerListener(luaListener_, "Lua"));
+      changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100));
+    
+      dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetLossyQuality(lossyQuality);
+    }
+    catch (OrthancException&)
+    {
+      Stop();
+      throw;
+    }
   }
 
 
@@ -288,7 +331,7 @@
     if (!done_)
     {
       {
-        boost::recursive_mutex::scoped_lock lock(listenersMutex_);
+        boost::unique_lock<boost::shared_mutex> lock(listenersMutex_);
         listeners_.clear();
       }
 
@@ -338,9 +381,29 @@
   }
 
 
-  StoreStatus ServerContext::Store(std::string& resultPublicId,
-                                   DicomInstanceToStore& dicom)
+  StoreStatus ServerContext::StoreAfterTranscoding(std::string& resultPublicId,
+                                                   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");
@@ -355,7 +418,7 @@
       bool accepted = true;
 
       {
-        boost::recursive_mutex::scoped_lock lock(listenersMutex_);
+        boost::shared_lock<boost::shared_mutex> lock(listenersMutex_);
 
         for (ServerListeners::iterator it = listeners_.begin(); it != listeners_.end(); ++it)
         {
@@ -404,7 +467,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();
@@ -444,7 +508,7 @@
       if (status == StoreStatus_Success ||
           status == StoreStatus_AlreadyStored)
       {
-        boost::recursive_mutex::scoped_lock lock(listenersMutex_);
+        boost::shared_lock<boost::shared_mutex> lock(listenersMutex_);
 
         for (ServerListeners::iterator it = listeners_.begin(); it != listeners_.end(); ++it)
         {
@@ -475,6 +539,59 @@
   }
 
 
+  StoreStatus ServerContext::Store(std::string& resultPublicId,
+                                   DicomInstanceToStore& dicom,
+                                   StoreInstanceMode mode)
+  {
+    if (!isIngestTranscoding_)
+    {
+      // No automated transcoding. This was the only path in Orthanc <= 1.6.1.
+      return StoreAfterTranscoding(resultPublicId, dicom, mode);
+    }
+    else
+    {
+      // Automated transcoding of incoming DICOM files
+
+      DicomTransferSyntax sourceSyntax;
+      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(
+            sourceSyntax, dicom.GetParsedDicomFile().GetDcmtkObject()) ||
+          sourceSyntax == ingestTransferSyntax_)
+      {
+        // No transcoding
+        return StoreAfterTranscoding(resultPublicId, dicom, mode);
+      }
+      else
+      {      
+        std::set<DicomTransferSyntax> syntaxes;
+        syntaxes.insert(ingestTransferSyntax_);
+
+        IDicomTranscoder::DicomImage source;
+        source.SetExternalBuffer(dicom.GetBufferData(), dicom.GetBufferSize());
+
+        IDicomTranscoder::DicomImage transcoded;
+        if (Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */))
+        {
+          std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile());
+
+          DicomInstanceToStore toStore;
+          toStore.SetParsedDicomFile(*tmp);
+          toStore.SetOrigin(dicom.GetOrigin());
+
+          StoreStatus ok = StoreAfterTranscoding(resultPublicId, toStore, mode);
+          assert(resultPublicId == tmp->GetHasher().HashInstance());
+
+          return ok;
+        }
+        else
+        {
+          // Cannot transcode => store the original file
+          return StoreAfterTranscoding(resultPublicId, dicom, mode);
+        }
+      }
+    }
+  }
+
+  
   void ServerContext::AnswerAttachment(RestApiOutput& output,
                                        const std::string& resourceId,
                                        FileContentType content)
@@ -739,7 +856,7 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
   void ServerContext::SetPlugins(OrthancPlugins& plugins)
   {
-    boost::recursive_mutex::scoped_lock lock(listenersMutex_);
+    boost::unique_lock<boost::shared_mutex> lock(listenersMutex_);
 
     plugins_ = &plugins;
 
@@ -752,7 +869,7 @@
 
   void ServerContext::ResetPlugins()
   {
-    boost::recursive_mutex::scoped_lock lock(listenersMutex_);
+    boost::unique_lock<boost::shared_mutex> lock(listenersMutex_);
 
     plugins_ = NULL;
 
@@ -1086,4 +1203,159 @@
 
     return NULL;
   }
+
+
+  ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId,
+                                                 unsigned int frameIndex)
+  {
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
+    {
+      // Use Orthanc's built-in decoder, using the cache to speed-up
+      // things on multi-frame images
+      ServerContext::DicomCacheLocker locker(*this, publicId);        
+      std::unique_ptr<ImageAccessor> decoded(
+        DicomImageDecoder::Decode(locker.GetDicom(), frameIndex));
+      if (decoded.get() != NULL)
+      {
+        return decoded.release();
+      }
+    }
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins() &&
+        GetPlugins().HasCustomImageDecoder())
+    {
+      // TODO: Store the raw buffer in the DicomCacheLocker
+      std::string dicomContent;
+      ReadDicom(dicomContent, publicId);
+      std::unique_ptr<ImageAccessor> decoded(
+        GetPlugins().Decode(dicomContent.c_str(), dicomContent.size(), frameIndex));
+      if (decoded.get() != NULL)
+      {
+        return decoded.release();
+      }
+      else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
+      {
+        LOG(INFO) << "The installed image decoding plugins cannot handle an image, "
+                  << "fallback to the built-in DCMTK decoder";
+      }
+    }
+#endif
+
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
+    {
+      ServerContext::DicomCacheLocker locker(*this, publicId);        
+      return DicomImageDecoder::Decode(locker.GetDicom(), frameIndex);
+    }
+    else
+    {
+      return NULL;  // Built-in decoder is disabled
+    }
+  }
+
+
+  ImageAccessor* ServerContext::DecodeDicomFrame(const DicomInstanceToStore& dicom,
+                                                 unsigned int frameIndex)
+  {
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
+    {
+      std::unique_ptr<ImageAccessor> decoded(
+        DicomImageDecoder::Decode(dicom.GetParsedDicomFile(), frameIndex));
+      if (decoded.get() != NULL)
+      {
+        return decoded.release();
+      }
+    }
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins() &&
+        GetPlugins().HasCustomImageDecoder())
+    {
+      std::unique_ptr<ImageAccessor> decoded(
+        GetPlugins().Decode(dicom.GetBufferData(), dicom.GetBufferSize(), frameIndex));
+      if (decoded.get() != NULL)
+      {
+        return decoded.release();
+      }
+      else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
+      {
+        LOG(INFO) << "The installed image decoding plugins cannot handle an image, "
+                  << "fallback to the built-in DCMTK decoder";
+      }
+    }
+#endif
+
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
+    {
+      return DicomImageDecoder::Decode(dicom.GetParsedDicomFile(), frameIndex);
+    }
+    else
+    {
+      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 (!transcodeDicomProtocol_ ||
+        !connection.GetParameters().GetRemoteModality().IsTranscodingAllowed())
+    {
+      connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(),
+                       hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
+    }
+    else
+    {
+      connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(),
+                           hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
+    }
+  }
+
+
+  bool ServerContext::Transcode(DicomImage& target,
+                                DicomImage& source /* in, "GetParsed()" possibly modified */,
+                                const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                bool allowNewSopInstanceUid)
+  {
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
+    {
+      if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid))
+      {
+        return true;
+      }
+    }
+      
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins() &&
+        GetPlugins().HasCustomTranscoder())
+    {
+      if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid))
+      {
+        return true;
+      }
+      else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
+      {
+        LOG(INFO) << "The installed transcoding plugins cannot handle an image, "
+                  << "fallback to the built-in DCMTK transcoder";
+      }
+    }
+#endif
+
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After)
+    {
+      return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/OrthancServer/ServerContext.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerContext.h	Wed May 20 16:42:44 2020 +0200
@@ -40,6 +40,7 @@
 #include "ServerJobs/IStorageCommitmentFactory.h"
 
 #include "../Core/Cache/MemoryCache.h"
+#include "../Core/DicomParsing/IDicomTranscoder.h"
 
 
 namespace Orthanc
@@ -64,6 +65,7 @@
    **/
   class ServerContext :
     public IStorageCommitmentFactory,
+    public IDicomTranscoder,
     private JobsRegistry::IObserver
   {
   public:
@@ -98,7 +100,7 @@
       }
 
       virtual void SignalStoredInstance(const std::string& publicId,
-                                        DicomInstanceToStore& instance,
+                                        const DicomInstanceToStore& instance,
                                         const Json::Value& simplifiedTags)
       {
         context_.mainLua_.SignalStoredInstance(publicId, instance, simplifiedTags);
@@ -201,7 +203,7 @@
 #endif
 
     ServerListeners listeners_;
-    boost::recursive_mutex listenersMutex_;
+    boost::shared_mutex listenersMutex_;
 
     bool done_;
     bool haveJobsChanged_;
@@ -221,9 +223,20 @@
     std::unique_ptr<MetricsRegistry>  metricsRegistry_;
     bool isHttpServerSecure_;
     bool isExecuteLuaEnabled_;
+    bool overwriteInstances_;
 
     std::unique_ptr<StorageCommitmentReports>  storageCommitmentReports_;
 
+    bool transcodeDicomProtocol_;
+    std::unique_ptr<IDicomTranscoder>  dcmtkTranscoder_;
+    BuiltinDecoderTranscoderOrder builtinDecoderTranscoderOrder_;
+    bool isIngestTranscoding_;
+    DicomTransferSyntax ingestTransferSyntax_;
+
+    StoreStatus StoreAfterTranscoding(std::string& resultPublicId,
+                                      DicomInstanceToStore& dicom,
+                                      StoreInstanceMode mode);
+
   public:
     class DicomCacheLocker : public boost::noncopyable
     {
@@ -275,7 +288,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 +440,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 +462,26 @@
     {
       return *storageCommitmentReports_;
     }
+
+    ImageAccessor* DecodeDicomFrame(const std::string& publicId,
+                                    unsigned int frameIndex);
+
+    ImageAccessor* DecodeDicomFrame(const DicomInstanceToStore& dicom,
+                                    unsigned int frameIndex);
+
+    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 the global option
+    // "TranscodeDicomProtocol" is set to "false"
+    virtual bool Transcode(DicomImage& target,
+                           DicomImage& source /* in, "GetParsed()" possibly modified */,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Wed May 20 16:42:44 2020 +0200
@@ -214,6 +214,29 @@
                              "should be \"Always\", \"Never\" or \"Answers\": " + value);
     }    
   }
+
+
+  BuiltinDecoderTranscoderOrder StringToBuiltinDecoderTranscoderOrder(const std::string& value)
+  {
+    if (value == "Before")
+    {
+      return BuiltinDecoderTranscoderOrder_Before;
+    }
+    else if (value == "After")
+    {
+      return BuiltinDecoderTranscoderOrder_After;
+    }
+    else if (value == "Disabled")
+    {
+      return BuiltinDecoderTranscoderOrder_Disabled;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Configuration option \"BuiltinDecoderTranscoderOrder\" "
+                             "should be \"After\", \"Before\" or \"Disabled\": " + value);
+    }    
+  }
   
 
   std::string GetBasePath(ResourceType type,
--- a/OrthancServer/ServerEnumerations.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerEnumerations.h	Wed May 20 16:42:44 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
@@ -168,6 +175,13 @@
     ChangeType_NewChildInstance = 4097
   };
 
+  enum BuiltinDecoderTranscoderOrder
+  {
+    BuiltinDecoderTranscoderOrder_Before,
+    BuiltinDecoderTranscoderOrder_After,
+    BuiltinDecoderTranscoderOrder_Disabled
+  };
+
 
 
   void InitializeServerEnumerations();
@@ -187,6 +201,8 @@
 
   FindStorageAccessMode StringToFindStorageAccessMode(const std::string& str);
 
+  BuiltinDecoderTranscoderOrder StringToBuiltinDecoderTranscoderOrder(const std::string& str);
+
   std::string EnumerationToString(FileContentType type);
 
   std::string GetFileContentMime(FileContentType type);
--- a/OrthancServer/ServerIndex.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerIndex.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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/ArchiveJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/ArchiveJob.cpp	Wed May 20 16:42:44 2020 +0200
@@ -37,6 +37,7 @@
 #include "../../Core/Cache/SharedArchive.h"
 #include "../../Core/Compression/HierarchicalZipWriter.h"
 #include "../../Core/DicomParsing/DicomDirWriter.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 #include "../OrthancConfiguration.h"
@@ -55,6 +56,7 @@
 static const char* const KEY_DESCRIPTION = "Description";
 static const char* const KEY_INSTANCES_COUNT = "InstancesCount";
 static const char* const KEY_UNCOMPRESSED_SIZE_MB = "UncompressedSizeMB";
+static const char* const KEY_TRANSCODE = "Transcode";
 
 
 namespace Orthanc
@@ -399,7 +401,9 @@
       void Apply(HierarchicalZipWriter& writer,
                  ServerContext& context,
                  DicomDirWriter* dicomDir,
-                 const std::string& dicomDirFolder) const
+                 const std::string& dicomDirFolder,
+                 bool transcode,
+                 DicomTransferSyntax transferSyntax) const
       {
         switch (type_)
         {
@@ -426,14 +430,54 @@
             }
 
             //boost::this_thread::sleep(boost::posix_time::milliseconds(300));
-            
+
             writer.OpenFile(filename_.c_str());
-            writer.Write(content);
+
+            bool transcodeSuccess = false;
+
+            std::unique_ptr<ParsedDicomFile> parsed;
+            
+            if (transcode)
+            {
+              // New in Orthanc 1.7.0
+              std::set<DicomTransferSyntax> syntaxes;
+              syntaxes.insert(transferSyntax);
+
+              IDicomTranscoder::DicomImage source, transcoded;
+              source.SetExternalBuffer(content);
+
+              if (context.Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */))
+              {
+                writer.Write(transcoded.GetBufferData(), transcoded.GetBufferSize());
 
-            if (dicomDir != NULL)
+                if (dicomDir != NULL)
+                {
+                  std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile());
+                  dicomDir->Add(dicomDirFolder, filename_, *tmp);
+                }
+                
+                transcodeSuccess = true;
+              }
+              else
+              {
+                LOG(INFO) << "Cannot transcode instance " << instanceId_
+                          << " to transfer syntax: " << GetTransferSyntaxUid(transferSyntax);
+              }
+            }
+
+            if (!transcodeSuccess)
             {
-              ParsedDicomFile parsed(content);
-              dicomDir->Add(dicomDirFolder, filename_, parsed);
+              writer.Write(content);
+
+              if (dicomDir != NULL)
+              {
+                if (parsed.get() == NULL)
+                {
+                  parsed.reset(new ParsedDicomFile(content));
+                }
+
+                dicomDir->Add(dicomDirFolder, filename_, *parsed);
+              }
             }
               
             break;
@@ -454,14 +498,16 @@
                        ServerContext& context,
                        size_t index,
                        DicomDirWriter* dicomDir,
-                       const std::string& dicomDirFolder) const
+                       const std::string& dicomDirFolder,
+                       bool transcode,
+                       DicomTransferSyntax transferSyntax) const
     {
       if (index >= commands_.size())
       {
         throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
 
-      commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder);
+      commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder, transcode, transferSyntax);
     }
       
   public:
@@ -496,20 +542,26 @@
       return uncompressedSize_;
     }
 
+    // "media" flavor (with DICOMDIR)
     void Apply(HierarchicalZipWriter& writer,
                ServerContext& context,
                size_t index,
                DicomDirWriter& dicomDir,
-               const std::string& dicomDirFolder) const
+               const std::string& dicomDirFolder,
+               bool transcode,
+               DicomTransferSyntax transferSyntax) const
     {
-      ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder);
+      ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder, transcode, transferSyntax);
     }
 
+    // "archive" flavor (without DICOMDIR)
     void Apply(HierarchicalZipWriter& writer,
                ServerContext& context,
-               size_t index) const
+               size_t index,
+               bool transcode,
+               DicomTransferSyntax transferSyntax) const
     {
-      ApplyInternal(writer, context, index, NULL, "");
+      ApplyInternal(writer, context, index, NULL, "", transcode, transferSyntax);
     }
       
     void AddOpenDirectory(const std::string& filename)
@@ -740,7 +792,9 @@
       return commands_.GetSize() + 1;
     }
 
-    void RunStep(size_t index)
+    void RunStep(size_t index,
+                 bool transcode,
+                 DicomTransferSyntax transferSyntax)
     {
       if (index > commands_.GetSize())
       {
@@ -764,12 +818,13 @@
         if (isMedia_)
         {
           assert(dicomDir_.get() != NULL);
-          commands_.Apply(*zip_, context_, index, *dicomDir_, MEDIA_IMAGES_FOLDER);
+          commands_.Apply(*zip_, context_, index, *dicomDir_,
+                          MEDIA_IMAGES_FOLDER, transcode, transferSyntax);
         }
         else
         {
           assert(dicomDir_.get() == NULL);
-          commands_.Apply(*zip_, context_, index);
+          commands_.Apply(*zip_, context_, index, transcode, transferSyntax);
         }
       }
     }
@@ -795,7 +850,9 @@
     enableExtendedSopClass_(enableExtendedSopClass),
     currentStep_(0),
     instancesCount_(0),
-    uncompressedSize_(0)
+    uncompressedSize_(0),
+    transcode_(false),
+    transferSyntax_(DicomTransferSyntax_LittleEndianImplicit)
   {
   }
 
@@ -854,6 +911,20 @@
     }
   }
 
+
+  void ArchiveJob::SetTranscode(DicomTransferSyntax transferSyntax)
+  {
+    if (writer_.get() != NULL)   // Already started
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = true;
+      transferSyntax_ = transferSyntax;
+    }
+  }
+
   
   void ArchiveJob::Reset()
   {
@@ -954,7 +1025,7 @@
     }
     else
     {
-      writer_->RunStep(currentStep_);
+      writer_->RunStep(currentStep_, transcode_, transferSyntax_);
 
       currentStep_ ++;
 
@@ -1006,6 +1077,11 @@
     value[KEY_INSTANCES_COUNT] = instancesCount_;
     value[KEY_UNCOMPRESSED_SIZE_MB] =
       static_cast<unsigned int>(uncompressedSize_ / MEGA_BYTES);
+
+    if (transcode_)
+    {
+      value[KEY_TRANSCODE] = GetTransferSyntaxUid(transferSyntax_);
+    }
   }
 
 
--- a/OrthancServer/ServerJobs/ArchiveJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/ArchiveJob.h	Wed May 20 16:42:44 2020 +0200
@@ -69,6 +69,10 @@
     uint64_t                              uncompressedSize_;
     std::string                           mediaArchiveId_;
 
+    // New in Orthanc 1.7.0
+    bool                 transcode_;
+    DicomTransferSyntax  transferSyntax_;
+    
     void FinalizeTarget();
     
   public:
@@ -89,6 +93,8 @@
 
     void AddResource(const std::string& publicId);
 
+    void SetTranscode(DicomTransferSyntax transferSyntax);
+
     virtual void Reset() ORTHANC_OVERRIDE;
 
     virtual void Start() ORTHANC_OVERRIDE;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerJobs/CleaningInstancesJob.cpp	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,120 @@
+/**
+ * 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 "CleaningInstancesJob.h"
+
+#include "../../Core/SerializationToolbox.h"
+#include "../ServerContext.h"
+
+
+namespace Orthanc
+{
+  bool CleaningInstancesJob::HandleTrailingStep()
+  {
+    if (!keepSource_)
+    {
+      const size_t n = GetInstancesCount();
+
+      for (size_t i = 0; i < n; i++)
+      {
+        Json::Value tmp;
+        context_.DeleteResource(tmp, GetInstance(i), ResourceType_Instance);
+      }
+    }
+
+    return true;
+  }
+
+  
+  void CleaningInstancesJob::SetKeepSource(bool keep)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    keepSource_ = keep;
+  }
+
+
+  static const char* KEEP_SOURCE = "KeepSource";
+
+
+  CleaningInstancesJob::CleaningInstancesJob(ServerContext& context,
+                                             const Json::Value& serialized,
+                                             bool defaultKeepSource) :
+    SetOfInstancesJob(serialized),  // (*)
+    context_(context)
+  {
+    if (!HasTrailingStep())
+    {
+      // Should have been set by (*)
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (serialized.isMember(KEEP_SOURCE))
+    {
+      keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE);
+    }
+    else
+    {
+      keepSource_ = defaultKeepSource;
+    }
+  }
+
+  
+  bool CleaningInstancesJob::Serialize(Json::Value& target)
+  {
+    if (!SetOfInstancesJob::Serialize(target))
+    {
+      return false;
+    }
+    else
+    {
+      target[KEEP_SOURCE] = keepSource_;
+      return true;
+    }
+  }
+
+
+  void CleaningInstancesJob::Start()
+  {
+    if (!HasTrailingStep())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "AddTrailingStep() should have been called before submitting the job");
+    }
+
+    SetOfInstancesJob::Start();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerJobs/CleaningInstancesJob.h	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,79 @@
+/**
+ * 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/JobsEngine/SetOfInstancesJob.h"
+
+namespace Orthanc
+{
+  class ServerContext;
+  
+  class CleaningInstancesJob : public SetOfInstancesJob
+  {
+  private:
+    ServerContext&  context_;
+    bool            keepSource_;
+    
+  protected:
+    virtual bool HandleTrailingStep();
+    
+  public:
+    CleaningInstancesJob(ServerContext& context,
+                         bool keepSource) :
+      context_(context),
+      keepSource_(keepSource)
+    {
+    }
+
+    CleaningInstancesJob(ServerContext& context,
+                         const Json::Value& serialized,
+                         bool defaultKeepSource);
+
+    ServerContext& GetContext() const
+    {
+      return context_;
+    }
+    
+    bool IsKeepSource() const
+    {
+      return keepSource_;
+    }
+    
+    void SetKeepSource(bool keep);
+
+    virtual bool Serialize(Json::Value& target);
+
+    virtual void Start();
+  };
+}
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -48,7 +48,7 @@
 
     // Add all the instances of the series as to be processed
     std::list<std::string> instances;
-    context_.GetIndex().GetChildren(instances, series);
+    GetContext().GetIndex().GetChildren(instances, series);
 
     for (std::list<std::string>::const_iterator
            it = instances.begin(); it != instances.end(); ++it)
@@ -68,7 +68,7 @@
     else
     {
       std::list<std::string> series;
-      context_.GetIndex().GetChildren(series, study);
+      GetContext().GetIndex().GetChildren(series, study);
 
       for (std::list<std::string>::const_iterator
              it = series.begin(); it != series.end(); ++it)
@@ -81,6 +81,12 @@
 
   bool MergeStudyJob::HandleInstance(const std::string& instance)
   {
+    if (!HasTrailingStep())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "AddTrailingStep() should have been called after AddSourceXXX()");
+    }
+    
     /**
      * Retrieve the DICOM instance to be modified
      **/
@@ -89,7 +95,7 @@
 
     try
     {
-      ServerContext::DicomCacheLocker locker(context_, instance);
+      ServerContext::DicomCacheLocker locker(GetContext(), instance);
       modified.reset(locker.GetDicom().Clone(true));
     }
     catch (OrthancException&)
@@ -145,7 +151,8 @@
     toStore.SetParsedDicomFile(*modified);
 
     std::string modifiedInstance;
-    if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
+    if (GetContext().Store(modifiedInstance, toStore,
+                       StoreInstanceMode_Default) != StoreStatus_Success)
     {
       LOG(ERROR) << "Error while storing a modified instance " << instance;
       return false;
@@ -155,27 +162,9 @@
   }
 
   
-  bool MergeStudyJob::HandleTrailingStep()
-  {
-    if (!keepSource_)
-    {
-      const size_t n = GetInstancesCount();
-
-      for (size_t i = 0; i < n; i++)
-      {
-        Json::Value tmp;
-        context_.DeleteResource(tmp, GetInstance(i), ResourceType_Instance);
-      }
-    }
-
-    return true;
-  }
-
-  
   MergeStudyJob::MergeStudyJob(ServerContext& context,
                                const std::string& targetStudy) :
-    context_(context),
-    keepSource_(false),
+    CleaningInstancesJob(context, false /* by default, remove source instances */),
     targetStudy_(targetStudy)
   {
     /**
@@ -184,7 +173,7 @@
     
     ResourceType type;
 
-    if (!context_.GetIndex().LookupResourceType(type, targetStudy) ||
+    if (!GetContext().GetIndex().LookupResourceType(type, targetStudy) ||
         type != ResourceType_Study)
     {
       throw OrthancException(ErrorCode_UnknownResource,
@@ -201,7 +190,7 @@
     DicomTag::AddTagsForModule(removals_, DicomModule_Study);
     
     std::list<std::string> instances;
-    context_.GetIndex().GetChildInstances(instances, targetStudy);
+    GetContext().GetIndex().GetChildInstances(instances, targetStudy);
     
     if (instances.empty())
     {
@@ -211,7 +200,7 @@
     DicomMap dicom;
 
     {
-      ServerContext::DicomCacheLocker locker(context_, instances.front());
+      ServerContext::DicomCacheLocker locker(GetContext(), instances.front());
       locker.GetDicom().ExtractDicomSummary(dicom);
     }
 
@@ -259,7 +248,7 @@
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
-    else if (!context_.GetIndex().LookupResourceType(level, studyOrSeries))
+    else if (!GetContext().GetIndex().LookupResourceType(level, studyOrSeries))
     {
       throw OrthancException(ErrorCode_UnknownResource,
                              "Cannot find this resource: " + studyOrSeries);
@@ -294,7 +283,7 @@
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
-    else if (!context_.GetIndex().LookupParent(parent, series, ResourceType_Study))
+    else if (!GetContext().GetIndex().LookupParent(parent, series, ResourceType_Study))
     {
       throw OrthancException(ErrorCode_UnknownResource,
                              "This resource is not a series: " + series);
@@ -320,7 +309,7 @@
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
-    else if (!context_.GetIndex().LookupResourceType(actualLevel, study) ||
+    else if (!GetContext().GetIndex().LookupResourceType(actualLevel, study) ||
              actualLevel != ResourceType_Study)
     {
       throw OrthancException(ErrorCode_UnknownResource,
@@ -333,25 +322,13 @@
   }
 
 
-  void MergeStudyJob::SetKeepSource(bool keep)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    keepSource_ = keep;
-  }
-
-
   void MergeStudyJob::GetPublicContent(Json::Value& value)
   {
-    SetOfInstancesJob::GetPublicContent(value);
+    CleaningInstancesJob::GetPublicContent(value);
     value["TargetStudy"] = targetStudy_;
   }
 
 
-  static const char* KEEP_SOURCE = "KeepSource";
   static const char* TARGET_STUDY = "TargetStudy";
   static const char* REPLACEMENTS = "Replacements";
   static const char* REMOVALS = "Removals";
@@ -361,8 +338,8 @@
 
   MergeStudyJob::MergeStudyJob(ServerContext& context,
                                const Json::Value& serialized) :
-    SetOfInstancesJob(serialized),  // (*)
-    context_(context)
+    CleaningInstancesJob(context, serialized,
+                         false /* by default, remove source instances */)  // (*)
   {
     if (!HasTrailingStep())
     {
@@ -370,7 +347,6 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE);
     targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY);
     SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS);
     SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS);
@@ -381,13 +357,12 @@
   
   bool MergeStudyJob::Serialize(Json::Value& target)
   {
-    if (!SetOfInstancesJob::Serialize(target))
+    if (!CleaningInstancesJob::Serialize(target))
     {
       return false;
     }
     else
     {
-      target[KEEP_SOURCE] = keepSource_;
       target[TARGET_STUDY] = targetStudy_;
       SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS);
       SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS);
--- a/OrthancServer/ServerJobs/MergeStudyJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/MergeStudyJob.h	Wed May 20 16:42:44 2020 +0200
@@ -34,22 +34,20 @@
 #pragma once
 
 #include "../../Core/DicomFormat/DicomMap.h"
-#include "../../Core/JobsEngine/SetOfInstancesJob.h"
 #include "../DicomInstanceOrigin.h"
+#include "CleaningInstancesJob.h"
 
 namespace Orthanc
 {
   class ServerContext;
   
-  class MergeStudyJob : public SetOfInstancesJob
+  class MergeStudyJob : public CleaningInstancesJob
   {
   private:
     typedef std::map<std::string, std::string>  SeriesUidMap;
     typedef std::map<DicomTag, std::string>     Replacements;
     
     
-    ServerContext&         context_;
-    bool                   keepSource_;
     std::string            targetStudy_;
     Replacements           replacements_;
     std::set<DicomTag>     removals_;
@@ -61,12 +59,9 @@
 
     void AddSourceStudyInternal(const std::string& study);
 
-
   protected:
     virtual bool HandleInstance(const std::string& instance);
 
-    virtual bool HandleTrailingStep();
-    
   public:
     MergeStudyJob(ServerContext& context,
                   const std::string& targetStudy);
@@ -85,13 +80,6 @@
 
     void AddSourceSeries(const std::string& series);
 
-    bool IsKeepSource() const
-    {
-      return keepSource_;
-    }
-    
-    void SetKeepSource(bool keep);
-
     void SetOrigin(const DicomInstanceOrigin& origin);
 
     void SetOrigin(const RestApiCall& call);
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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/OrthancPeerStoreJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp	Wed May 20 16:42:44 2020 +0200
@@ -35,8 +35,11 @@
 #include "OrthancPeerStoreJob.h"
 
 #include "../../Core/Logging.h"
+#include "../../Core/SerializationToolbox.h"
 #include "../ServerContext.h"
 
+#include <dcmtk/dcmdata/dcfilefo.h>
+
 
 namespace Orthanc
 {
@@ -55,7 +58,31 @@
 
     try
     {
-      context_.ReadDicom(client_->GetBody(), instance);
+      if (transcode_)
+      {
+        std::string dicom;
+        context_.ReadDicom(dicom, instance);
+
+        std::set<DicomTransferSyntax> syntaxes;
+        syntaxes.insert(transferSyntax_);
+        
+        IDicomTranscoder::DicomImage source, transcoded;
+        source.SetExternalBuffer(dicom);
+
+        if (context_.Transcode(transcoded, source, syntaxes, true))
+        {
+          client_->GetBody().assign(reinterpret_cast<const char*>(transcoded.GetBufferData()),
+                                    transcoded.GetBufferSize());
+        }
+        else
+        {
+          client_->GetBody().swap(dicom);
+        }
+      }
+      else
+      {
+        context_.ReadDicom(client_->GetBody(), instance);
+      }
     }
     catch (OrthancException& e)
     {
@@ -94,6 +121,61 @@
   }
 
 
+  DicomTransferSyntax OrthancPeerStoreJob::GetTransferSyntax() const
+  {
+    if (transcode_)
+    {
+      return transferSyntax_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  void OrthancPeerStoreJob::SetTranscode(DicomTransferSyntax syntax)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = true;
+      transferSyntax_ = syntax;
+    }    
+  }
+  
+
+  void OrthancPeerStoreJob::SetTranscode(const std::string& transferSyntaxUid)
+  {
+    DicomTransferSyntax s;
+    if (LookupTransferSyntax(s, transferSyntaxUid))
+    {
+      SetTranscode(s);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Unknown transfer syntax UID: " + transferSyntaxUid);
+    }
+  }
+
+
+  void OrthancPeerStoreJob::ClearTranscode()
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = false;
+    }
+  }
+
+
   void OrthancPeerStoreJob::Stop(JobStopReason reason)   // For pausing jobs
   {
     client_.reset(NULL);
@@ -109,17 +191,33 @@
                     false /* allow simple format if possible */,
                     false /* don't include passwords */);
     value["Peer"] = v;
+    
+    if (transcode_)
+    {
+      value["Transcode"] = GetTransferSyntaxUid(transferSyntax_);
+    }
   }
 
 
   static const char* PEER = "Peer";
+  static const char* TRANSCODE = "Transcode";
 
   OrthancPeerStoreJob::OrthancPeerStoreJob(ServerContext& context,
                                            const Json::Value& serialized) :
     SetOfInstancesJob(serialized),
     context_(context)
   {
+    assert(serialized.type() == Json::objectValue);
     peer_ = WebServiceParameters(serialized[PEER]);
+
+    if (serialized.isMember(TRANSCODE))
+    {
+      SetTranscode(SerializationToolbox::ReadString(serialized, TRANSCODE));
+    }
+    else
+    {
+      transcode_ = false;
+    }
   }
 
 
@@ -131,9 +229,16 @@
     }
     else
     {
+      assert(target.type() == Json::objectValue);
       peer_.Serialize(target[PEER],
                       true /* force advanced format */,
                       true /* include passwords */);
+
+      if (transcode_)
+      {
+        target[TRANSCODE] = GetTransferSyntaxUid(transferSyntax_);
+      }
+      
       return true;
     }
   }  
--- a/OrthancServer/ServerJobs/OrthancPeerStoreJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.h	Wed May 20 16:42:44 2020 +0200
@@ -48,6 +48,8 @@
     ServerContext&               context_;
     WebServiceParameters         peer_;
     std::unique_ptr<HttpClient>  client_;
+    bool                         transcode_;
+    DicomTransferSyntax          transferSyntax_;
 
   protected:
     virtual bool HandleInstance(const std::string& instance);
@@ -56,7 +58,8 @@
 
   public:
     OrthancPeerStoreJob(ServerContext& context) :
-      context_(context)
+      context_(context),
+      transcode_(false)
     {
     }
 
@@ -70,6 +73,19 @@
       return peer_;
     }
 
+    bool IsTranscode() const
+    {
+      return transcode_;
+    }
+
+    DicomTransferSyntax GetTransferSyntax() const;
+
+    void SetTranscode(DicomTransferSyntax syntax);
+
+    void SetTranscode(const std::string& transferSyntaxUid);
+
+    void ClearTranscode();
+
     virtual void Stop(JobStopReason reason);   // For pausing jobs
 
     virtual void GetJobType(std::string& target)
--- a/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Wed May 20 16:42:44 2020 +0200
@@ -38,6 +38,10 @@
 #include "../../Core/SerializationToolbox.h"
 #include "../ServerContext.h"
 
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <cassert>
+
 namespace Orthanc
 {
   class ResourceModificationJob::Output : public boost::noncopyable
@@ -152,7 +156,7 @@
 
     try
     {
-      ServerContext::DicomCacheLocker locker(context_, instance);
+      ServerContext::DicomCacheLocker locker(GetContext(), instance);
       ParsedDicomFile& original = locker.GetDicom();
 
       originalHasher.reset(new DicomInstanceHasher(original.GetHasher()));
@@ -171,6 +175,41 @@
 
     modification_->Apply(*modified);
 
+    const std::string modifiedUid = IDicomTranscoder::GetSopInstanceUid(modified->GetDcmtkObject());
+    
+    if (transcode_)
+    {
+      std::set<DicomTransferSyntax> syntaxes;
+      syntaxes.insert(transferSyntax_);
+
+      IDicomTranscoder::DicomImage source;
+      source.AcquireParsed(*modified);  // "modified" is invalid below this point
+      
+      IDicomTranscoder::DicomImage transcoded;
+      if (GetContext().Transcode(transcoded, source, syntaxes, true))
+      {
+        modified.reset(transcoded.ReleaseAsParsedDicomFile());
+
+        // Fix the SOP instance UID in order the preserve the
+        // references between instance UIDs in the DICOM hierarchy
+        // (the UID might have changed in the case of lossy transcoding)
+        if (modified.get() == NULL ||
+            modified->GetDcmtkObject().getDataset() == NULL ||
+            !modified->GetDcmtkObject().getDataset()->putAndInsertString(
+              DCM_SOPInstanceUID, modifiedUid.c_str(), OFTrue /* replace */).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+      else
+      {
+        LOG(WARNING) << "Cannot transcode instance, keeping original transfer syntax: " << instance;
+        modified.reset(source.ReleaseAsParsedDicomFile());
+      }
+    }
+
+    assert(modifiedUid == IDicomTranscoder::GetSopInstanceUid(modified->GetDcmtkObject()));
+
     DicomInstanceToStore toStore;
     toStore.SetOrigin(origin_);
     toStore.SetParsedDicomFile(*modified);
@@ -211,23 +250,32 @@
      **/
 
     std::string modifiedInstance;
-    if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
+    if (GetContext().Store(modifiedInstance, toStore,
+                           StoreInstanceMode_Default) != StoreStatus_Success)
     {
       throw OrthancException(ErrorCode_CannotStoreInstance,
                              "Error while storing a modified instance " + instance);
     }
 
-    assert(modifiedInstance == modifiedHasher.HashInstance());
+    /**
+     * The assertion below will fail if automated transcoding to a
+     * lossy transfer syntax is enabled in the Orthanc core, and if
+     * the source instance is not in this transfer syntax.
+     **/
+    // assert(modifiedInstance == modifiedHasher.HashInstance());
 
     output_->Update(modifiedHasher);
 
     return true;
   }
 
-  
-  bool ResourceModificationJob::HandleTrailingStep()
+
+  ResourceModificationJob::ResourceModificationJob(ServerContext& context) :
+    CleaningInstancesJob(context, true /* by default, keep source */),
+    modification_(new DicomModification),
+    isAnonymization_(false),
+    transcode_(false)
   {
-    throw OrthancException(ErrorCode_InternalError);
   }
 
 
@@ -284,9 +332,64 @@
   }
 
 
+  DicomTransferSyntax ResourceModificationJob::GetTransferSyntax() const
+  {
+    if (transcode_)
+    {
+      return transferSyntax_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  void ResourceModificationJob::SetTranscode(DicomTransferSyntax syntax)
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = true;
+      transferSyntax_ = syntax;
+    }    
+  }
+
+
+  void ResourceModificationJob::SetTranscode(const std::string& transferSyntaxUid)
+  {
+    DicomTransferSyntax s;
+    if (LookupTransferSyntax(s, transferSyntaxUid))
+    {
+      SetTranscode(s);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Unknown transfer syntax UID: " + transferSyntaxUid);
+    }
+  }
+
+
+  void ResourceModificationJob::ClearTranscode()
+  {
+    if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      transcode_ = false;
+    }
+  }
+
+
   void ResourceModificationJob::GetPublicContent(Json::Value& value)
   {
-    SetOfInstancesJob::GetPublicContent(value);
+    CleaningInstancesJob::GetPublicContent(value);
 
     value["IsAnonymization"] = isAnonymization_;
 
@@ -294,33 +397,57 @@
     {
       output_->Format(value);
     }
+
+    if (transcode_)
+    {
+      value["Transcode"] = GetTransferSyntaxUid(transferSyntax_);
+    }
   }
 
 
   static const char* MODIFICATION = "Modification";
   static const char* ORIGIN = "Origin";
   static const char* IS_ANONYMIZATION = "IsAnonymization";
+  static const char* TRANSCODE = "Transcode";
   
 
   ResourceModificationJob::ResourceModificationJob(ServerContext& context,
                                                    const Json::Value& serialized) :
-    SetOfInstancesJob(serialized),
-    context_(context)
+    CleaningInstancesJob(context, serialized, true /* by default, keep source */)
   {
+    assert(serialized.type() == Json::objectValue);
+
     isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION);
     origin_ = DicomInstanceOrigin(serialized[ORIGIN]);
     modification_.reset(new DicomModification(serialized[MODIFICATION]));
+
+    if (serialized.isMember(TRANSCODE))
+    {
+      SetTranscode(SerializationToolbox::ReadString(serialized, TRANSCODE));
+    }
+    else
+    {
+      transcode_ = false;
+    }
   }
   
   bool ResourceModificationJob::Serialize(Json::Value& value)
   {
-    if (!SetOfInstancesJob::Serialize(value))
+    if (!CleaningInstancesJob::Serialize(value))
     {
       return false;
     }
     else
     {
+      assert(value.type() == Json::objectValue);
+      
       value[IS_ANONYMIZATION] = isAnonymization_;
+
+      if (transcode_)
+      {
+        value[TRANSCODE] = GetTransferSyntaxUid(transferSyntax_);
+      }
+      
       origin_.Serialize(value[ORIGIN]);
       
       Json::Value tmp;
--- a/OrthancServer/ServerJobs/ResourceModificationJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.h	Wed May 20 16:42:44 2020 +0200
@@ -33,36 +33,31 @@
 
 #pragma once
 
-#include "../../Core/JobsEngine/SetOfInstancesJob.h"
 #include "../../Core/DicomParsing/DicomModification.h"
 #include "../DicomInstanceOrigin.h"
+#include "CleaningInstancesJob.h"
 
 namespace Orthanc
 {
   class ServerContext;
   
-  class ResourceModificationJob : public SetOfInstancesJob
+  class ResourceModificationJob : public CleaningInstancesJob
   {
   private:
     class Output;
     
-    ServerContext&                      context_;
     std::unique_ptr<DicomModification>  modification_;
     boost::shared_ptr<Output>           output_;
     bool                                isAnonymization_;
     DicomInstanceOrigin                 origin_;
+    bool                                transcode_;
+    DicomTransferSyntax                 transferSyntax_;
 
   protected:
     virtual bool HandleInstance(const std::string& instance);
     
-    virtual bool HandleTrailingStep();
-
   public:
-    ResourceModificationJob(ServerContext& context) :
-      context_(context),
-      isAnonymization_(false)
-    {
-    }
+    ResourceModificationJob(ServerContext& context);
 
     ResourceModificationJob(ServerContext& context,
                             const Json::Value& serialized);
@@ -87,6 +82,19 @@
       return origin_;
     }
 
+    bool IsTranscode() const
+    {
+      return transcode_;
+    }
+
+    DicomTransferSyntax GetTransferSyntax() const;
+
+    void SetTranscode(DicomTransferSyntax syntax);
+
+    void SetTranscode(const std::string& transferSyntaxUid);
+
+    void ClearTranscode();
+
     virtual void Stop(JobStopReason reason)
     {
     }
--- a/OrthancServer/ServerJobs/SplitStudyJob.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp	Wed May 20 16:42:44 2020 +0200
@@ -67,6 +67,12 @@
   
   bool SplitStudyJob::HandleInstance(const std::string& instance)
   {
+    if (!HasTrailingStep())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "AddTrailingStep() should have been called after AddSourceSeries()");
+    }
+    
     /**
      * Retrieve the DICOM instance to be modified
      **/
@@ -75,7 +81,7 @@
 
     try
     {
-      ServerContext::DicomCacheLocker locker(context_, instance);
+      ServerContext::DicomCacheLocker locker(GetContext(), instance);
       modified.reset(locker.GetDicom().Clone(true));
     }
     catch (OrthancException&)
@@ -138,7 +144,8 @@
     toStore.SetParsedDicomFile(*modified);
 
     std::string modifiedInstance;
-    if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
+    if (GetContext().Store(modifiedInstance, toStore,
+                           StoreInstanceMode_Default) != StoreStatus_Success)
     {
       LOG(ERROR) << "Error while storing a modified instance " << instance;
       return false;
@@ -148,27 +155,9 @@
   }
 
   
-  bool SplitStudyJob::HandleTrailingStep()
-  {
-    if (!keepSource_)
-    {
-      const size_t n = GetInstancesCount();
-
-      for (size_t i = 0; i < n; i++)
-      {
-        Json::Value tmp;
-        context_.DeleteResource(tmp, GetInstance(i), ResourceType_Instance);
-      }
-    }
-
-    return true;
-  }
-
-  
   SplitStudyJob::SplitStudyJob(ServerContext& context,
                                const std::string& sourceStudy) :
-    context_(context),
-    keepSource_(false),
+    CleaningInstancesJob(context, false /* by default, remove source instances */),
     sourceStudy_(sourceStudy),
     targetStudyUid_(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study))
   {
@@ -176,7 +165,7 @@
     
     ResourceType type;
     
-    if (!context_.GetIndex().LookupResourceType(type, sourceStudy) ||
+    if (!GetContext().GetIndex().LookupResourceType(type, sourceStudy) ||
         type != ResourceType_Study)
     {
       throw OrthancException(ErrorCode_UnknownResource,
@@ -212,7 +201,7 @@
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
-    else if (!context_.GetIndex().LookupParent(parent, series, ResourceType_Study) ||
+    else if (!GetContext().GetIndex().LookupParent(parent, series, ResourceType_Study) ||
              parent != sourceStudy_)
     {
       throw OrthancException(ErrorCode_UnknownResource,
@@ -225,7 +214,7 @@
 
       // Add all the instances of the series as to be processed
       std::list<std::string> instances;
-      context_.GetIndex().GetChildren(instances, series);
+      GetContext().GetIndex().GetChildren(instances, series);
 
       for (std::list<std::string>::const_iterator
              it = instances.begin(); it != instances.end(); ++it)
@@ -236,17 +225,6 @@
   }
 
 
-  void SplitStudyJob::SetKeepSource(bool keep)
-  {
-    if (IsStarted())
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    keepSource_ = keep;
-  }
-
-
   bool SplitStudyJob::LookupTargetSeriesUid(std::string& uid,
                                             const std::string& series) const
   {
@@ -308,7 +286,7 @@
     
   void SplitStudyJob::GetPublicContent(Json::Value& value)
   {
-    SetOfInstancesJob::GetPublicContent(value);
+    CleaningInstancesJob::GetPublicContent(value);
 
     if (!targetStudy_.empty())
     {
@@ -319,7 +297,6 @@
   }
 
 
-  static const char* KEEP_SOURCE = "KeepSource";
   static const char* SOURCE_STUDY = "SourceStudy";
   static const char* TARGET_STUDY = "TargetStudy";
   static const char* TARGET_STUDY_UID = "TargetStudyUID";
@@ -331,8 +308,8 @@
 
   SplitStudyJob::SplitStudyJob(ServerContext& context,
                                const Json::Value& serialized) :
-    SetOfInstancesJob(serialized),  // (*)
-    context_(context)
+    CleaningInstancesJob(context, serialized,
+                         false /* by default, remove source instances */)  // (*)
   {
     if (!HasTrailingStep())
     {
@@ -342,7 +319,6 @@
 
     Setup();
 
-    keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE);
     sourceStudy_ = SerializationToolbox::ReadString(serialized, SOURCE_STUDY);
     targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY);
     targetStudyUid_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY_UID);
@@ -355,13 +331,12 @@
   
   bool SplitStudyJob::Serialize(Json::Value& target)
   {
-    if (!SetOfInstancesJob::Serialize(target))
+    if (!CleaningInstancesJob::Serialize(target))
     {
       return false;
     }
     else
     {
-      target[KEEP_SOURCE] = keepSource_;
       target[SOURCE_STUDY] = sourceStudy_;
       target[TARGET_STUDY] = targetStudy_;
       target[TARGET_STUDY_UID] = targetStudyUid_;
--- a/OrthancServer/ServerJobs/SplitStudyJob.h	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/ServerJobs/SplitStudyJob.h	Wed May 20 16:42:44 2020 +0200
@@ -33,24 +33,22 @@
 
 #pragma once
 
-#include "../../Core/JobsEngine/SetOfInstancesJob.h"
 #include "../../Core/DicomFormat/DicomTag.h"
 #include "../DicomInstanceOrigin.h"
+#include "CleaningInstancesJob.h"
 
 namespace Orthanc
 {
   class ServerContext;
   
-  class SplitStudyJob : public SetOfInstancesJob
+  class SplitStudyJob : public CleaningInstancesJob
   {
   private:
     typedef std::map<std::string, std::string>  SeriesUidMap;
     typedef std::map<DicomTag, std::string>     Replacements;
     
     
-    ServerContext&         context_;
     std::set<DicomTag>     allowedTags_;
-    bool                   keepSource_;
     std::string            sourceStudy_;
     std::string            targetStudy_;
     std::string            targetStudyUid_;
@@ -66,8 +64,6 @@
   protected:
     virtual bool HandleInstance(const std::string& instance);
 
-    virtual bool HandleTrailingStep();
-    
   public:
     SplitStudyJob(ServerContext& context,
                   const std::string& sourceStudy);
@@ -92,13 +88,6 @@
 
     void AddSourceSeries(const std::string& series);
 
-    bool IsKeepSource() const
-    {
-      return keepSource_;
-    }
-    
-    void SetKeepSource(bool keep);
-
     bool LookupTargetSeriesUid(std::string& uid,
                                const std::string& series) const;
 
--- a/OrthancServer/SliceOrdering.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/OrthancServer/SliceOrdering.cpp	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -62,7 +62,6 @@
 #include "../../Core/OrthancException.h"
 #include "../../Core/SerializationToolbox.h"
 #include "../../Core/Toolbox.h"
-#include "../../OrthancServer/DefaultDicomImageDecoder.h"
 #include "../../OrthancServer/OrthancConfiguration.h"
 #include "../../OrthancServer/OrthancFindRequestHandler.h"
 #include "../../OrthancServer/Search/HierarchicalMatcher.h"
@@ -71,12 +70,13 @@
 #include "PluginsEnumerations.h"
 #include "PluginsJob.h"
 
-#include <boost/regex.hpp> 
+#include <boost/regex.hpp>
 #include <dcmtk/dcmdata/dcdict.h>
 #include <dcmtk/dcmdata/dcdicent.h>
 
 #define ERROR_MESSAGE_64BIT "A 64bit version of the Orthanc API is necessary"
 
+
 namespace Orthanc
 {
   static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
@@ -330,9 +330,11 @@
     class DicomWebBinaryFormatter : public DicomWebJsonVisitor::IBinaryFormatter
     {
     private:
-      OrthancPluginDicomWebBinaryCallback  callback_;
-      DicomWebJsonVisitor::BinaryMode      currentMode_;
-      std::string                          currentBulkDataUri_;
+      OrthancPluginDicomWebBinaryCallback   oldCallback_;
+      OrthancPluginDicomWebBinaryCallback2  newCallback_;  // New in Orthanc 1.7.0
+      void*                                 newPayload_;   // New in Orthanc 1.7.0
+      DicomWebJsonVisitor::BinaryMode       currentMode_;
+      std::string                           currentBulkDataUri_;
 
       static void Setter(OrthancPluginDicomWebNode*       node,
                          OrthancPluginDicomWebBinaryMode  mode,
@@ -366,8 +368,18 @@
       }
       
     public:
-      DicomWebBinaryFormatter(const _OrthancPluginEncodeDicomWeb& parameters) :
-        callback_(parameters.callback)
+      DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback callback) :
+        oldCallback_(callback),
+        newCallback_(NULL),
+        newPayload_(NULL)
+      {
+      }
+      
+      DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback2 callback,
+                              void* payload) :
+        oldCallback_(NULL),
+        newCallback_(callback),
+        newPayload_(payload)
       {
       }
       
@@ -377,7 +389,8 @@
                                                      const DicomTag& tag,
                                                      ValueRepresentation vr)
       {
-        if (callback_ == NULL)
+        if (oldCallback_ == NULL &&
+            newCallback_ == NULL)
         {
           return DicomWebJsonVisitor::BinaryMode_InlineBinary;
         }
@@ -398,20 +411,70 @@
 
           currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
 
-          callback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
-                    DicomWebBinaryFormatter::Setter,
-                    static_cast<uint32_t>(parentTags.size()),
-                    (empty ? NULL : &groups[0]),
-                    (empty ? NULL : &elements[0]),
-                    (empty ? NULL : &indexes[0]),
-                    tag.GetGroup(),
-                    tag.GetElement(),
-                    Plugins::Convert(vr));
+          if (oldCallback_ != NULL)
+          {
+            oldCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
+                         DicomWebBinaryFormatter::Setter,
+                         static_cast<uint32_t>(parentTags.size()),
+                         (empty ? NULL : &groups[0]),
+                         (empty ? NULL : &elements[0]),
+                         (empty ? NULL : &indexes[0]),
+                         tag.GetGroup(),
+                         tag.GetElement(),
+                         Plugins::Convert(vr));
+          }
+          else
+          {
+            assert(newCallback_ != NULL);
+            newCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
+                         DicomWebBinaryFormatter::Setter,
+                         static_cast<uint32_t>(parentTags.size()),
+                         (empty ? NULL : &groups[0]),
+                         (empty ? NULL : &elements[0]),
+                         (empty ? NULL : &indexes[0]),
+                         tag.GetGroup(),
+                         tag.GetElement(),
+                         Plugins::Convert(vr),
+                         newPayload_);
+          }          
 
           bulkDataUri = currentBulkDataUri_;          
           return currentMode_;
         }
       }
+
+      void Apply(char** target,
+                 bool isJson,
+                 ParsedDicomFile& dicom)
+      {
+        DicomWebJsonVisitor visitor;
+        visitor.SetFormatter(*this);
+
+        dicom.Apply(visitor);
+
+        std::string s;
+
+        if (isJson)
+        {
+          s = visitor.GetResult().toStyledString();
+        }
+        else
+        {
+          visitor.FormatXml(s);
+        }
+
+        *target = CopyString(s);
+      }
+
+  
+      void Apply(char** target,
+                 bool isJson,
+                 const void* dicom,
+                 size_t dicomSize) 
+      {
+        ParsedDicomFile parsed(dicom, dicomSize);
+        Apply(target, isJson, parsed);
+      }
     };
   }
 
@@ -827,6 +890,7 @@
     typedef std::list<OrthancPluginIncomingHttpRequestFilter2>  IncomingHttpRequestFilters2;
     typedef std::list<OrthancPluginIncomingDicomInstanceFilter>  IncomingDicomInstanceFilters;
     typedef std::list<OrthancPluginDecodeImageCallback>  DecodeImageCallbacks;
+    typedef std::list<OrthancPluginTranscoderCallback>  TranscoderCallbacks;
     typedef std::list<OrthancPluginJobsUnserializer>  JobsUnserializers;
     typedef std::list<OrthancPluginRefreshMetricsCallback>  RefreshMetricsCallbacks;
     typedef std::list<StorageCommitmentScp*>  StorageCommitmentScpCallbacks;
@@ -841,6 +905,7 @@
     OrthancPluginFindCallback  findCallback_;
     OrthancPluginWorklistCallback  worklistCallback_;
     DecodeImageCallbacks  decodeImageCallbacks_;
+    TranscoderCallbacks  transcoderCallbacks_;
     JobsUnserializers  jobsUnserializers_;
     _OrthancPluginMoveCallback moveCallbacks_;
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
@@ -855,7 +920,7 @@
     boost::recursive_mutex changeCallbackMutex_;
     boost::mutex findCallbackMutex_;
     boost::mutex worklistCallbackMutex_;
-    boost::mutex decodeImageCallbackMutex_;
+    boost::shared_mutex decoderTranscoderMutex_;  // Changed from "boost::mutex" in Orthanc 1.7.0
     boost::mutex jobsUnserializersMutex_;
     boost::mutex refreshMetricsMutex_;
     boost::mutex storageCommitmentScpMutex_;
@@ -1761,19 +1826,110 @@
   }
 
 
+  class OrthancPlugins::IDicomInstance : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomInstance()
+    {
+    }
+
+    virtual bool CanBeFreed() const = 0;
+
+    virtual const DicomInstanceToStore& GetInstance() const = 0;
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromCallback : public IDicomInstance
+  {
+  private:
+    const DicomInstanceToStore&  instance_;
+
+  public:
+    DicomInstanceFromCallback(const DicomInstanceToStore& instance) :
+      instance_(instance)
+    {
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromBuffer : public IDicomInstance
+  {
+  private:
+    std::string           buffer_;
+    DicomInstanceToStore  instance_;
+
+  public:
+    DicomInstanceFromBuffer(const void* buffer,
+                            size_t size)
+    {
+      buffer_.assign(reinterpret_cast<const char*>(buffer), size);
+      instance_.SetBuffer(buffer_.empty() ? NULL : buffer_.c_str(), buffer_.size());
+      instance_.SetOrigin(DicomInstanceOrigin::FromPlugins());
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromTranscoded : public IDicomInstance
+  {
+  private:
+    std::unique_ptr<ParsedDicomFile>  parsed_;
+    DicomInstanceToStore              instance_;
+
+  public:
+    DicomInstanceFromTranscoded(IDicomTranscoder::DicomImage& transcoded) :
+      parsed_(transcoded.ReleaseAsParsedDicomFile())
+    {
+      instance_.SetParsedDicomFile(*parsed_);
+      instance_.SetOrigin(DicomInstanceOrigin::FromPlugins());
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
   void OrthancPlugins::SignalStoredInstance(const std::string& instanceId,
-                                            DicomInstanceToStore& instance,
+                                            const DicomInstanceToStore& instance,
                                             const Json::Value& simplifiedTags)
   {
+    DicomInstanceFromCallback wrapped(instance);
+    
     boost::recursive_mutex::scoped_lock lock(pimpl_->storedCallbackMutex_);
 
     for (PImpl::OnStoredCallbacks::const_iterator
            callback = pimpl_->onStoredCallbacks_.begin(); 
          callback != pimpl_->onStoredCallbacks_.end(); ++callback)
     {
-      OrthancPluginErrorCode error = (*callback) 
-        (reinterpret_cast<OrthancPluginDicomInstance*>(&instance),
-         instanceId.c_str());
+      OrthancPluginErrorCode error = (*callback) (
+        reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped),
+        instanceId.c_str());
 
       if (error != OrthancPluginErrorCode_Success)
       {
@@ -1787,14 +1943,15 @@
   bool OrthancPlugins::FilterIncomingInstance(const DicomInstanceToStore& instance,
                                               const Json::Value& simplified)
   {
+    DicomInstanceFromCallback wrapped(instance);
+    
     boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
     
     for (PImpl::IncomingDicomInstanceFilters::const_iterator
            filter = pimpl_->incomingDicomInstanceFilters_.begin();
          filter != pimpl_->incomingDicomInstanceFilters_.end(); ++filter)
     {
-      int32_t allowed = (*filter) (
-        reinterpret_cast<const OrthancPluginDicomInstance*>(&instance));
+      int32_t allowed = (*filter) (reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
 
       if (allowed == 0)
       {
@@ -1954,7 +2111,7 @@
     const _OrthancPluginDecodeImageCallback& p = 
       *reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters);
 
-    boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
+    boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
 
     pimpl_->decodeImageCallbacks_.push_back(p.callback);
     LOG(INFO) << "Plugin has registered a callback to decode DICOM images (" 
@@ -1962,6 +2119,19 @@
   }
 
 
+  void OrthancPlugins::RegisterTranscoderCallback(const void* parameters)
+  {
+    const _OrthancPluginTranscoderCallback& p = 
+      *reinterpret_cast<const _OrthancPluginTranscoderCallback*>(parameters);
+
+    boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+
+    pimpl_->transcoderCallbacks_.push_back(p.callback);
+    LOG(INFO) << "Plugin has registered a callback to transcode DICOM images (" 
+              << pimpl_->transcoderCallbacks_.size() << " transcoder(s) now active)";
+  }
+
+
   void OrthancPlugins::RegisterJobsUnserializer(const void* parameters)
   {
     const _OrthancPluginJobsUnserializer& p = 
@@ -2451,14 +2621,19 @@
   }
 
 
-  static void AccessDicomInstance(_OrthancPluginService service,
-                                  const void* parameters)
+  void OrthancPlugins::AccessDicomInstance(_OrthancPluginService service,
+                                           const void* parameters)
   {
     const _OrthancPluginAccessDicomInstance& p = 
       *reinterpret_cast<const _OrthancPluginAccessDicomInstance*>(parameters);
 
+    if (p.instance == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
     const DicomInstanceToStore& instance =
-      *reinterpret_cast<const DicomInstanceToStore*>(p.instance);
+      reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
 
     switch (service)
     {
@@ -2523,6 +2698,10 @@
         *p.resultInt64 = instance.HasPixelData();
         return;
 
+      case _OrthancPluginService_GetInstanceFramesCount:  // New in Orthanc 1.7.0
+        *p.resultInt64 = instance.GetParsedDicomFile().GetFramesCount();
+        return;
+        
       default:
         throw OrthancException(ErrorCode_InternalError);
     }
@@ -2592,6 +2771,11 @@
     // Images returned to plugins are assumed to be writeable. If the
     // input image is read-only, we return a copy so that it can be modified.
 
+    if (image.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    
     if (image->IsReadOnly())
     {
       std::unique_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
@@ -2606,6 +2790,114 @@
   }
 
 
+  void OrthancPlugins::AccessDicomInstance2(_OrthancPluginService service,
+                                            const void* parameters)
+  {
+    const _OrthancPluginAccessDicomInstance2& p = 
+      *reinterpret_cast<const _OrthancPluginAccessDicomInstance2*>(parameters);
+
+    if (p.instance == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    const DicomInstanceToStore& instance =
+      reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
+
+    switch (service)
+    {
+      case _OrthancPluginService_GetInstanceFramesCount:
+        *p.targetUint32 = instance.GetParsedDicomFile().GetFramesCount();
+        return;
+        
+      case _OrthancPluginService_GetInstanceRawFrame:
+      {
+        if (p.targetBuffer == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        p.targetBuffer->data = NULL;
+        p.targetBuffer->size = 0;
+        
+        MimeType mime;
+        std::string frame;
+        instance.GetParsedDicomFile().GetRawFrame(frame, mime, p.frameIndex);
+        CopyToMemoryBuffer(*p.targetBuffer, frame);
+        return;
+      }
+        
+      case _OrthancPluginService_GetInstanceDecodedFrame:
+      {
+        if (p.targetImage == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        std::unique_ptr<ImageAccessor> decoded;
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          decoded.reset(lock.GetContext().DecodeDicomFrame(instance, p.frameIndex));
+        }
+        
+        *(p.targetImage) = ReturnImage(decoded);
+        return;
+      }
+        
+      case _OrthancPluginService_SerializeDicomInstance:
+      {
+        if (p.targetBuffer == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        p.targetBuffer->data = NULL;
+        p.targetBuffer->size = 0;
+        
+        std::string serialized;
+        instance.GetParsedDicomFile().SaveToMemoryBuffer(serialized);
+        CopyToMemoryBuffer(*p.targetBuffer, serialized);
+        return;
+      }
+
+      case _OrthancPluginService_GetInstanceAdvancedJson:
+      {
+        if (p.targetStringToFree == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        
+        Json::Value json;
+        instance.GetParsedDicomFile().DatasetToJson(
+          json, Plugins::Convert(p.format), 
+          static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
+
+        Json::FastWriter writer;
+        *p.targetStringToFree = CopyString(writer.write(json));        
+        return;
+      }
+      
+      case _OrthancPluginService_GetInstanceDicomWebJson:
+      case _OrthancPluginService_GetInstanceDicomWebXml:
+      {
+        if (p.targetStringToFree == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        DicomWebBinaryFormatter formatter(p.dicomWebCallback, p.dicomWebPayload);
+        formatter.Apply(p.targetStringToFree,
+                        (service == _OrthancPluginService_GetInstanceDicomWebJson),
+                        instance.GetParsedDicomFile());
+        return;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
   void OrthancPlugins::UncompressImage(const void* parameters)
   {
     const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
@@ -3481,6 +3773,16 @@
         AccessDicomInstance(service, parameters);
         return true;
 
+      case _OrthancPluginService_GetInstanceFramesCount:
+      case _OrthancPluginService_GetInstanceRawFrame:
+      case _OrthancPluginService_GetInstanceDecodedFrame:
+      case _OrthancPluginService_SerializeDicomInstance:
+      case _OrthancPluginService_GetInstanceAdvancedJson:
+      case _OrthancPluginService_GetInstanceDicomWebJson:
+      case _OrthancPluginService_GetInstanceDicomWebXml:
+        AccessDicomInstance2(service, parameters);
+        return true;
+
       case _OrthancPluginService_SetGlobalProperty:
       {
         const _OrthancPluginGlobalProperty& p = 
@@ -3998,28 +4300,23 @@
         const _OrthancPluginEncodeDicomWeb& p =
           *reinterpret_cast<const _OrthancPluginEncodeDicomWeb*>(parameters);
 
-        DicomWebBinaryFormatter formatter(p);
-        
-        DicomWebJsonVisitor visitor;
-        visitor.SetFormatter(formatter);
-
-        {
-          ParsedDicomFile dicom(p.dicom, p.dicomSize);
-          dicom.Apply(visitor);
-        }
-
-        std::string s;
-
-        if (service == _OrthancPluginService_EncodeDicomWebJson)
-        {
-          s = visitor.GetResult().toStyledString();
-        }
-        else
-        {
-          visitor.FormatXml(s);
-        }
-
-        *p.target = CopyString(s);
+        DicomWebBinaryFormatter formatter(p.callback);
+        formatter.Apply(p.target,
+                        (service == _OrthancPluginService_EncodeDicomWebJson),
+                        p.dicom, p.dicomSize);
+        return true;
+      }
+
+      case _OrthancPluginService_EncodeDicomWebJson2:
+      case _OrthancPluginService_EncodeDicomWebXml2:
+      {
+        const _OrthancPluginEncodeDicomWeb2& p =
+          *reinterpret_cast<const _OrthancPluginEncodeDicomWeb2*>(parameters);
+
+        DicomWebBinaryFormatter formatter(p.callback, p.payload);
+        formatter.Apply(p.target,
+                        (service == _OrthancPluginService_EncodeDicomWebJson2),
+                        p.dicom, p.dicomSize);
         return true;
       }
 
@@ -4027,6 +4324,98 @@
         GetTagName(parameters);
         return true;
 
+      case _OrthancPluginService_CreateDicomInstance:
+      {
+        const _OrthancPluginCreateDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
+        *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
+          new DicomInstanceFromBuffer(p.buffer, p.size));
+        return true;
+      }
+        
+      case _OrthancPluginService_FreeDicomInstance:
+      {
+        const _OrthancPluginFreeDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginFreeDicomInstance*>(parameters);
+
+        if (p.dicom != NULL)
+        {
+          IDicomInstance* obj = reinterpret_cast<IDicomInstance*>(p.dicom);
+          
+          if (obj->CanBeFreed())
+          {
+            delete obj;
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_Plugin, "Cannot free a DICOM instance provided to a callback");
+          }
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_TranscodeDicomInstance:
+      {
+        const _OrthancPluginCreateDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
+
+        DicomTransferSyntax transferSyntax;
+        if (p.transferSyntax == NULL ||
+            !LookupTransferSyntax(transferSyntax, p.transferSyntax))
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange, "Unsupported transfer syntax: " +
+                                 std::string(p.transferSyntax == NULL ? "(null)" : p.transferSyntax));
+        }
+        else
+        {
+          std::set<DicomTransferSyntax> syntaxes;
+          syntaxes.insert(transferSyntax);
+
+          IDicomTranscoder::DicomImage source;
+          source.SetExternalBuffer(p.buffer, p.size);
+
+          IDicomTranscoder::DicomImage transcoded;
+          bool success;
+          
+          {
+            PImpl::ServerContextLock lock(*pimpl_);
+            success = lock.GetContext().Transcode(
+              transcoded, source, syntaxes, true /* allow new sop */);
+          }
+
+          if (success)
+          {
+            *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
+              new DicomInstanceFromTranscoded(transcoded));
+            return true;
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode image");
+          }
+        }
+      }
+
+      case _OrthancPluginService_CreateMemoryBuffer:
+      {
+        const _OrthancPluginCreateMemoryBuffer& p =
+          *reinterpret_cast<const _OrthancPluginCreateMemoryBuffer*>(parameters);
+
+        p.target->size = p.size;
+        
+        if (p.size == 0)
+        {
+          p.target->data = NULL;
+        }
+        else
+        {
+          p.target->data = malloc(p.size);
+        }          
+        
+        return true;
+      }
+        
       default:
         return false;
     }
@@ -4080,6 +4469,10 @@
         RegisterDecodeImageCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterTranscoderCallback:
+        RegisterTranscoderCallback(parameters);
+        return true;
+
       case _OrthancPluginService_RegisterJobsUnserializer:
         RegisterJobsUnserializer(parameters);
         return true;
@@ -4464,16 +4857,23 @@
 
   bool OrthancPlugins::HasCustomImageDecoder()
   {
-    boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
     return !pimpl_->decodeImageCallbacks_.empty();
   }
 
 
-  ImageAccessor*  OrthancPlugins::DecodeUnsafe(const void* dicom,
-                                               size_t size,
-                                               unsigned int frame)
+  bool OrthancPlugins::HasCustomTranscoder()
   {
-    boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+    return !pimpl_->transcoderCallbacks_.empty();
+  }
+
+
+  ImageAccessor* OrthancPlugins::Decode(const void* dicom,
+                                        size_t size,
+                                        unsigned int frame)
+  {
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
 
     for (PImpl::DecodeImageCallbacks::const_iterator
            decoder = pimpl_->decodeImageCallbacks_.begin();
@@ -4490,25 +4890,6 @@
     return NULL;
   }
 
-
-  ImageAccessor* OrthancPlugins::Decode(const void* dicom,
-                                        size_t size,
-                                        unsigned int frame)
-  {
-    ImageAccessor* result = DecodeUnsafe(dicom, size, frame);
-
-    if (result != NULL)
-    {
-      return result;
-    }
-    else
-    {
-      LOG(INFO) << "The installed image decoding plugins cannot handle an image, fallback to the built-in decoder";
-      DefaultDicomImageDecoder defaultDecoder;
-      return defaultDecoder.Decode(dicom, size, frame); 
-    }
-  }
-
   
   bool OrthancPlugins::IsAllowed(HttpMethod method,
                                  const char* uri,
@@ -4793,4 +5174,81 @@
     
     return NULL;
   }
+
+
+  class MemoryBufferRaii : public boost::noncopyable
+  {
+  private:
+    OrthancPluginMemoryBuffer  buffer_;
+
+  public:
+    MemoryBufferRaii()
+    {
+      buffer_.size = 0;
+      buffer_.data = NULL;
+    }
+
+    ~MemoryBufferRaii()
+    {
+      if (buffer_.size != 0)
+      {
+        free(buffer_.data);
+      }
+    }
+
+    OrthancPluginMemoryBuffer* GetObject()
+    {
+      return &buffer_;
+    }
+
+    void ToString(std::string& target) const
+    {
+      target.resize(buffer_.size);
+
+      if (buffer_.size != 0)
+      {
+        memcpy(&target[0], buffer_.data, buffer_.size);
+      }
+    }
+  };
+  
+
+  bool OrthancPlugins::TranscodeBuffer(std::string& target,
+                                       const void* buffer,
+                                       size_t size,
+                                       const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                       bool allowNewSopInstanceUid)
+  {
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+
+    if (pimpl_->transcoderCallbacks_.empty())
+    {
+      return NULL;
+    }
+
+    std::vector<const char*> uids;
+    uids.reserve(allowedSyntaxes.size());
+    for (std::set<DicomTransferSyntax>::const_iterator
+           it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it)
+    {
+      uids.push_back(GetTransferSyntaxUid(*it));
+    }
+    
+    for (PImpl::TranscoderCallbacks::const_iterator
+           transcoder = pimpl_->transcoderCallbacks_.begin();
+         transcoder != pimpl_->transcoderCallbacks_.end(); ++transcoder)
+    {
+      MemoryBufferRaii a;
+
+      if ((*transcoder) (a.GetObject(), buffer, size, uids.empty() ? NULL : &uids[0],
+                         static_cast<uint32_t>(uids.size()), allowNewSopInstanceUid) ==
+          OrthancPluginErrorCode_Success)
+      {
+        a.ToString(target);
+        return true;
+      }
+    }
+
+    return false;
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Wed May 06 08:40:48 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Wed May 20 16:42:44 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;
@@ -94,6 +96,10 @@
     class HttpClientChunkedRequest;
     class HttpClientChunkedAnswer;
     class HttpServerChunkedReader;
+    class IDicomInstance;
+    class DicomInstanceFromCallback;
+    class DicomInstanceFromBuffer;
+    class DicomInstanceFromTranscoded;
     
     void RegisterRestCallback(const void* parameters,
                               bool lock);
@@ -118,6 +124,8 @@
 
     void RegisterDecodeImageCallback(const void* parameters);
 
+    void RegisterTranscoderCallback(const void* parameters);
+
     void RegisterJobsUnserializer(const void* parameters);
 
     void RegisterIncomingHttpRequestFilter(const void* parameters);
@@ -155,6 +163,12 @@
     void LookupResource(_OrthancPluginService service,
                         const void* parameters);
 
+    void AccessDicomInstance(_OrthancPluginService service,
+                             const void* parameters);
+    
+    void AccessDicomInstance2(_OrthancPluginService service,
+                              const void* parameters);
+    
     void SendHttpStatusCode(const void* parameters);
 
     void SendHttpStatus(const void* parameters);
@@ -223,6 +237,14 @@
                                 _OrthancPluginService service,
                                 const void* parameters);
 
+  protected:
+    // From "MemoryBufferTranscoder"
+    virtual bool TranscodeBuffer(std::string& target,
+                                 const void* buffer,
+                                 size_t size,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+    
   public:
     OrthancPlugins();
 
@@ -250,7 +272,7 @@
     virtual void SignalChange(const ServerIndexChange& change) ORTHANC_OVERRIDE;
     
     virtual void SignalStoredInstance(const std::string& instanceId,
-                                      DicomInstanceToStore& instance,
+                                      const DicomInstanceToStore& instance,
                                       const Json::Value& simplifiedTags) ORTHANC_OVERRIDE;
 
     virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance,
@@ -305,12 +327,7 @@
 
     bool HasCustomImageDecoder();
 
-    // Contrarily to "Decode()", this method does not fallback to the
-    // builtin image decoder, if no installed custom decoder can
-    // handle the image (it returns NULL in this case).
-    ImageAccessor* DecodeUnsafe(const void* dicom,
-                                size_t size,
-                                unsigned int frame);
+    bool HasCustomTranscoder();
 
     virtual ImageAccessor* Decode(const void* dicom,
                                   size_t size,
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed May 06 08:40:48 2020 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed May 20 16:42:44 2020 +0200
@@ -28,6 +28,7 @@
  *    - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback().
  *    - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback().
  *    - Possibly register a callback to filter incoming DICOM instance using OrthancPluginRegisterIncomingDicomInstanceFilter().
+ *    - Possibly register a custom transcoder for DICOM images using OrthancPluginRegisterTranscoderCallback().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    This function is invoked by Orthanc during its shutdown. The plugin
  *    must free all its memory.
@@ -64,6 +65,9 @@
  *
  * @defgroup Orthanc Orthanc
  * @brief Functions to access the content of the Orthanc server.
+ *
+ * @defgroup DicomInstance DicomInstance
+ * @brief Functions to access DICOM images that are managed by the Orthanc core.
  **/
 
 
@@ -124,8 +128,8 @@
 #endif
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     6
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     7
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -437,8 +441,11 @@
     _OrthancPluginService_SetMetricsValue = 31,
     _OrthancPluginService_EncodeDicomWebJson = 32,
     _OrthancPluginService_EncodeDicomWebXml = 33,
-    _OrthancPluginService_ChunkedHttpClient = 34,   /* New in Orthanc 1.5.7 */
-    _OrthancPluginService_GetTagName = 35,   /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_ChunkedHttpClient = 34,    /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_GetTagName = 35,           /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_EncodeDicomWebJson2 = 36,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_EncodeDicomWebXml2 = 37,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_CreateMemoryBuffer = 38,   /* New in Orthanc 1.7.0 */
     
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -456,7 +463,8 @@
     _OrthancPluginService_RegisterChunkedRestCallback = 1012,  /* New in Orthanc 1.5.7 */
     _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013,
     _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014,
-
+    _OrthancPluginService_RegisterTranscoderCallback = 1015,   /* New in Orthanc 1.7.0 */
+    
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
     _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
@@ -502,7 +510,17 @@
     _OrthancPluginService_GetInstanceOrigin = 4007,
     _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008,
     _OrthancPluginService_HasInstancePixelData = 4009,
-
+    _OrthancPluginService_CreateDicomInstance = 4010,      /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_FreeDicomInstance = 4011,        /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceFramesCount = 4012,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceRawFrame = 4013,      /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDecodedFrame = 4014,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_TranscodeDicomInstance = 4015,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_SerializeDicomInstance = 4016,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceAdvancedJson = 4017,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDicomWebJson = 4018,  /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDicomWebXml = 4019,   /* New in Orthanc 1.7.0 */
+    
     /* Services for plugins implementing a database back-end */
     _OrthancPluginService_RegisterDatabaseBackend = 5000,
     _OrthancPluginService_DatabaseAnswer = 5001,
@@ -1009,7 +1027,8 @@
 
 
   /**
-   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   * @brief Opaque structure that represents a DICOM instance that is managed by the Orthanc core.
+   * @ingroup DicomInstance
    **/
   typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
 
@@ -1609,6 +1628,45 @@
 
 
   /**
+   * @brief Callback executed to encode a binary tag in DICOMweb.
+   * 
+   * Signature of a callback function that is called by Orthanc
+   * whenever a DICOM tag that contains a binary value must be written
+   * to a JSON or XML node, while a DICOMweb document is being
+   * generated. The value representation (VR) of the DICOM tag can be
+   * OB, OD, OF, OL, OW, or UN.
+   * 
+   * @see OrthancPluginEncodeDicomWebJson() and OrthancPluginEncodeDicomWebXml()
+   * @param node The node being generated, as provided by Orthanc.
+   * @param setter The setter to be used to encode the content of the node. If
+   * the setter is not called, the binary tag is not written to the output document.
+   * @param levelDepth The depth of the node in the DICOM hierarchy of sequences.
+   * This parameter gives the number of elements in the "levelTagGroup", 
+   * "levelTagElement", and "levelIndex" arrays.
+   * @param levelTagGroup The group of the parent DICOM tags in the hierarchy.
+   * @param levelTagElement The element of the parent DICOM tags in the hierarchy.
+   * @param levelIndex The index of the node in the parent sequences of the hierarchy.
+   * @param tagGroup The group of the DICOM tag of interest.
+   * @param tagElement The element of the DICOM tag of interest.
+   * @param vr The value representation of the binary DICOM node.
+   * @param payload The user payload.
+   * @ingroup Callbacks
+   **/
+  typedef void (*OrthancPluginDicomWebBinaryCallback2) (
+    OrthancPluginDicomWebNode*          node,
+    OrthancPluginDicomWebSetBinaryNode  setter,
+    uint32_t                            levelDepth,
+    const uint16_t*                     levelTagGroup,
+    const uint16_t*                     levelTagElement,
+    const uint32_t*                     levelIndex,
+    uint16_t                            tagGroup,
+    uint16_t                            tagElement,
+    OrthancPluginValueRepresentation    vr,
+    void*                               payload);
+
+
+
+  /**
    * @brief Data structure that contains information about the Orthanc core.
    **/
   typedef struct _OrthancPluginContext_t
@@ -2715,7 +2773,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param instance The instance of interest.
    * @return The AET if success, NULL if error.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
     OrthancPluginContext*              context,
@@ -2748,7 +2806,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param instance The instance of interest.
    * @return The size of the file, -1 in case of error.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
     OrthancPluginContext*             context,
@@ -2781,7 +2839,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param instance The instance of interest.
    * @return The pointer to the DICOM data, NULL in case of error.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetInstanceData(
     OrthancPluginContext*              context,
@@ -2817,7 +2875,7 @@
    * @param instance The instance of interest.
    * @return The NULL value in case of error, or a string containing the JSON file.
    * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
     OrthancPluginContext*              context,
@@ -2855,7 +2913,7 @@
    * @param instance The instance of interest.
    * @return The NULL value in case of error, or a string containing the JSON file.
    * This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
     OrthancPluginContext*              context,
@@ -2894,7 +2952,7 @@
    * @param instance The instance of interest.
    * @param metadata The metadata of interest.
    * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
     OrthancPluginContext*              context,
@@ -2935,7 +2993,7 @@
    *         returned string belongs to the instance object and must NOT be 
    *         deallocated. Please make a copy of the string if you wish to access 
    *         it later.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
     OrthancPluginContext*              context,
@@ -5107,7 +5165,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param instance The instance of interest.
    * @return The origin of the instance.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
     OrthancPluginContext*             context,
@@ -5183,8 +5241,11 @@
   /**
    * @brief Register a callback to handle the decoding of DICOM images.
    *
-   * This function registers a custom callback to the decoding of
-   * DICOM images, replacing the built-in decoder of Orthanc.
+   * This function registers a custom callback to decode DICOM images,
+   * extending the built-in decoder of Orthanc that uses
+   * DCMTK. Starting with Orthanc 1.7.0, the exact behavior is
+   * affected by the configuration option
+   * "BuiltinDecoderTranscoderOrder" of Orthanc.
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param callback The callback.
@@ -5316,6 +5377,7 @@
    * @param frameIndex The index of the frame of interest in a multi-frame image.
    * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
    * @ingroup Images
+   * @see OrthancPluginGetInstanceDecodedFrame()
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
     OrthancPluginContext*  context,
@@ -6791,6 +6853,7 @@
    * @see OrthancPluginCreateDicom()
    * @return The NULL value in case of error, or the JSON document. This string must
    * be freed by OrthancPluginFreeString().
+   * @deprecated OrthancPluginEncodeDicomWebJson2()
    * @ingroup Toolbox
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson(
@@ -6829,9 +6892,10 @@
    * @param dicom Pointer to the DICOM instance.
    * @param dicomSize Size of the DICOM instance.
    * @param callback Callback to set the value of the binary tags.
-   * @return The NULL value in case of error, or the JSON document. This string must
+   * @return The NULL value in case of error, or the XML document. This string must
    * be freed by OrthancPluginFreeString().
    * @see OrthancPluginCreateDicom()
+   * @deprecated OrthancPluginEncodeDicomWebXml2()
    * @ingroup Toolbox
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml(
@@ -6861,6 +6925,104 @@
   
 
 
+  typedef struct
+  {
+    char**                                target;
+    const void*                           dicom;
+    uint32_t                              dicomSize;
+    OrthancPluginDicomWebBinaryCallback2  callback;
+    void*                                 payload;
+  } _OrthancPluginEncodeDicomWeb2;
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb JSON.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb JSON representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @see OrthancPluginCreateDicom()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebJson2(
+    OrthancPluginContext*                 context,
+    const void*                           dicom,
+    uint32_t                              dicomSize,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb2 params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+    params.payload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebJson2, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb XML.
+   *
+   * This function converts a memory buffer containing a DICOM instance,
+   * into its DICOMweb XML representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom Pointer to the DICOM instance.
+   * @param dicomSize Size of the DICOM instance.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the XML document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @see OrthancPluginCreateDicom()
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginEncodeDicomWebXml2(
+    OrthancPluginContext*                 context,
+    const void*                           dicom,
+    uint32_t                              dicomSize,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginEncodeDicomWeb2 params;
+    params.target = &target;
+    params.dicom = dicom;
+    params.dicomSize = dicomSize;
+    params.callback = callback;
+    params.payload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_EncodeDicomWebXml2, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+  
+
+
   /**
    * @brief Callback executed when a HTTP header is received during a chunked transfer.
    *
@@ -7476,7 +7638,7 @@
    * @param instance The instance of interest.
    * @return The NULL value in case of error, or a string containing the
    * transfer syntax UID. This string must be freed by OrthancPluginFreeString().
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceTransferSyntaxUid(
     OrthancPluginContext*              context,
@@ -7511,7 +7673,7 @@
    * @param instance The instance of interest.
    * @return "1" if the DICOM instance contains pixel data, or "0" if
    * the tag is missing, or "-1" in the case of an error.
-   * @ingroup Callbacks
+   * @ingroup DicomInstance
    **/
   ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstancePixelData(
     OrthancPluginContext*             context,
@@ -7538,6 +7700,486 @@
   }
 
 
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance**  target;
+    const void*                   buffer;
+    uint32_t                      size;
+    const char*                   transferSyntax;
+  } _OrthancPluginCreateDicomInstance;
+
+  /**
+   * @brief Parse a DICOM instance.
+   *
+   * This function parses a memory buffer that contains a DICOM
+   * file. The function returns a new pointer to a data structure that
+   * is managed by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM instance.
+   * @param size The size of the memory buffer.
+   * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginCreateDicomInstance(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginCreateDicomInstance params;
+    params.target = &target;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance*   dicom;
+  } _OrthancPluginFreeDicomInstance;
+
+  /**
+   * @brief Free a DICOM instance.
+   *
+   * This function frees a DICOM instance that was parsed using
+   * OrthancPluginCreateDicomInstance().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param dicom The DICOM instance.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeDicomInstance(
+    OrthancPluginContext*        context, 
+    OrthancPluginDicomInstance*  dicom)
+  {
+    _OrthancPluginFreeDicomInstance params;
+    params.dicom = dicom;
+
+    context->InvokeService(context, _OrthancPluginService_FreeDicomInstance, &params);
+  }
+
+
+  typedef struct
+  {
+    uint32_t*                             targetUint32;
+    OrthancPluginMemoryBuffer*            targetBuffer;
+    OrthancPluginImage**                  targetImage;
+    char**                                targetStringToFree;
+    const OrthancPluginDicomInstance*     instance;
+    uint32_t                              frameIndex;
+    OrthancPluginDicomToJsonFormat        format;
+    OrthancPluginDicomToJsonFlags         flags;
+    uint32_t                              maxStringLength;
+    OrthancPluginDicomWebBinaryCallback2  dicomWebCallback;
+    void*                                 dicomWebPayload;
+  } _OrthancPluginAccessDicomInstance2;
+
+  /**
+   * @brief Get the number of frames in a DICOM instance.
+   *
+   * This function returns the number of frames that are part of a
+   * DICOM image managed by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The number of frames (will be zero in the case of an error).
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetInstanceFramesCount(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance)
+  {
+    uint32_t count;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetUint32 = &count;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceFramesCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  /**
+   * @brief Get the raw content of a frame in a DICOM instance.
+   *
+   * This function returns a memory buffer containing the raw content
+   * of a frame in a DICOM instance that is managed by the Orthanc
+   * core. This is notably useful for compressed transfer syntaxes, as
+   * it gives access to the embedded files (such as JPEG, JPEG-LS or
+   * JPEG2k). The Orthanc core transparently reassembles the fragments
+   * to extract the raw frame.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instance The instance of interest.
+   * @param frameIndex The index of the frame of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetInstanceRawFrame(
+    OrthancPluginContext*             context,
+    OrthancPluginMemoryBuffer*        target,
+    const OrthancPluginDicomInstance* instance,
+    uint32_t                          frameIndex)
+  {
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetBuffer = target;
+    params.instance = instance;
+    params.frameIndex = frameIndex;
+
+    return context->InvokeService(context, _OrthancPluginService_GetInstanceRawFrame, &params);
+  }
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is managed
+   * by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param frameIndex The index of the frame of interest.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginGetInstanceDecodedFrame(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance,
+    uint32_t                          frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetImage = &target;
+    params.instance = instance;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDecodedFrame, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  
+  /**
+   * @brief Parse and transcode a DICOM instance.
+   *
+   * This function parses a memory buffer that contains a DICOM file,
+   * then transcodes it to the given transfer syntax. The function
+   * returns a new pointer to a data structure that is managed by the
+   * Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM instance.
+   * @param size The size of the memory buffer.
+   * @param transferSyntax The transfer syntax UID for the transcoding.
+   * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginTranscodeDicomInstance(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size,
+    const char*            transferSyntax)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginCreateDicomInstance params;
+    params.target = &target;
+    params.buffer = buffer;
+    params.size = size;
+    params.transferSyntax = transferSyntax;
+
+    if (context->InvokeService(context, _OrthancPluginService_TranscodeDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  /**
+   * @brief Writes a DICOM instance to a memory buffer.
+   *
+   * This function returns a memory buffer containing the
+   * serialization of a DICOM instance that is managed by the Orthanc
+   * core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instance The instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSerializeDicomInstance(
+    OrthancPluginContext*             context,
+    OrthancPluginMemoryBuffer*        target,
+    const OrthancPluginDicomInstance* instance)
+  {
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetBuffer = target;
+    params.instance = instance;
+
+    return context->InvokeService(context, _OrthancPluginService_SerializeDicomInstance, &params);
+  }
+  
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as DICOM instance managed by the Orthanc
+   * core, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The DICOM instance of interest.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceAdvancedJson(
+    OrthancPluginContext*              context,
+    const OrthancPluginDicomInstance*  instance,
+    OrthancPluginDicomToJsonFormat     format,
+    OrthancPluginDicomToJsonFlags      flags, 
+    uint32_t                           maxStringLength)
+  {
+    char* result = NULL;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetStringToFree = &result;
+    params.instance = instance;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceAdvancedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb JSON.
+   *
+   * This function converts a DICOM instance that is managed by the
+   * Orthanc core, into its DICOMweb JSON representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The DICOM instance of interest.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the JSON document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebJson(
+    OrthancPluginContext*                 context,
+    const OrthancPluginDicomInstance*     instance,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginAccessDicomInstance2 params;
+    params.targetStringToFree = &target;
+    params.instance = instance;
+    params.dicomWebCallback = callback;
+    params.dicomWebPayload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+  
+
+  /**
+   * @brief Convert a DICOM instance to DICOMweb XML.
+   *
+   * This function converts a DICOM instance that is managed by the
+   * Orthanc core, into its DICOMweb XML representation.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The DICOM instance of interest.
+   * @param callback Callback to set the value of the binary tags.
+   * @param payload User payload.
+   * @return The NULL value in case of error, or the XML document. This string must
+   * be freed by OrthancPluginFreeString().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceDicomWebXml(
+    OrthancPluginContext*                 context,
+    const OrthancPluginDicomInstance*     instance,
+    OrthancPluginDicomWebBinaryCallback2  callback,
+    void*                                 payload)
+  {
+    char* target = NULL;
+    
+    _OrthancPluginAccessDicomInstance2 params;
+    params.targetStringToFree = &target;
+    params.instance = instance;
+    params.dicomWebCallback = callback;
+    params.dicomWebPayload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDicomWebXml, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Signature of a callback function to transcode a DICOM instance.
+   * @param transcoded Target memory buffer. It must be allocated by the
+   * plugin using OrthancPluginCreateMemoryBuffer().
+   * @param buffer Memory buffer containing the source DICOM instance.
+   * @param size Size of the source memory buffer.
+   * @param allowedSyntaxes A C array of possible transfer syntaxes UIDs for the
+   * result of the transcoding. The plugin must choose by itself the 
+   * transfer syntax that will be used for the resulting DICOM image.
+   * @param countSyntaxes The number of transfer syntaxes that are contained
+   * in the "allowedSyntaxes" array.
+   * @param allowNewSopInstanceUid Whether the transcoding plugin can select
+   * a transfer syntax that will change the SOP instance UID (or, in other 
+   * terms, whether the plugin can transcode using lossy compression).
+   * @return 0 if success (i.e. image successfully transcoded and stored into
+   * "transcoded"), or the error code if failure.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) (
+    OrthancPluginMemoryBuffer* transcoded /* out */,
+    const void*                buffer,
+    uint64_t                   size,
+    const char* const*         allowedSyntaxes,
+    uint32_t                   countSyntaxes,
+    uint8_t                    allowNewSopInstanceUid);
+
+
+  typedef struct
+  {
+    OrthancPluginTranscoderCallback callback;
+  } _OrthancPluginTranscoderCallback;
+
+  /**
+   * @brief Register a callback to handle the transcoding of DICOM images.
+   *
+   * This function registers a custom callback to transcode DICOM
+   * images, extending the built-in transcoder of Orthanc that uses
+   * DCMTK. The exact behavior is affected by the configuration option
+   * "BuiltinDecoderTranscoderOrder" of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterTranscoderCallback(
+    OrthancPluginContext*            context,
+    OrthancPluginTranscoderCallback  callback)
+  {
+    _OrthancPluginTranscoderCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterTranscoderCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    uint32_t                    size;
+  } _OrthancPluginCreateMemoryBuffer;
+
+  /**
+   * @brief Create a memory buffer.
+   *
+   * This function creates a memory buffer that is managed by the
+   * Orthanc core. The main use case of this function is for plugins
+   * that act as DICOM transcoders.
+   * 
+   * Your plugin should never call "free()" on the resulting memory
+   * buffer, as the C library that is used by the plugin is in general
+   * not the same as the one used by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param size Size of the memory buffer to be created.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateMemoryBuffer(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    uint32_t                    size)
+  {
+    _OrthancPluginCreateMemoryBuffer params;
+    params.target = target;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateMemoryBuffer, &params);
+  }
+  
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed May 20 16:42:44 2020 +0200
@@ -40,6 +40,11 @@
 #include <json/writer.h>
 
 
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
+#endif
+
+
 namespace OrthancPlugins
 {
   static OrthancPluginContext* globalContext_ = NULL;
@@ -125,6 +130,28 @@
   }
 
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  MemoryBuffer::MemoryBuffer(const void* buffer,
+                             size_t size)
+  {
+    uint32_t s = static_cast<uint32_t>(size);
+    if (static_cast<size_t>(s) != size)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
+             OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else
+    {
+      memcpy(buffer_.data, buffer, size);
+    }
+  }
+#endif
+
+
   void MemoryBuffer::Clear()
   {
     if (buffer_.data != NULL)
@@ -1045,7 +1072,7 @@
   }
 
 
-  const void* OrthancImage::GetBuffer() const
+  void* OrthancImage::GetBuffer() const
   {
     CheckImageAvailable();
     return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
@@ -1094,6 +1121,14 @@
   }
 
 
+  OrthancPluginImage* OrthancImage::Release()
+  {
+    CheckImageAvailable();
+    OrthancPluginImage* tmp = image_;
+    image_ = NULL;
+    return tmp;
+  }
+
 
 #if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
   FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
@@ -3177,4 +3212,184 @@
     delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
   }
 #endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#else
+  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance::DicomInstance(const void* buffer,
+                               size_t size) :
+    toFree_(true),
+    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
+  {
+    if (instance_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+  }
+#endif
+
+
+  DicomInstance::~DicomInstance()
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    if (toFree_ &&
+        instance_ != NULL)
+    {
+      OrthancPluginFreeDicomInstance(
+        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
+    }
+#endif
+  }
+
+  
+  std::string DicomInstance::GetRemoteAet() const
+  {
+    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return std::string(s);
+    }
+  }
+
+
+  void DicomInstance::GetJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+  
+
+  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  std::string DicomInstance::GetTransferSyntaxUid() const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
+
+    std::string result;
+    s.ToString(result);
+    return result;
+  }
+#endif
+
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  bool DicomInstance::HasPixelData() const
+  {
+    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
+    if (result < 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  void DicomInstance::GetRawFrame(std::string& target,
+                                  unsigned int frameIndex) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
+      GetGlobalContext(), *buffer, instance_, frameIndex);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
+  {
+    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
+      GetGlobalContext(), instance_, frameIndex);
+
+    if (image == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return new OrthancImage(image);
+    }
+  }
+#endif  
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void DicomInstance::Serialize(std::string& target) const
+  {
+    MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
+      GetGlobalContext(), *buffer, instance_);
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      buffer.ToString(target);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+  
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance* DicomInstance::Transcode(const void* buffer,
+                                          size_t size,
+                                          const std::string& transferSyntax)
+  {
+    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
+      GetGlobalContext(), buffer, size, transferSyntax.c_str());
+
+    if (instance == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
+      result->toFree_ = true;
+      return result.release();
+    }
+  }
+#endif
 }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed May 06 08:40:48 2020 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed May 20 16:42:44 2020 +0200
@@ -47,6 +47,13 @@
 
 
 
+/**
+ * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
+ * backward compatibility with Orthanc SDK <= 1.3.0.
+ * 
+ *   $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
+ *
+ **/
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
 #define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision)        \
   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||                      \
@@ -139,6 +146,13 @@
   public:
     MemoryBuffer();
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    // This constructor makes a copy of the given buffer in the memory
+    // handled by the Orthanc core
+    MemoryBuffer(const void* buffer,
+                 size_t size);
+#endif
+
     ~MemoryBuffer()
     {
       Clear();
@@ -379,8 +393,7 @@
                  uint32_t                  width,
                  uint32_t                  height,
                  uint32_t                  pitch,
-                 void*                     buffer
-      );
+                 void*                     buffer);
 
     ~OrthancImage()
     {
@@ -405,7 +418,7 @@
 
     unsigned int GetPitch() const;
     
-    const void* GetBuffer() const;
+    void* GetBuffer() const;
 
     const OrthancPluginImage* GetObject() const
     {
@@ -421,6 +434,10 @@
 
     void AnswerJpegImage(OrthancPluginRestOutput* output,
                          uint8_t quality) const;
+    
+    void* GetWriteableBuffer();
+
+    OrthancPluginImage* Release();
   };
 
 
@@ -1128,4 +1145,86 @@
     static void Destructor(void* rawHandler);
   };
 #endif
+
+
+  class DicomInstance : public boost::noncopyable
+  {
+  private:
+    bool toFree_;
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    const OrthancPluginDicomInstance*  instance_;
+#else
+    OrthancPluginDicomInstance*  instance_;
+#endif
+    
+  public:
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    DicomInstance(const OrthancPluginDicomInstance* instance);
+#else
+    DicomInstance(OrthancPluginDicomInstance* instance);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    DicomInstance(const void* buffer,
+                  size_t size);
+#endif
+
+    ~DicomInstance();
+
+    std::string GetRemoteAet() const;
+
+    const void* GetBuffer() const
+    {
+      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
+    }
+
+    size_t GetSize() const
+    {
+      return OrthancPluginGetInstanceSize(GetGlobalContext(), instance_);
+    }
+
+    void GetJson(Json::Value& target) const;
+
+    void GetSimplifiedJson(Json::Value& target) const;
+
+    OrthancPluginInstanceOrigin GetOrigin() const
+    {
+      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
+    }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    std::string GetTransferSyntaxUid() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    bool HasPixelData() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    unsigned int GetFramesCount() const
+    {
+      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void GetRawFrame(std::string& target,
+                     unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void Serialize(std::string& target) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    static DicomInstance* Transcode(const void* buffer,
+                                    size_t size,
+                                    const std::string& transferSyntax);
+#endif
+  };
 }
--- a/Plugins/Samples/GdcmDecoder/CMakeLists.txt	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-cmake_minimum_required(VERSION 2.8)
-
-project(GdcmDecoder)
-
-SET(GDCM_DECODER_VERSION "0.0" CACHE STRING "Version of the plugin")
-SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
-SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
-SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
-
-set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
-include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
-
-find_package(GDCM REQUIRED)
-if (GDCM_FOUND)
-  include(${GDCM_USE_FILE})
-  set(GDCM_LIBRARIES gdcmCommon gdcmMSFF)
-else(GDCM_FOUND)
-  message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?")
-endif(GDCM_FOUND)
-
-add_definitions(-DGDCM_DECODER_VERSION="${GDCM_DECODER_VERSION}")
-
-add_library(GdcmDecoder SHARED
-  ${BOOST_SOURCES}
-  GdcmDecoderCache.cpp
-  GdcmImageDecoder.cpp
-  OrthancImageWrapper.cpp
-  Plugin.cpp
-  )
-
-target_link_libraries(GdcmDecoder ${GDCM_LIBRARIES})
--- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +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.
- * 
- * 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 "GdcmDecoderCache.h"
-
-#include "../../../Core/Compatibility.h"
-#include "OrthancImageWrapper.h"
-
-namespace OrthancPlugins
-{
-  std::string GdcmDecoderCache::ComputeMd5(OrthancPluginContext* context,
-                                           const void* dicom,
-                                           size_t size)
-  {
-    std::string result;
-
-    char* md5 = OrthancPluginComputeMd5(context, dicom, size);
-
-    if (md5 == NULL)
-    {
-      throw std::runtime_error("Cannot compute MD5 hash");
-    }
-
-    bool ok = false;
-    try
-    {
-      result.assign(md5);
-      ok = true;
-    }
-    catch (...)
-    {
-    }
-
-    OrthancPluginFreeString(context, md5);
-
-    if (!ok)
-    {
-      throw std::runtime_error("Not enough memory");
-    }
-    else
-    {    
-      return result;
-    }
-  }
-
-
-  OrthancImageWrapper* GdcmDecoderCache::Decode(OrthancPluginContext* context,
-                                                const void* dicom,
-                                                const uint32_t size,
-                                                uint32_t frameIndex)
-  {
-    std::string md5 = ComputeMd5(context, dicom, size);
-
-    // First check whether the previously decoded image is the same
-    // as this one
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (decoder_.get() != NULL &&
-          size_ == size &&
-          md5_ == md5)
-      {
-        // This is the same image: Reuse the previous decoding
-        return new OrthancImageWrapper(context, decoder_->Decode(context, frameIndex));
-      }
-    }
-
-    // This is not the same image
-    std::unique_ptr<GdcmImageDecoder> decoder(new GdcmImageDecoder(dicom, size));
-    std::unique_ptr<OrthancImageWrapper> image(new OrthancImageWrapper(context, decoder->Decode(context, frameIndex)));
-
-    {
-      // Cache the newly created decoder for further use
-      boost::mutex::scoped_lock lock(mutex_);
-      decoder_.reset(decoder.release());
-      size_ = size;
-      md5_ = md5;
-    }
-
-    return image.release();
-  }
-}
--- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +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.
- * 
- * 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/Compatibility.h"
-#include "GdcmImageDecoder.h"
-#include "OrthancImageWrapper.h"
-
-#include <boost/thread.hpp>
-
-
-namespace OrthancPlugins
-{
-  class GdcmDecoderCache : public boost::noncopyable
-  {
-  private:
-    boost::mutex   mutex_;
-    std::unique_ptr<OrthancPlugins::GdcmImageDecoder>  decoder_;
-    size_t       size_;
-    std::string  md5_;
-
-    static std::string ComputeMd5(OrthancPluginContext* context,
-                                  const void* dicom,
-                                  size_t size);
-
-  public:
-    GdcmDecoderCache() : size_(0)
-    {
-    }
-
-    OrthancImageWrapper* Decode(OrthancPluginContext* context,
-                                const void* dicom,
-                                const uint32_t size,
-                                uint32_t frameIndex);
-  };
-}
--- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,409 +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.
- * 
- * 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 "GdcmImageDecoder.h"
-
-#include "../../../Core/Compatibility.h"
-#include "OrthancImageWrapper.h"
-
-#include <gdcmImageReader.h>
-#include <gdcmImageApplyLookupTable.h>
-#include <gdcmImageChangePlanarConfiguration.h>
-#include <gdcmImageChangePhotometricInterpretation.h>
-#include <stdexcept>
-#include <boost/iostreams/stream.hpp>
-#include <boost/iostreams/device/array.hpp>
-
-
-namespace OrthancPlugins
-{
-  struct GdcmImageDecoder::PImpl
-  {
-    const void*           dicom_;
-    size_t                size_;
-
-    gdcm::ImageReader reader_;
-    std::unique_ptr<gdcm::ImageApplyLookupTable> lut_;
-    std::unique_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_;
-    std::unique_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_;
-    std::string decoded_;
-
-    PImpl(const void* dicom,
-          size_t size) :
-      dicom_(dicom),
-      size_(size)
-    {
-    }
-
-
-    const gdcm::DataSet& GetDataSet() const
-    {
-      return reader_.GetFile().GetDataSet();
-    }
-
-
-    const gdcm::Image& GetImage() const
-    {
-      if (interleaved_.get() != NULL)
-      {
-        return interleaved_->GetOutput();
-      }
-
-      if (lut_.get() != NULL)
-      {
-        return lut_->GetOutput();
-      }
-
-      if (photometric_.get() != NULL)
-      {
-        return photometric_->GetOutput();
-      }
-
-      return reader_.GetImage();
-    }
-
-
-    void Decode()
-    {
-      // Change photometric interpretation or apply LUT, if required
-      {
-        const gdcm::Image& image = GetImage();
-        if (image.GetPixelFormat().GetSamplesPerPixel() == 1 &&
-            image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::PALETTE_COLOR)
-        {
-          lut_.reset(new gdcm::ImageApplyLookupTable());
-          lut_->SetInput(image);
-          if (!lut_->Apply())
-          {
-            throw std::runtime_error( "GDCM cannot apply the lookup table");
-          }
-        }
-        else if (image.GetPixelFormat().GetSamplesPerPixel() == 1)
-        {
-          if (image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME1 &&
-              image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2)
-          {
-            photometric_.reset(new gdcm::ImageChangePhotometricInterpretation());
-            photometric_->SetInput(image);
-            photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2);
-            if (!photometric_->Change() ||
-                GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2)
-            {
-              throw std::runtime_error("GDCM cannot change the photometric interpretation");
-            }
-          }      
-        }
-        else 
-        {
-          if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
-              image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB &&
-              image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::YBR_FULL &&
-              (image.GetTransferSyntax() != gdcm::TransferSyntax::JPEG2000Lossless ||
-               image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::YBR_RCT))
-          {
-            photometric_.reset(new gdcm::ImageChangePhotometricInterpretation());
-            photometric_->SetInput(image);
-            photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::RGB);
-            if (!photometric_->Change() ||
-                GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB)
-            {
-              throw std::runtime_error("GDCM cannot change the photometric interpretation");
-            }
-          }
-        }
-      }
-
-      // Possibly convert planar configuration to interleaved
-      {
-        const gdcm::Image& image = GetImage();
-        if (image.GetPlanarConfiguration() != 0 && 
-            image.GetPixelFormat().GetSamplesPerPixel() != 1)
-        {
-          interleaved_.reset(new gdcm::ImageChangePlanarConfiguration());
-          interleaved_->SetInput(image);
-          if (!interleaved_->Change() ||
-              GetImage().GetPlanarConfiguration() != 0)
-          {
-            throw std::runtime_error("GDCM cannot change the planar configuration to interleaved");
-          }
-        }
-      }
-    }
-  };
-
-  GdcmImageDecoder::GdcmImageDecoder(const void* dicom,
-                                     size_t size) :
-    pimpl_(new PImpl(dicom, size))
-  {
-    // Setup a stream to the memory buffer
-    using namespace boost::iostreams;
-    basic_array_source<char> source(reinterpret_cast<const char*>(dicom), size);
-    stream<basic_array_source<char> > stream(source);
-
-    // Parse the DICOM instance using GDCM
-    pimpl_->reader_.SetStream(stream);
-    if (!pimpl_->reader_.Read())
-    {
-      throw std::runtime_error("Bad file format");
-    }
-
-    pimpl_->Decode();
-  }
-
-
-  OrthancPluginPixelFormat GdcmImageDecoder::GetFormat() const
-  {
-    const gdcm::Image& image = pimpl_->GetImage();
-
-    if (image.GetPixelFormat().GetSamplesPerPixel() == 1 &&
-        (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME1 ||
-         image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME2))
-    {
-      switch (image.GetPixelFormat())
-      {
-        case gdcm::PixelFormat::UINT16:
-          return OrthancPluginPixelFormat_Grayscale16;
-
-        case gdcm::PixelFormat::INT16:
-          return OrthancPluginPixelFormat_SignedGrayscale16;
-
-        case gdcm::PixelFormat::UINT8:
-          return OrthancPluginPixelFormat_Grayscale8;
-
-        default:
-          throw std::runtime_error("Unsupported pixel format");
-      }
-    }
-    else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
-             (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::RGB ||
-              image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::YBR_FULL ||
-              image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::YBR_RCT))
-    {
-      switch (image.GetPixelFormat())
-      {
-        case gdcm::PixelFormat::UINT8:
-          return OrthancPluginPixelFormat_RGB24;
-
-        case gdcm::PixelFormat::UINT16:
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 1)
-          return OrthancPluginPixelFormat_RGB48;
-#else
-          throw std::runtime_error("RGB48 pixel format is only supported if compiled against Orthanc SDK >= 1.3.1");
-#endif
-          
-        default:
-          break;
-      }      
-    }
-
-    throw std::runtime_error("Unsupported pixel format");
-  }
-
-
-  unsigned int GdcmImageDecoder::GetWidth() const
-  {
-    return pimpl_->GetImage().GetColumns();
-  }
-
-
-  unsigned int GdcmImageDecoder::GetHeight() const
-  {
-    return pimpl_->GetImage().GetRows();
-  }
-
-  
-  unsigned int GdcmImageDecoder::GetFramesCount() const
-  {
-    return pimpl_->GetImage().GetDimension(2);
-  }
-
-
-  size_t GdcmImageDecoder::GetBytesPerPixel(OrthancPluginPixelFormat format)
-  {
-    switch (format)
-    {
-      case OrthancPluginPixelFormat_Grayscale8:
-        return 1;
-
-      case OrthancPluginPixelFormat_Grayscale16:
-      case OrthancPluginPixelFormat_SignedGrayscale16:
-        return 2;
-
-      case OrthancPluginPixelFormat_RGB24:
-        return 3;
-
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 1)
-      case OrthancPluginPixelFormat_RGB48:
-        return 6;
-#endif
-
-      default:
-        throw std::runtime_error("Unsupport pixel format");
-    }
-  }
-
-  static void ConvertYbrToRgb(uint8_t rgb[3],
-                              const uint8_t ybr[3])
-  {
-    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2
-    // https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
-    
-    // TODO - Check out the outcome of Mathieu's discussion about
-    // truncation of YCbCr-to-RGB conversion:
-    // https://groups.google.com/forum/#!msg/comp.protocols.dicom/JHuGeyWbTz8/ARoTWrJzAQAJ
-
-    const float Y  = ybr[0];
-    const float Cb = ybr[1];
-    const float Cr = ybr[2];
-
-    const float result[3] = {
-      Y                             + 1.402f    * (Cr - 128.0f),
-      Y - 0.344136f * (Cb - 128.0f) - 0.714136f * (Cr - 128.0f),
-      Y + 1.772f    * (Cb - 128.0f)
-    };
-
-    for (uint8_t i = 0; i < 3 ; i++)
-    {
-      if (result[i] < 0)
-      {
-        rgb[i] = 0;
-      }
-      else if (result[i] > 255)
-      {
-        rgb[i] = 255;
-      }
-      else
-      {
-        rgb[i] = static_cast<uint8_t>(result[i]);
-      }
-    }    
-  }
-
-  
-  static void FixPhotometricInterpretation(OrthancImageWrapper& image,
-                                           gdcm::PhotometricInterpretation interpretation)
-  {
-    switch (interpretation)
-    {
-      case gdcm::PhotometricInterpretation::MONOCHROME1:
-      case gdcm::PhotometricInterpretation::MONOCHROME2:
-      case gdcm::PhotometricInterpretation::RGB:
-        return;
-
-      case gdcm::PhotometricInterpretation::YBR_FULL:
-      {
-        // Fix for Osimis issue WVB-319: Some images are not loading in US_MF
-
-        uint32_t width = image.GetWidth();
-        uint32_t height = image.GetHeight();
-        uint32_t pitch = image.GetPitch();
-        uint8_t* buffer = reinterpret_cast<uint8_t*>(image.GetBuffer());
-        
-        if (image.GetFormat() != OrthancPluginPixelFormat_RGB24 ||
-            pitch < 3 * width)
-        {
-          throw std::runtime_error("Internal error");
-        }
-
-        for (uint32_t y = 0; y < height; y++)
-        {
-          uint8_t* p = buffer + y * pitch;
-          for (uint32_t x = 0; x < width; x++, p += 3)
-          {
-            const uint8_t ybr[3] = { p[0], p[1], p[2] };
-            uint8_t rgb[3];
-            ConvertYbrToRgb(rgb, ybr);
-            p[0] = rgb[0];
-            p[1] = rgb[1];
-            p[2] = rgb[2];
-          }
-        }
-
-        return;
-      }
-
-      default:
-        throw std::runtime_error("Unsupported output photometric interpretation");
-    }    
-  }
-
-
-  OrthancPluginImage* GdcmImageDecoder::Decode(OrthancPluginContext* context,
-                                               unsigned int frameIndex) const
-  {
-    unsigned int frames = GetFramesCount();
-    unsigned int width = GetWidth();
-    unsigned int height = GetHeight();
-    OrthancPluginPixelFormat format = GetFormat();
-    size_t bpp = GetBytesPerPixel(format);
-
-    if (frameIndex >= frames)
-    {
-      throw std::runtime_error("Inexistent frame index");
-    }
-
-    std::string& decoded = pimpl_->decoded_;
-    OrthancImageWrapper target(context, format, width, height);
-
-    if (width == 0 ||
-        height == 0)
-    {
-      return target.Release();
-    }
-
-    if (decoded.empty())
-    {
-      decoded.resize(pimpl_->GetImage().GetBufferLength());
-      if (!pimpl_->GetImage().GetBuffer(&decoded[0]))
-      {
-        throw std::runtime_error("Image not properly decoded to a memory buffer");
-      }
-    }
-
-    const void* sourceBuffer = &decoded[0];
-
-    if (target.GetPitch() == bpp * width &&
-        frames == 1)
-    {
-      assert(decoded.size() == target.GetPitch() * target.GetHeight());      
-      memcpy(target.GetBuffer(), sourceBuffer, decoded.size());
-    }
-    else 
-    {
-      size_t targetPitch = target.GetPitch();
-      size_t sourcePitch = width * bpp;
-
-      const char* a = &decoded[sourcePitch * height * frameIndex];
-      char* b = target.GetBuffer();
-
-      for (uint32_t y = 0; y < height; y++)
-      {
-        memcpy(b, a, sourcePitch);
-        a += sourcePitch;
-        b += targetPitch;
-      }
-    }
-    
-    FixPhotometricInterpretation(target, pimpl_->GetImage().GetPhotometricInterpretation());
-                                 
-    return target.Release();
-  }
-}
--- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +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.
- * 
- * 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 <orthanc/OrthancCPlugin.h>
-#include <stdint.h>
-#include <boost/noncopyable.hpp>
-#include <boost/shared_ptr.hpp>
-
-
-// This is for compatibility with Orthanc SDK <= 1.3.0
-#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
-#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \
-  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||               \
-   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&             \
-    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||             \
-     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&           \
-      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
-#endif
-
-
-namespace OrthancPlugins
-{
-  class GdcmImageDecoder : public boost::noncopyable
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-  
-  public:
-    GdcmImageDecoder(const void* dicom,
-                     size_t size);
-
-    OrthancPluginPixelFormat GetFormat() const;
-
-    unsigned int GetWidth() const;
-
-    unsigned int GetHeight() const;
-
-    unsigned int GetFramesCount() const;
-
-    static size_t GetBytesPerPixel(OrthancPluginPixelFormat format);
-
-    OrthancPluginImage* Decode(OrthancPluginContext* context,
-                               unsigned int frameIndex) const;
-  };
-}
--- a/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +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.
- * 
- * 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 "OrthancImageWrapper.h"
-
-#include <stdexcept>
-
-namespace OrthancPlugins
-{
-  OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context,
-                                           OrthancPluginPixelFormat format,
-                                           uint32_t width,
-                                           uint32_t height) :
-    context_(context)
-  {
-    image_ = OrthancPluginCreateImage(context_, format, width, height);
-    if (image_ == NULL)
-    {
-      throw std::runtime_error("Cannot create an image");
-    }
-  }
-
-
-  OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context,
-                                           OrthancPluginImage* image) :
-    context_(context),
-    image_(image)
-  {
-    if (image_ == NULL)
-    {
-      throw std::runtime_error("Invalid image returned by the core of Orthanc");
-    }
-  }
-
-
-
-  OrthancImageWrapper::~OrthancImageWrapper()
-  {
-    if (image_ != NULL)
-    {
-      OrthancPluginFreeImage(context_, image_);
-    }
-  }
-
-
-  OrthancPluginImage* OrthancImageWrapper::Release()
-  {
-    OrthancPluginImage* tmp = image_;
-    image_ = NULL;
-    return tmp;
-  }
-
-
-  uint32_t OrthancImageWrapper::GetWidth()
-  {
-    return OrthancPluginGetImageWidth(context_, image_);
-  }
-
-
-  uint32_t OrthancImageWrapper::GetHeight()
-  {
-    return OrthancPluginGetImageHeight(context_, image_);
-  }
-
-
-  uint32_t OrthancImageWrapper::GetPitch()
-  {
-    return OrthancPluginGetImagePitch(context_, image_);
-  }
-
-
-  OrthancPluginPixelFormat OrthancImageWrapper::GetFormat()
-  {
-    return OrthancPluginGetImagePixelFormat(context_, image_);
-  }
-
-
-  char* OrthancImageWrapper::GetBuffer()
-  {
-    return reinterpret_cast<char*>(OrthancPluginGetImageBuffer(context_, image_));
-  }
-}
--- a/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.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.
- * 
- * 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 <orthanc/OrthancCPlugin.h>
-
-#include "GdcmImageDecoder.h"
-
-namespace OrthancPlugins
-{
-  class OrthancImageWrapper
-  {
-  private:
-    OrthancPluginContext*  context_;
-    OrthancPluginImage*    image_;
-
-  public:
-    OrthancImageWrapper(OrthancPluginContext* context,
-                        OrthancPluginPixelFormat format,
-                        uint32_t width,
-                        uint32_t height);
-
-    OrthancImageWrapper(OrthancPluginContext* context,
-                        OrthancPluginImage* image);  // Takes ownership
-
-    ~OrthancImageWrapper();
-
-    OrthancPluginContext* GetContext()
-    {
-      return context_;
-    }
-
-    OrthancPluginImage* Release();
-
-    uint32_t GetWidth();
-
-    uint32_t GetHeight();
-
-    uint32_t GetPitch();
-
-    OrthancPluginPixelFormat GetFormat();
-
-    char* GetBuffer();
-  };
-}
--- a/Plugins/Samples/GdcmDecoder/Plugin.cpp	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +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.
- * 
- * 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 "../../../Core/Compatibility.h"
-#include "GdcmDecoderCache.h"
-#include "OrthancImageWrapper.h"
-
-#include <orthanc/OrthancCPlugin.h>
-
-static OrthancPluginContext* context_ = NULL;
-static OrthancPlugins::GdcmDecoderCache  cache_;
-
-
-static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target,
-                                                  const void* dicom,
-                                                  const uint32_t size,
-                                                  uint32_t frameIndex)
-{
-  try
-  {
-    std::unique_ptr<OrthancPlugins::OrthancImageWrapper> image;
-
-#if 0
-    // Do not use the cache
-    OrthancPlugins::GdcmImageDecoder decoder(dicom, size);
-    image.reset(new OrthancPlugins::OrthancImageWrapper(context_, decoder.Decode(context_, frameIndex)));
-#else
-    image.reset(cache_.Decode(context_, dicom, size, frameIndex));
-#endif
-
-    *target = image->Release();
-
-    return OrthancPluginErrorCode_Success;
-  }
-  catch (std::runtime_error& e)
-  {
-    *target = NULL;
-
-    std::string s = "Cannot decode image using GDCM: " + std::string(e.what());
-    OrthancPluginLogInfo(context_, s.c_str());
-    return OrthancPluginErrorCode_Plugin;
-  }
-}
-
-
-
-extern "C"
-{
-  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
-  {
-    context_ = context;
-    OrthancPluginLogWarning(context_, "Initializing the advanced decoder of medical images using GDCM");
-
-
-    /* Check the version of the Orthanc core */
-    if (OrthancPluginCheckVersion(context_) == 0)
-    {
-      char info[1024];
-      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
-              context_->orthancVersion,
-              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
-              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
-      OrthancPluginLogError(context_, info);
-      return -1;
-    }
-
-    OrthancPluginSetDescription(context_, "Advanced decoder of medical images using GDCM.");
-    OrthancPluginRegisterDecodeImageCallback(context_, DecodeImageCallback);
-
-    return 0;
-  }
-
-
-  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
-  {
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
-  {
-    return "gdcm-decoder";
-  }
-
-
-  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
-  {
-    return GDCM_DECODER_VERSION;
-  }
-}
--- a/Plugins/Samples/GdcmDecoder/README	Wed May 06 08:40:48 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-This sample shows how to replace the decoder of DICOM images that is
-built in Orthanc, by the GDCM library.
-
-A production-ready version of this sample, is available in the
-offical Web viewer plugin:
-http://www.orthanc-server.com/static.php?page=web-viewer
--- a/Resources/CMake/DcmtkConfiguration.cmake	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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,11 @@
   # 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/IDicomTranscoder.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	Wed May 20 16:42:44 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	Wed May 20 16:42:44 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,35 @@
 
   // 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 over the DICOM protocol, if the remote modality
+  // does not support compressed transfer syntaxes (new in Orthanc 1.7.0).
+  "TranscodeDicomProtocol" : true,
+
+  // If some plugin to decode/transcode DICOM instances is installed,
+  // this option specifies whether the built-in decoder/transcoder of
+  // Orthanc (that uses DCMTK) is applied before or after the plugins,
+  // or is not applied at all (new in Orthanc 1.7.0). The allowed
+  // values for this option are "After" (default value, corresponding
+  // to the behavior of Orthanc <= 1.6.1), "Before", or "Disabled".
+  "BuiltinDecoderTranscoderOrder" : "After",
+
+  // If this option is set, Orthanc will transparently transcode any
+  // incoming DICOM instance to the given transfer syntax before
+  // storing it into its database. Beware that this might result in
+  // high CPU usage (if transcoding to some compressed transfer
+  // syntax), or in higher disk consumption (if transcoding to an
+  // uncompressed syntax). Also, beware that transcoding to a transfer
+  // syntax with lossy compression (notably JPEG) will change the
+  // "SOPInstanceUID" DICOM tag, and thus the Orthanc identifier at
+  // the instance level, which might break external workflow.
+  /**
+     "IngestTranscoding" : "1.2.840.10008.1.2",
+  **/
+
+  // The compression level that is used when transcoding to one of the
+  // lossy/JPEG transfer syntaxes (integer between 1 and 100).
+  "DicomLossyTranscodingQuality" : 90
 }
--- a/Resources/DicomTransferSyntaxes.json	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/DicomTransferSyntaxes.json	Wed May 20 16:42:44 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	Wed May 20 16:42:44 2020 +0200
@@ -0,0 +1,967 @@
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmFileFormat& dicom,
+                                           DicomTransferSyntax syntax)
+  {
+    E_TransferSyntax xfer;
+    if (!LookupDcmtkTransferSyntax(xfer, syntax))
+    {
+      return false;
+    }
+    else if (!dicom.validateMetaInfo(xfer).good())
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot setup the transfer syntax to write a DICOM instance");
+    }
+    else
+    {
+      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
+    }
+  }
+
+
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmFileFormat& dicom)
+  {
+    E_TransferSyntax xfer = dicom.getDataset()->getCurrentXfer();
+    if (xfer == EXS_Unknown)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot write a DICOM instance with unknown transfer syntax");
+    }
+    else if (!dicom.validateMetaInfo(xfer).good())
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot setup the transfer syntax to write a DICOM instance");
+    }
+    else
+    {
+      return SaveToMemoryBufferInternal(buffer, dicom, xfer);
+    }
+  }
+
+
+
+
+
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+
+#include "../Core/DicomParsing/Internals/DicomFrameIndex.h"
+
+namespace Orthanc
+{
+  class IParsedDicomImage : public boost::noncopyable
+  {
+  public:
+    virtual ~IParsedDicomImage()
+    {
+    }
+
+    virtual DicomTransferSyntax GetTransferSyntax() = 0;
+
+    virtual std::string GetSopClassUid() = 0;
+
+    virtual std::string GetSopInstanceUid() = 0;
+
+    virtual unsigned int GetFramesCount() = 0;
+
+    // Can return NULL, for compressed transfer syntaxes
+    virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) = 0;
+    
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) = 0;
+    
+    virtual void WriteToMemoryBuffer(std::string& target) = 0;
+  };
+
+
+  class IDicomImageReader : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomImageReader()
+    {
+    }
+
+    virtual IParsedDicomImage* Read(const void* data,
+                                    size_t size) = 0;
+
+    virtual IParsedDicomImage* Transcode(const void* data,
+                                         size_t size,
+                                         DicomTransferSyntax syntax,
+                                         bool allowNewSopInstanceUid) = 0;
+  };
+
+
+  class DcmtkImageReader : public IDicomImageReader
+  {
+  private:
+    class Image : public IParsedDicomImage
+    {
+    private:
+      std::unique_ptr<DcmFileFormat>    dicom_;
+      std::unique_ptr<DicomFrameIndex>  index_;
+      DicomTransferSyntax               transferSyntax_;
+      std::string                       sopClassUid_;
+      std::string                       sopInstanceUid_;
+
+      static std::string GetStringTag(DcmDataset& dataset,
+                                      const DcmTagKey& tag)
+      {
+        const char* value = NULL;
+
+        if (!dataset.findAndGetString(tag, value).good() ||
+            value == NULL)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Missing SOP class/instance UID in DICOM instance");
+        }
+        else
+        {
+          return std::string(value);
+        }
+      }
+
+    public:
+      Image(DcmFileFormat* dicom,
+            DicomTransferSyntax syntax) :
+        dicom_(dicom),
+        transferSyntax_(syntax)
+      {
+        if (dicom == NULL ||
+            dicom_->getDataset() == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        DcmDataset& dataset = *dicom_->getDataset();
+        index_.reset(new DicomFrameIndex(dataset));
+
+        sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
+        sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
+      }
+
+      virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
+      {
+        return transferSyntax_;
+      }
+
+      virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
+      {
+        return sopClassUid_;
+      }
+    
+      virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
+      {
+        return sopInstanceUid_;
+      }
+
+      virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
+      {
+        return index_->GetFramesCount();
+      }
+
+      virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
+      {
+        assert(dicom_.get() != NULL);
+        if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, transferSyntax_))
+        {
+          throw OrthancException(ErrorCode_InternalError,
+                                 "Cannot write the DICOM instance to a memory buffer");
+        }
+      }
+
+      virtual ImageAccessor* GetUncompressedFrame(unsigned int frame) ORTHANC_OVERRIDE
+      {
+        assert(dicom_.get() != NULL &&
+               dicom_->getDataset() != NULL);
+        return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
+      }
+
+      virtual void GetCompressedFrame(std::string& target,
+                                      unsigned int frame) ORTHANC_OVERRIDE
+      {
+        assert(index_.get() != NULL);
+        index_->GetRawFrame(target, frame);
+      }
+    };
+
+    unsigned int lossyQuality_;
+
+    static DicomTransferSyntax DetectTransferSyntax(DcmFileFormat& dicom)
+    {
+      if (dicom.getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+        
+      DcmDataset& dataset = *dicom.getDataset();
+
+      E_TransferSyntax xfer = dataset.getCurrentXfer();
+      if (xfer == EXS_Unknown)
+      {
+        dataset.updateOriginalXfer();
+        xfer = dataset.getCurrentXfer();
+        if (xfer == EXS_Unknown)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Cannot determine the transfer syntax of the DICOM instance");
+        }
+      }
+
+      DicomTransferSyntax syntax;
+      if (FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, xfer))
+      {
+        return syntax;
+      }
+      else
+      {
+        throw OrthancException(
+          ErrorCode_BadFileFormat,
+          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
+      }
+    }
+
+
+    static uint16_t GetBitsStored(DcmFileFormat& dicom)
+    {
+      if (dicom.getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      uint16_t bitsStored;
+      if (dicom.getDataset()->findAndGetUint16(DCM_BitsStored, bitsStored).good())
+      {
+        return bitsStored;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing \"Bits Stored\" tag in DICOM instance");
+      }      
+    }
+    
+      
+  public:
+    DcmtkImageReader() :
+      lossyQuality_(90)
+    {
+    }
+
+    void SetLossyQuality(unsigned int quality)
+    {
+      if (quality <= 0 ||
+          quality > 100)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        lossyQuality_ = quality;
+      }
+    }
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+
+    virtual IParsedDicomImage* Read(const void* data,
+                                    size_t size)
+    {
+      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
+      if (dicom.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      DicomTransferSyntax transferSyntax = DetectTransferSyntax(*dicom);
+
+      return new Image(dicom.release(), transferSyntax);
+    }
+
+    virtual IParsedDicomImage* Transcode(const void* data,
+                                         size_t size,
+                                         DicomTransferSyntax syntax,
+                                         bool allowNewSopInstanceUid)
+    {
+      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(data, size));
+      if (dicom.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      const uint16_t bitsStored = GetBitsStored(*dicom);
+
+      if (syntax == DetectTransferSyntax(*dicom))
+      {
+        // No transcoding is needed
+        return new Image(dicom.release(), syntax);
+      }
+      
+      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+
+      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+      
+      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+
+      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
+      {
+        return new Image(dicom.release(), syntax);
+      }
+
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
+          allowNewSopInstanceUid &&
+          bitsStored == 8)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        
+        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy))
+        {
+          return new Image(dicom.release(), syntax);
+        }
+      }
+#endif
+      
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
+          allowNewSopInstanceUid &&
+          bitsStored <= 12)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy))
+        {
+          return new Image(dicom.release(), syntax);
+        }
+      }
+#endif
+
+      //LOG(INFO) << "Unable to transcode DICOM image using the built-in reader";
+      return NULL;
+    }
+  };
+  
+
+  
+  class IDicomTranscoder1 : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomTranscoder1()
+    {
+    }
+
+    virtual DcmFileFormat& GetDicom() = 0;
+
+    virtual DicomTransferSyntax GetTransferSyntax() = 0;
+
+    virtual std::string GetSopClassUid() = 0;
+
+    virtual std::string GetSopInstanceUid() = 0;
+
+    virtual unsigned int GetFramesCount() = 0;
+
+    virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0;
+
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) = 0;
+
+    // NB: Transcoding can change the value of "GetSopInstanceUid()"
+    // and "GetTransferSyntax()" if lossy compression is applied
+    virtual bool Transcode(std::string& target,
+                           DicomTransferSyntax syntax,
+                           bool allowNewSopInstanceUid) = 0;
+
+    virtual void WriteToMemoryBuffer(std::string& target) = 0;
+  };
+
+
+  class DcmtkTranscoder2 : public IDicomTranscoder1
+  {
+  private:
+    std::unique_ptr<DcmFileFormat>    dicom_;
+    std::unique_ptr<DicomFrameIndex>  index_;
+    DicomTransferSyntax               transferSyntax_;
+    std::string                       sopClassUid_;
+    std::string                       sopInstanceUid_;
+    uint16_t                          bitsStored_;
+    unsigned int                      lossyQuality_;
+
+    static std::string GetStringTag(DcmDataset& dataset,
+                                    const DcmTagKey& tag)
+    {
+      const char* value = NULL;
+
+      if (!dataset.findAndGetString(tag, value).good() ||
+          value == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing SOP class/instance UID in DICOM instance");
+      }
+      else
+      {
+        return std::string(value);
+      }
+    }
+
+    void Setup(DcmFileFormat* dicom)
+    {
+      lossyQuality_ = 90;
+      
+      dicom_.reset(dicom);
+      
+      if (dicom == NULL ||
+          dicom_->getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      DcmDataset& dataset = *dicom_->getDataset();
+      index_.reset(new DicomFrameIndex(dataset));
+
+      E_TransferSyntax xfer = dataset.getCurrentXfer();
+      if (xfer == EXS_Unknown)
+      {
+        dataset.updateOriginalXfer();
+        xfer = dataset.getCurrentXfer();
+        if (xfer == EXS_Unknown)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Cannot determine the transfer syntax of the DICOM instance");
+        }
+      }
+
+      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
+      {
+        throw OrthancException(
+          ErrorCode_BadFileFormat,
+          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
+      }
+
+      if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good())
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing \"Bits Stored\" tag in DICOM instance");
+      }      
+
+      sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
+      sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
+    }
+    
+  public:
+    DcmtkTranscoder2(DcmFileFormat* dicom)  // Takes ownership
+    {
+      Setup(dicom);
+    }
+
+    DcmtkTranscoder2(const void* dicom,
+                    size_t size)
+    {
+      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
+    }
+
+    void SetLossyQuality(unsigned int quality)
+    {
+      if (quality <= 0 ||
+          quality > 100)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        lossyQuality_ = quality;
+      }
+    }
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+
+    unsigned int GetBitsStored() const
+    {
+      return bitsStored_;
+    }
+
+    virtual DcmFileFormat& GetDicom()
+    {
+      assert(dicom_ != NULL);
+      return *dicom_;
+    }
+
+    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
+    {
+      return transferSyntax_;
+    }
+
+    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
+    {
+      return sopClassUid_;
+    }
+    
+    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
+    {
+      return sopInstanceUid_;
+    }
+
+    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
+    {
+      return index_->GetFramesCount();
+    }
+
+    virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE
+    {
+      if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_))
+      {
+        throw OrthancException(ErrorCode_InternalError,
+                               "Cannot write the DICOM instance to a memory buffer");
+      }
+    }
+
+    virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE
+    {
+      assert(dicom_->getDataset() != NULL);
+      return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
+    }
+
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) ORTHANC_OVERRIDE
+    {
+      index_->GetRawFrame(target, frame);
+    }
+
+    virtual bool Transcode(std::string& target,
+                           DicomTransferSyntax syntax,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
+    {
+      assert(dicom_ != NULL &&
+             dicom_->getDataset() != NULL);
+      
+      if (syntax == GetTransferSyntax())
+      {
+        printf("NO TRANSCODING\n");
+        
+        // No change in the transfer syntax => simply serialize the current dataset
+        WriteToMemoryBuffer(target);
+        return true;
+      }
+      
+      printf(">> %d\n", bitsStored_);
+
+      if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit;
+        return true;
+      }
+
+      if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit;
+        return true;
+      }
+      
+      if (syntax == DicomTransferSyntax_BigEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_BigEndianExplicit;
+        return true;
+      }
+
+      if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
+          FromDcmtkBridge::Transcode(*dicom_, syntax, NULL) &&
+          FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+      {
+        transferSyntax_ = DicomTransferSyntax_DeflatedLittleEndianExplicit;
+        return true;
+      }
+
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess1 &&
+          allowNewSopInstanceUid &&
+          GetBitsStored() == 8)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        
+        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
+            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+        {
+          transferSyntax_ = DicomTransferSyntax_JPEGProcess1;
+          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
+          return true;
+        }
+      }
+#endif
+      
+#if ORTHANC_ENABLE_JPEG == 1
+      if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
+          allowNewSopInstanceUid &&
+          GetBitsStored() <= 12)
+      {
+        DJ_RPLossy rpLossy(lossyQuality_);
+        if (FromDcmtkBridge::Transcode(*dicom_, syntax, &rpLossy) &&
+            FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_, syntax))
+        {
+          transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4;
+          sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID);
+          return true;
+        }
+      }
+#endif
+
+      return false;
+    }
+  };
+}
+
+
+
+
+#include <boost/filesystem.hpp>
+
+
+static void TestFile(const std::string& path)
+{
+  static unsigned int count = 0;
+  count++;
+  
+
+  printf("** %s\n", path.c_str());
+
+  std::string s;
+  SystemToolbox::ReadFile(s, path);
+
+  Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size());
+
+  /*if (transcoder.GetBitsStored() != 8)  // TODO
+    return; */
+
+  {
+    char buf[1024];
+    sprintf(buf, "/tmp/source-%06d.dcm", count);
+    printf(">> %s\n", buf);
+    Orthanc::SystemToolbox::WriteFile(s, buf);
+  }
+
+  printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
+         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
+         transcoder.GetFramesCount(), transcoder.GetTransferSyntax());
+
+  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
+  {
+    std::string f;
+    transcoder.GetCompressedFrame(f, i);
+
+    if (i == 0)
+    {
+      char buf[1024];
+      sprintf(buf, "/tmp/frame-%06d.raw", count);
+      printf(">> %s\n", buf);
+      Orthanc::SystemToolbox::WriteFile(f, buf);
+    }
+  }
+
+  {
+    std::string t;
+    transcoder.WriteToMemoryBuffer(t);
+
+    Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
+    printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size());
+  }
+
+  {
+    std::string a = transcoder.GetSopInstanceUid();
+    DicomTransferSyntax b = transcoder.GetTransferSyntax();
+    
+    DicomTransferSyntax syntax = DicomTransferSyntax_JPEGProcess2_4;
+    //DicomTransferSyntax syntax = DicomTransferSyntax_LittleEndianExplicit;
+
+    std::string t;
+    bool ok = transcoder.Transcode(t, syntax, true);
+    printf("Transcoding: %d\n", ok);
+
+    if (ok)
+    {
+      printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str());
+      printf("[%s] => [%s]\n", GetTransferSyntaxUid(b),
+             GetTransferSyntaxUid(transcoder.GetTransferSyntax()));
+      
+      {
+        char buf[1024];
+        sprintf(buf, "/tmp/transcoded-%06d.dcm", count);
+        printf(">> %s\n", buf);
+        Orthanc::SystemToolbox::WriteFile(t, buf);
+      }
+
+      Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
+      printf("  => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size());
+    }
+  }
+  
+  printf("\n");
+}
+
+TEST(Toto, DISABLED_Transcode)
+{
+  //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
+
+  if (1)
+  {
+    const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";
+    
+    for (boost::filesystem::directory_iterator it(PATH);
+         it != boost::filesystem::directory_iterator(); ++it)
+    {
+      if (boost::filesystem::is_regular_file(it->status()))
+      {
+        TestFile(it->path().string());
+      }
+    }
+  }
+
+  if (0)
+  {
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
+  }
+
+  if (0)
+  {
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
+  }
+}
+
+
+TEST(Toto, DISABLED_Transcode2)
+{
+  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
+  {
+    DicomTransferSyntax a = (DicomTransferSyntax) i;
+
+    std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
+                        std::string(GetTransferSyntaxUid(a)) + ".dcm");
+    if (Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      printf("\n======= %s\n", GetTransferSyntaxUid(a));
+
+      std::string source;
+      Orthanc::SystemToolbox::ReadFile(source, path);
+
+      DcmtkImageReader reader;
+
+      {
+        std::unique_ptr<IParsedDicomImage> image(
+          reader.Read(source.c_str(), source.size()));
+        ASSERT_TRUE(image.get() != NULL);
+        ASSERT_EQ(a, image->GetTransferSyntax());
+
+        std::string target;
+        image->WriteToMemoryBuffer(target);
+      }
+
+      for (int j = 0; j <= DicomTransferSyntax_XML; j++)
+      {
+        DicomTransferSyntax b = (DicomTransferSyntax) j;
+        //if (a == b) continue;
+
+        std::unique_ptr<IParsedDicomImage> image(
+          reader.Transcode(source.c_str(), source.size(), b, true));
+        if (image.get() != NULL)
+        {
+          printf("[%s] -> [%s]\n", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));
+
+          std::string target;
+          image->WriteToMemoryBuffer(target);
+
+          char buf[1024];
+          sprintf(buf, "/tmp/%s-%s.dcm", GetTransferSyntaxUid(a), GetTransferSyntaxUid(b));
+          
+          SystemToolbox::WriteFile(target, buf);
+        }
+        else if (a != DicomTransferSyntax_JPEG2000 &&
+                 a != DicomTransferSyntax_JPEG2000LosslessOnly)
+        {
+          ASSERT_TRUE(b != DicomTransferSyntax_LittleEndianImplicit &&
+                      b != DicomTransferSyntax_LittleEndianExplicit &&
+                      b != DicomTransferSyntax_BigEndianExplicit &&
+                      b != DicomTransferSyntax_DeflatedLittleEndianExplicit);
+        }
+      }
+    }
+  }
+}
+
+
+#include "../Core/DicomNetworking/DicomAssociation.h"
+#include "../Core/DicomNetworking/DicomControlUserConnection.h"
+#include "../Core/DicomNetworking/DicomStoreUserConnection.h"
+
+TEST(Toto, DISABLED_DicomAssociation)
+{
+  DicomAssociationParameters params;
+  params.SetLocalApplicationEntityTitle("ORTHANC");
+  params.SetRemoteApplicationEntityTitle("PACS");
+  params.SetRemotePort(2001);
+
+#if 0
+  DicomAssociation assoc;
+  assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass);
+  assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass);
+  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
+                                   DicomTransferSyntax_JPEGProcess1);
+  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
+                                   DicomTransferSyntax_JPEGProcess2_4);
+  assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage,
+                                   DicomTransferSyntax_JPEG2000);
+  
+  assoc.Open(params);
+
+  int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage);
+  printf(">> %d\n", presID);
+    
+  std::map<DicomTransferSyntax, uint8_t> pc;
+  printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage));
+  
+  for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
+         it = pc.begin(); it != pc.end(); ++it)
+  {
+    printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second);
+  }
+#else
+  {
+    DicomControlUserConnection assoc(params);
+
+    try
+    {
+      printf(">> %d\n", assoc.Echo());
+    }
+    catch (OrthancException&)
+    {
+    }
+  }
+    
+  params.SetRemoteApplicationEntityTitle("PACS");
+  params.SetRemotePort(2000);
+
+  {
+    DicomControlUserConnection assoc(params);
+    printf(">> %d\n", assoc.Echo());
+  }
+
+#endif
+}
+
+static void TestTranscode(DicomStoreUserConnection& scu,
+                          const std::string& sopClassUid,
+                          DicomTransferSyntax transferSyntax)
+{
+  std::set<DicomTransferSyntax> accepted;
+
+  scu.LookupTranscoding(accepted, sopClassUid, transferSyntax);
+  if (accepted.empty())
+  {
+    throw OrthancException(ErrorCode_NetworkProtocol,
+                           "The SOP class is not supported by the remote modality");
+  }
+
+  {
+    unsigned int count = 0;
+    for (std::set<DicomTransferSyntax>::const_iterator
+           it = accepted.begin(); it != accepted.end(); ++it)
+    {
+      LOG(INFO) << "available for transcoding " << (count++) << ": " << sopClassUid
+                << " / " << GetTransferSyntaxUid(*it);
+    }
+  }
+  
+  if (accepted.find(transferSyntax) != accepted.end())
+  {
+    printf("**** OK, without transcoding !! [%s]\n", GetTransferSyntaxUid(transferSyntax));
+  }
+  else
+  {
+    // Transcoding - only in Orthanc >= 1.7.0
+
+    const DicomTransferSyntax uncompressed[] = {
+      DicomTransferSyntax_LittleEndianImplicit,  // Default transfer syntax
+      DicomTransferSyntax_LittleEndianExplicit,
+      DicomTransferSyntax_BigEndianExplicit
+    };
+
+    bool found = false;
+    for (size_t i = 0; i < 3; i++)
+    {
+      if (accepted.find(uncompressed[i]) != accepted.end())
+      {
+        printf("**** TRANSCODING to %s\n", GetTransferSyntaxUid(uncompressed[i]));
+        found = true;
+        break;
+      }
+    }
+
+    if (!found)
+    {
+      printf("**** KO KO KO\n");
+    }
+  }
+}
+
+
+TEST(Toto, DISABLED_Store)
+{
+  DicomAssociationParameters params;
+  params.SetLocalApplicationEntityTitle("ORTHANC");
+  params.SetRemoteApplicationEntityTitle("STORESCP");
+  params.SetRemotePort(2000);
+
+  DicomStoreUserConnection assoc(params);
+  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1);
+  assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4);
+  //assoc.RegisterStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
+
+  //assoc.SetUncompressedSyntaxesProposed(false);  // Necessary for transcoding
+  assoc.SetCommonClassesProposed(false);
+  assoc.SetRetiredBigEndianProposed(true);
+  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
+  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
+  TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
+}
+
+
+TEST(Toto, DISABLED_Store2)
+{
+  DicomAssociationParameters params;
+  params.SetLocalApplicationEntityTitle("ORTHANC");
+  params.SetRemoteApplicationEntityTitle("STORESCP");
+  params.SetRemotePort(2000);
+
+  DicomStoreUserConnection assoc(params);
+  //assoc.SetCommonClassesProposed(false);
+  assoc.SetRetiredBigEndianProposed(true);
+
+  std::string s;
+  Orthanc::SystemToolbox::ReadFile(s, "/tmp/i/" + std::string(GetTransferSyntaxUid(DicomTransferSyntax_BigEndianExplicit)) +".dcm");
+
+  std::string c, i;
+  assoc.Store(c, i, s.c_str(), s.size());
+  printf("[%s] [%s]\n", c.c_str(), i.c_str());
+}
+
--- a/Resources/Orthanc.doxygen	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/Orthanc.doxygen	Wed May 20 16:42:44 2020 +0200
@@ -545,7 +545,7 @@
 # this will also influence the order of the classes in the class list.
 # The default value is: NO.
 
-SORT_BRIEF_DOCS        = NO
+SORT_BRIEF_DOCS        = YES
 
 # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
 # (brief and detailed) documentation of class members so that constructors and
--- a/Resources/OrthancPlugin.doxygen	Wed May 06 08:40:48 2020 +0200
+++ b/Resources/OrthancPlugin.doxygen	Wed May 20 16:42:44 2020 +0200
@@ -545,7 +545,7 @@
 # this will also influence the order of the classes in the class list.
 # The default value is: NO.
 
-SORT_BRIEF_DOCS        = NO
+SORT_BRIEF_DOCS        = YES
 
 # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
 # (brief and detailed) documentation of class members so that constructors and
--- a/UnitTestsSources/FromDcmtkTests.cpp	Wed May 06 08:40:48 2020 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Wed May 20 16:42:44 2020 +0200
@@ -513,7 +513,8 @@
   f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata");  // (**)
 
   std::string s;
-  ASSERT_FALSE(f.LookupTransferSyntax(s));
+  ASSERT_TRUE(f.LookupTransferSyntax(s));
+  ASSERT_EQ(s, GetTransferSyntaxUid(DicomTransferSyntax_LittleEndianExplicit));
 
   ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"),
                          false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
@@ -1924,349 +1925,105 @@
 
 #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());
-
-  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);
-    }
-  }
+  std::unique_ptr<DcmFileFormat> toto(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size()));
+  const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(*toto);
+  
+  DicomTransferSyntax sourceSyntax;
+  ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
 
-  printf("\n");
-}
-
-TEST(Toto, Transcode)
-{
-  if (0)
-  {
-    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()));
+  DcmtkTranscoder transcoder;
 
-    // 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));
+  for (int i = 0; i <= DicomTransferSyntax_XML; i++)
+  {
+    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");
-  }
+    IDicomTranscoder::DicomImage source, target;
+    source.AcquireParsed(dynamic_cast<DcmFileFormat*>(toto->clone()));
 
-  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 (!transcoder.Transcode(target, source, s, true))
+    {
+      printf("**************** CANNOT: [%s] => [%s]\n",
+             GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
+    }
+    else
     {
-      if (boost::filesystem::is_regular_file(it->status()))
+      DicomTransferSyntax targetSyntax;
+      ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, target.GetParsed()));
+      
+      ASSERT_EQ(targetSyntax, a);
+      bool lossy = (a == DicomTransferSyntax_JPEGProcess1 ||
+                    a == DicomTransferSyntax_JPEGProcess2_4 ||
+                    a == DicomTransferSyntax_JPEGLSLossy);
+      
+      printf("SIZE: %lu\n", t.size());
+      if (sourceUid == IDicomTranscoder::GetSopInstanceUid(target.GetParsed()))
       {
-        TestFile(it->path().string());
+        ASSERT_FALSE(lossy);
+      }
+      else
+      {
+        ASSERT_TRUE(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	Wed May 20 16:42:44 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());
@@ -1548,6 +1549,30 @@
     ASSERT_EQ("username", tmp.GetPeer().GetUsername());
     ASSERT_EQ("password", tmp.GetPeer().GetPassword());
     ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled());
+    ASSERT_FALSE(tmp.IsTranscode());
+    ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException);
+  }
+
+  {
+    OrthancPeerStoreJob job(GetContext());
+    ASSERT_THROW(job.SetTranscode("nope"), OrthancException);
+    job.SetTranscode("1.2.840.10008.1.2.4.50");
+    
+    ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
+    ASSERT_TRUE(job.Serialize(s));
+  }
+
+  {
+    std::unique_ptr<IJob> job;
+    job.reset(unserializer.UnserializeJob(s));
+
+    OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job);
+    ASSERT_EQ("http://127.0.0.1:8042/", tmp.GetPeer().GetUrl());
+    ASSERT_EQ("", tmp.GetPeer().GetUsername());
+    ASSERT_EQ("", tmp.GetPeer().GetPassword());
+    ASSERT_FALSE(tmp.GetPeer().IsPkcs11Enabled());
+    ASSERT_TRUE(tmp.IsTranscode());
+    ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax());
   }
 
   // ResourceModificationJob
@@ -1559,7 +1584,8 @@
     ResourceModificationJob job(GetContext());
     job.SetModification(modification.release(), ResourceType_Patient, true);
     job.SetOrigin(DicomInstanceOrigin::FromLua());
-    
+
+    job.AddTrailingStep();  // Necessary since 1.7.0
     ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     ASSERT_TRUE(job.Serialize(s));
   }
@@ -1570,10 +1596,33 @@
 
     ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
     ASSERT_TRUE(tmp.IsAnonymization());
+    ASSERT_FALSE(tmp.IsTranscode());
+    ASSERT_THROW(tmp.GetTransferSyntax(), OrthancException);
     ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
     ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
   }
 
+  {
+    ResourceModificationJob job(GetContext());
+    ASSERT_THROW(job.SetTranscode("nope"), OrthancException);
+    job.SetTranscode(DicomTransferSyntax_JPEGProcess1);
+
+    job.AddTrailingStep();  // Necessary since 1.7.0
+    ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
+    ASSERT_TRUE(job.Serialize(s));
+  }
+
+  {
+    std::unique_ptr<IJob> job;
+    job.reset(unserializer.UnserializeJob(s));
+
+    ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
+    ASSERT_FALSE(tmp.IsAnonymization());
+    ASSERT_TRUE(tmp.IsTranscode());
+    ASSERT_EQ(DicomTransferSyntax_JPEGProcess1, tmp.GetTransferSyntax());
+    ASSERT_EQ(RequestOrigin_Unknown, tmp.GetOrigin().GetRequestOrigin());
+  }
+
   // SplitStudyJob
 
   std::string instance;
@@ -1902,6 +1951,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 +1982,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 +2049,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_TRUE(modality.IsTranscodingAllowed());
   }
 
   {
@@ -2007,6 +2059,7 @@
     s["AET"] = "AET";
     s["Host"] = "host";
     s["Port"] = "104";
+    s["AllowTranscoding"] = false;
     
     RemoteModalityParameters modality(s);
     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
@@ -2015,6 +2068,7 @@
     ASSERT_EQ(104u, modality.GetPortNumber());
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+    ASSERT_FALSE(modality.IsTranscodingAllowed());
   }
 
   {
@@ -2032,5 +2086,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	Wed May 20 16:42:44 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);
     }