changeset 3893:7a5fa8f307e9 transcoding

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 06 May 2020 12:48:28 +0200
parents 5571082a9df6
children 8f7ad4989fec
files CMakeLists.txt Core/DicomNetworking/DicomStoreUserConnection.cpp Core/DicomNetworking/DicomStoreUserConnection.h Core/DicomParsing/DcmtkTranscoder.cpp Core/DicomParsing/DcmtkTranscoder.h Core/DicomParsing/IDicomTranscoder.h Core/DicomParsing/MemoryBufferTranscoder.cpp Core/DicomParsing/MemoryBufferTranscoder.h Resources/CMake/OrthancFrameworkConfiguration.cmake UnitTestsSources/FromDcmtkTests.cpp
diffstat 10 files changed, 824 insertions(+), 539 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue May 05 18:08:51 2020 +0200
+++ b/CMakeLists.txt	Wed May 06 12:48:28 2020 +0200
@@ -13,6 +13,7 @@
 set(ENABLE_CRYPTO_OPTIONS ON)
 set(ENABLE_DCMTK ON)
 set(ENABLE_DCMTK_NETWORKING ON)
+set(ENABLE_DCMTK_TRANSCODING ON)
 set(ENABLE_GOOGLE_TEST ON)
 set(ENABLE_JPEG ON)
 set(ENABLE_LOCALE ON)
@@ -25,9 +26,6 @@
 set(ENABLE_WEB_SERVER ON)
 set(ENABLE_ZLIB ON)
 
-# To test transcoding
-set(ENABLE_DCMTK_TRANSCODING ON)
-
 set(HAS_EMBEDDED_RESOURCES ON)
 
 
--- a/Core/DicomNetworking/DicomStoreUserConnection.cpp	Tue May 05 18:08:51 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp	Wed May 06 12:48:28 2020 +0200
@@ -439,4 +439,97 @@
       }
     }
   }
+
+
+  void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */,
+                                           std::string& sopInstanceUid /* out */,
+                                           IDicomTranscoder& transcoder,
+                                           const void* buffer,
+                                           size_t size,
+                                           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, moveOriginatorAET, moveOriginatorID);
+    }
+    else
+    {
+      // Transcoding is needed
+      std::set<DicomTransferSyntax> uncompressedSyntaxes;
+
+      if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
+      {
+        uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
+      }
+
+      if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
+      {
+        uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
+      }
+
+      if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
+      {
+        uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
+      }
+
+      std::unique_ptr<DcmFileFormat> transcoded;
+
+      if (transcoder.HasInplaceTranscode())
+      {
+        if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false))
+        {
+          // In-place transcoding is supported and has succeeded
+          transcoded.reset(dicom.release());
+        }
+      }
+      else
+      {
+        transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false));
+      }
+
+      // WARNING: The "dicom" variable must not be used below this
+      // point. The "sopInstanceUid" might also have changed (if
+      // using lossy compression).
+        
+      if (transcoded == NULL ||
+          transcoded->getDataset() == NULL)
+      {
+        throw OrthancException(
+          ErrorCode_NotImplemented,
+          "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) +
+          "\" to an uncompressed syntax for modality: " +
+          GetParameters().GetRemoteModality().GetApplicationEntityTitle());
+      }
+      else
+      {
+        DicomTransferSyntax transcodedSyntax;
+
+        // Sanity check
+        if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) ||
+            accepted.find(transcodedSyntax) == accepted.end())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+        else
+        {
+          Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID);
+        }
+      }
+    }
+  }
 }
--- a/Core/DicomNetworking/DicomStoreUserConnection.h	Tue May 05 18:08:51 2020 +0200
+++ b/Core/DicomNetworking/DicomStoreUserConnection.h	Wed May 06 12:48:28 2020 +0200
@@ -33,8 +33,16 @@
 
 #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>
@@ -94,6 +102,10 @@
                                       const std::string& sopClassUid,
                                       DicomTransferSyntax transferSyntax);
 
+    void LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
+                           const std::string& sopClassUid,
+                           DicomTransferSyntax sourceSyntax);
+
   public:
     DicomStoreUserConnection(const DicomAssociationParameters& params);
     
@@ -168,8 +180,22 @@
                           DicomTransferSyntax& transferSyntax,
                           DcmFileFormat& dicom);
 
-    void LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
-                           const std::string& sopClassUid,
-                           DicomTransferSyntax sourceSyntax);
+    void Transcode(std::string& sopClassUid /* out */,
+                   std::string& sopInstanceUid /* out */,
+                   IDicomTranscoder& transcoder,
+                   const void* buffer,
+                   size_t size,
+                   const std::string& moveOriginatorAET,
+                   uint16_t moveOriginatorID);
+
+    void Transcode(std::string& sopClassUid /* out */,
+                   std::string& sopInstanceUid /* out */,
+                   IDicomTranscoder& transcoder,
+                   const void* buffer,
+                   size_t size)
+    {
+      Transcode(sopClassUid, sopInstanceUid, transcoder,
+                buffer, size, "", 0);  // Not a C-Move
+    }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DcmtkTranscoder.cpp	Wed May 06 12:48:28 2020 +0200
@@ -0,0 +1,320 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DcmtkTranscoder.h"
+
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
+#  error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
+#endif
+
+
+#include "FromDcmtkBridge.h"
+#include "../OrthancException.h"
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmjpeg/djrploss.h>  // for DJ_RPLossy
+#include <dcmtk/dcmjpeg/djrplol.h>   // for DJ_RPLossless
+#include <dcmtk/dcmjpls/djrparam.h>  // for DJLSRepresentationParameter
+
+
+namespace Orthanc
+{
+  static uint16_t GetBitsStored(DcmDataset& dataset)
+  {
+    uint16_t bitsStored;
+    if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good())
+    {
+      return bitsStored;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Missing \"Bits Stored\" tag in DICOM instance");
+    }      
+  }
+
+  
+  static std::string GetSopInstanceUid(DcmDataset& dataset)
+  {
+    const char* v = NULL;
+
+    if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() &&
+        v != NULL)
+    {
+      return std::string(v);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID");
+    }
+  }
+
+  
+  static void CheckSopInstanceUid(DcmFileFormat& dicom,
+                                  const std::string& sopInstanceUid,
+                                  bool mustEqual)
+  {
+    if (dicom.getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+      
+    bool ok;
+      
+    if (mustEqual)
+    {
+      ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid);
+    }
+    else
+    {
+      ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid);
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" :
+                             "The SOP instance UID has not changed as expected during transcoding");
+    }
+  }
+    
+
+  void DcmtkTranscoder::SetLossyQuality(unsigned int quality)
+  {
+    if (quality <= 0 ||
+        quality > 100)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      lossyQuality_ = quality;
+    }
+  }
+
+    
+  DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(const void* buffer,
+                                                    size_t size,
+                                                    const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                                    bool allowNewSopInstanceUid) 
+  {
+    std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
+
+    if (dicom.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid))
+    {
+      return dicom.release();
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool DcmtkTranscoder::InplaceTranscode(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");
+    }
+
+    const uint16_t bitsStored = GetBitsStored(*dicom.getDataset());
+    std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset());
+
+    if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end())
+    {
+      // No transcoding is needed
+      return true;
+    }
+      
+    if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+
+    if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+      
+    if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+
+    if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() &&
+        FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
+    {
+      CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+      return true;
+    }
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() &&
+        allowNewSopInstanceUid &&
+        bitsStored == 8)
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossy parameters(lossyQuality_);
+        
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() &&
+        allowNewSopInstanceUid &&
+        bitsStored <= 12)
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossy parameters(lossyQuality_);
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpeg/apps/dcmcjpeg.cc"
+      DJ_RPLossless parameters(6 /* opt_selection_value */,
+                               0 /* opt_point_transform */);
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpls/apps/dcmcjpls.cc"
+      DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
+                                             OFTrue /* opt_useLosslessProcess */);
+
+      /**
+       * WARNING: This call results in a segmentation fault if using
+       * the DCMTK package 3.6.2 from Ubuntu 18.04.
+       **/              
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
+        return true;
+      }
+    }
+#endif
+      
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    if (allowNewSopInstanceUid &&
+        allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end())
+    {
+      // Check out "dcmjpls/apps/dcmcjpls.cc"
+      DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
+                                             OFFalse /* opt_useLosslessProcess */);
+
+      /**
+       * WARNING: This call results in a segmentation fault if using
+       * the DCMTK package 3.6.2 from Ubuntu 18.04.
+       **/              
+      if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, &parameters))
+      {
+        CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
+        return true;
+      }
+    }
+#endif
+
+    return false;
+  }
+
+    
+  bool DcmtkTranscoder::TranscodeToBuffer(std::string& target,
+                                          const void* buffer,
+                                          size_t size,
+                                          const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                          bool allowNewSopInstanceUid) 
+  {
+    std::unique_ptr<DcmFileFormat> transcoded(
+      TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
+
+    if (transcoded.get() == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      if (transcoded->getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }          
+        
+      FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset());
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DcmtkTranscoder.h	Wed May 06 12:48:28 2020 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
+#  error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1
+#  error Transcoding is disabled, cannot compile this file
+#endif
+
+#include "IDicomTranscoder.h"
+
+namespace Orthanc
+{
+  class DcmtkTranscoder : public IDicomTranscoder
+  {
+  private:
+    unsigned int  lossyQuality_;
+    
+  public:
+    DcmtkTranscoder() :
+      lossyQuality_(90)
+    {
+    }
+
+    void SetLossyQuality(unsigned int quality);
+
+    unsigned int GetLossyQuality() const
+    {
+      return lossyQuality_;
+    }
+    
+    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
+                                             size_t size,
+                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                             bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    virtual bool HasInplaceTranscode() const
+    {
+      return true;
+    }
+
+    virtual bool InplaceTranscode(DcmFileFormat& dicom,
+                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                  bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+    
+    virtual bool TranscodeToBuffer(std::string& target,
+                                   const void* buffer,
+                                   size_t size,
+                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/IDicomTranscoder.h	Wed May 06 12:48:28 2020 +0200
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <boost/noncopyable.hpp>
+#include <set>
+
+class DcmFileFormat;
+
+namespace Orthanc
+{
+  /**
+   * WARNING: This class might be called from several threads at
+   * once. Make sure to implement proper locking.
+   **/
+  
+  class IDicomTranscoder : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomTranscoder()
+    {
+    }
+
+    virtual bool TranscodeToBuffer(std::string& target,
+                                   const void* buffer,
+                                   size_t size,
+                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                   bool allowNewSopInstanceUid) = 0;
+
+    /**
+     * Transcoding flavor that creates a new parsed DICOM file. A
+     * "std::set<>" is used to give the possible plugin the
+     * possibility to do a single parsing for all the possible
+     * transfer syntaxes.
+     **/
+    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
+                                             size_t size,
+                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                             bool allowNewSopInstanceUid) = 0;
+    
+    virtual bool HasInplaceTranscode() const = 0;
+
+    /**
+     * In-place transcoding. This method is preferred for C-STORE.
+     **/
+    virtual bool InplaceTranscode(DcmFileFormat& dicom,
+                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                  bool allowNewSopInstanceUid) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp	Wed May 06 12:48:28 2020 +0200
@@ -0,0 +1,116 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "MemoryBufferTranscoder.h"
+
+#include "../OrthancException.h"
+#include "FromDcmtkBridge.h"
+
+namespace Orthanc
+{
+  MemoryBufferTranscoder::MemoryBufferTranscoder(bool tryDcmtk) :
+    tryDcmtk_(tryDcmtk)
+  {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1
+    if (tryDcmtk)
+    {
+      throw OrthancException(ErrorCode_NotImplemented,
+                             "Orthanc was built without support for DMCTK transcoding");
+    }
+#endif    
+  }
+
+  bool MemoryBufferTranscoder::TranscodeToBuffer(std::string& target,
+                                                 const void* buffer,
+                                                 size_t size,
+                                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                                 bool allowNewSopInstanceUid)
+  {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    if (tryDcmtk_)
+    {
+      return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+#endif
+    {
+      return Transcode(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+  }
+
+  
+  DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(const void* buffer,
+                                                           size_t size,
+                                                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                                           bool allowNewSopInstanceUid)
+  {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    if (tryDcmtk_)
+    {
+      return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+#endif
+    {
+      std::string transcoded;
+      if (Transcode(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
+      {
+        return FromDcmtkBridge::LoadFromMemoryBuffer(
+          transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size());
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+  }
+
+
+  bool MemoryBufferTranscoder::InplaceTranscode(DcmFileFormat& dicom,
+                                                const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                                bool allowNewSopInstanceUid)
+  {
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    if (tryDcmtk_)
+    {
+      return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+#endif
+    {
+      // "HasInplaceTranscode()" should have been called
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/MemoryBufferTranscoder.h	Wed May 06 12:48:28 2020 +0200
@@ -0,0 +1,90 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
+#  error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#  include "DcmtkTranscoder.h"
+#endif
+
+namespace Orthanc
+{
+  // This is the basis class for transcoding plugins
+  class MemoryBufferTranscoder : public IDicomTranscoder
+  {
+  private:
+    bool  tryDcmtk_;
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DcmtkTranscoder  dcmtk_;
+#endif
+
+  protected:
+    virtual bool Transcode(std::string& target,
+                           const void* buffer,
+                           size_t size,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) = 0;
+    
+  public:
+    /**
+     * If "tryDcmtk" is "true", the transcoder will first try and call
+     * DCMTK, before calling its own "Transcode()" implementation.
+     **/
+    MemoryBufferTranscoder(bool tryDcmtk);
+    
+    virtual bool TranscodeToBuffer(std::string& target,
+                                   const void* buffer,
+                                   size_t size,
+                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+    
+    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
+                                             size_t size,
+                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                             bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE
+    {
+      return tryDcmtk_;
+    }
+    
+    virtual bool InplaceTranscode(DcmFileFormat& dicom,
+                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                  bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+  };
+}
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Tue May 05 18:08:51 2020 +0200
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed May 06 12:48:28 2020 +0200
@@ -501,6 +501,10 @@
   # New in Orthanc 1.6.0
   if (ENABLE_DCMTK_TRANSCODING)
     add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=1)
+    list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL
+      ${ORTHANC_ROOT}/Core/DicomParsing/DcmtkTranscoder.cpp
+      ${ORTHANC_ROOT}/Core/DicomParsing/MemoryBufferTranscoder.cpp
+      )
   else()
     add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=0)
   endif()
--- a/UnitTestsSources/FromDcmtkTests.cpp	Tue May 05 18:08:51 2020 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Wed May 06 12:48:28 2020 +0200
@@ -1925,535 +1925,7 @@
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
 
 #include "../Core/DicomNetworking/DicomStoreUserConnection.h"
-
-#include <dcmtk/dcmjpeg/djrploss.h>  // for DJ_RPLossy
-#include <dcmtk/dcmjpeg/djrplol.h>   // for DJ_RPLossless
-#include <dcmtk/dcmjpls/djrparam.h>  // for DJLSRepresentationParameter
-
-
-#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
-
-
-
-namespace Orthanc
-{
-  /**
-   * WARNING: This class might be called from several threads at
-   * once. Make sure to implement proper locking.
-   **/
-  
-  class IDicomTranscoder : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomTranscoder()
-    {
-    }
-
-    virtual bool TranscodeToBuffer(std::string& target,
-                                   const void* buffer,
-                                   size_t size,
-                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                   bool allowNewSopInstanceUid) = 0;
-
-    /**
-     * Transcoding flavor that creates a new parsed DICOM file. A
-     * "std::set<>" is used to give the possible plugin the
-     * possibility to do a single parsing for all the possible
-     * transfer syntaxes.
-     **/
-    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
-                                             size_t size,
-                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                             bool allowNewSopInstanceUid) = 0;
-
-    virtual bool HasInplaceTranscode() const = 0;
-
-    /**
-     * In-place transcoding. This method is preferred for C-STORE.
-     **/
-    virtual bool InplaceTranscode(DcmFileFormat& dicom,
-                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                  bool allowNewSopInstanceUid) = 0;
-
-    /**
-     * Important: Transcoding over the DICOM protocol is only
-     * implemented towards uncompressed transfer syntaxes.
-     **/
-    static void Store(std::string& sopClassUid /* out */,
-                      std::string& sopInstanceUid /* out */,
-                      DicomStoreUserConnection& connection,
-                      IDicomTranscoder& transcoder,
-                      const void* buffer,
-                      size_t size,
-                      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;
-      connection.LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
-
-      std::set<DicomTransferSyntax> accepted;
-      connection.LookupTranscoding(accepted, sopClassUid, inputSyntax);
-
-      if (accepted.find(inputSyntax) != accepted.end())
-      {
-        // No need for transcoding
-        connection.Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID);
-      }
-      else
-      {
-        // Transcoding is needed
-        std::set<DicomTransferSyntax> uncompressedSyntaxes;
-
-        if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
-        {
-          uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
-        }
-
-        if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
-        {
-          uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
-        }
-
-        if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
-        {
-          uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
-        }
-
-        std::unique_ptr<DcmFileFormat> transcoded;
-
-        if (transcoder.HasInplaceTranscode())
-        {
-          if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false))
-          {
-            // In-place transcoding is supported and has succeeded
-            transcoded.reset(dicom.release());
-          }
-        }
-        else
-        {
-          transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false));
-        }
-
-        // WARNING: The "dicom" variable must not be used below this
-        // point. The "sopInstanceUid" might also have changed (if
-        // using lossy compression).
-        
-        if (transcoded == NULL ||
-            transcoded->getDataset() == NULL)
-        {
-          throw OrthancException(
-            ErrorCode_NotImplemented,
-            "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) +
-            "\" to an uncompressed syntax for modality: " +
-            connection.GetParameters().GetRemoteModality().GetApplicationEntityTitle());
-        }
-        else
-        {
-          DicomTransferSyntax transcodedSyntax;
-
-          // Sanity check
-          if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) ||
-              accepted.find(transcodedSyntax) == accepted.end())
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
-          else
-          {
-            connection.Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID);
-          }
-        }
-      }
-    }
-
-    static void Store(std::string& sopClassUid /* out */,
-                      std::string& sopInstanceUid /* out */,
-                      DicomStoreUserConnection& connection,
-                      IDicomTranscoder& transcoder,
-                      const void* buffer,
-                      size_t size)
-    {
-      Store(sopClassUid, sopInstanceUid, connection, transcoder,
-            buffer, size, "", 0 /* Not a C-MOVE */);
-    }
-  };
-
-
-  class DcmtkTranscoder : public IDicomTranscoder
-  {
-  private:
-    unsigned int  lossyQuality_;
-    
-    static uint16_t GetBitsStored(DcmDataset& dataset)
-    {
-      uint16_t bitsStored;
-      if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good())
-      {
-        return bitsStored;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Missing \"Bits Stored\" tag in DICOM instance");
-      }      
-    }
-
-    static std::string GetSopInstanceUid(DcmDataset& dataset)
-    {
-      const char* v = NULL;
-
-      if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() &&
-          v != NULL)
-      {
-        return std::string(v);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID");
-      }
-    }
-
-    static void CheckSopInstanceUid(DcmFileFormat& dicom,
-                                    const std::string& sopInstanceUid,
-                                    bool mustEqual)
-    {
-      if (dicom.getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-      
-      bool ok;
-      
-      if (mustEqual)
-      {
-        ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid);
-      }
-      else
-      {
-        ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid);
-      }
-
-      if (!ok)
-      {
-        throw OrthancException(ErrorCode_InternalError,
-                               mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" :
-                               "The SOP instance UID has not changed as expected during transcoding");
-      }
-    }
-    
-  public:
-    DcmtkTranscoder() :
-      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 DcmFileFormat* TranscodeToParsed(const void* buffer,
-                                             size_t size,
-                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                             bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
-
-      if (dicom.get() == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid))
-      {
-        return dicom.release();
-      }
-      else
-      {
-        return NULL;
-      }
-    }
-
-    virtual bool HasInplaceTranscode() const
-    {
-      return true;
-    }
-
-    virtual bool InplaceTranscode(DcmFileFormat& dicom,
-                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                  bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      if (dicom.getDataset() == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      DicomTransferSyntax syntax;
-      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom))
-      {
-        throw OrthancException(ErrorCode_BadFileFormat,
-                               "Cannot determine the transfer syntax");
-      }
-
-      const uint16_t bitsStored = GetBitsStored(*dicom.getDataset());
-      std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset());
-
-      if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end())
-      {
-        // No transcoding is needed
-        return true;
-      }
-      
-      if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() &&
-          FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
-      {
-        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
-        return true;
-      }
-
-      if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() &&
-          FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
-      {
-        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
-        return true;
-      }
-      
-      if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() &&
-          FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
-      {
-        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
-        return true;
-      }
-
-      if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() &&
-          FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
-      {
-        CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
-        return true;
-      }
-
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-      if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() &&
-          allowNewSopInstanceUid &&
-          bitsStored == 8)
-      {
-        // Check out "dcmjpeg/apps/dcmcjpeg.cc"
-        DJ_RPLossy parameters(lossyQuality_);
-        
-        if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, &parameters))
-        {
-          CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
-          return true;
-        }
-      }
-#endif
-      
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-      if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() &&
-          allowNewSopInstanceUid &&
-          bitsStored <= 12)
-      {
-        // Check out "dcmjpeg/apps/dcmcjpeg.cc"
-        DJ_RPLossy parameters(lossyQuality_);
-        if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, &parameters))
-        {
-          CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
-          return true;
-        }
-      }
-#endif
-      
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-      if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end())
-      {
-        // Check out "dcmjpeg/apps/dcmcjpeg.cc"
-        DJ_RPLossless parameters(6 /* opt_selection_value */,
-                                 0 /* opt_point_transform */);
-        if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, &parameters))
-        {
-          CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
-          return true;
-        }
-      }
-#endif
-      
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-      if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end())
-      {
-        // Check out "dcmjpls/apps/dcmcjpls.cc"
-        DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
-                                               OFTrue /* opt_useLosslessProcess */);
-
-        /**
-         * WARNING: This call results in a segmentation fault if using
-         * the DCMTK package 3.6.2 from Ubuntu 18.04.
-         **/              
-        if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, &parameters))
-        {
-          CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
-          return true;
-        }
-      }
-#endif
-      
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-      if (allowNewSopInstanceUid &&
-          allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end())
-      {
-        // Check out "dcmjpls/apps/dcmcjpls.cc"
-        DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
-                                               OFFalse /* opt_useLosslessProcess */);
-
-        /**
-         * WARNING: This call results in a segmentation fault if using
-         * the DCMTK package 3.6.2 from Ubuntu 18.04.
-         **/              
-        if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, &parameters))
-        {
-          CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
-          return true;
-        }
-      }
-#endif
-
-      return false;
-    }
-
-    
-    virtual bool TranscodeToBuffer(std::string& target,
-                                   const void* buffer,
-                                   size_t size,
-                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      std::unique_ptr<DcmFileFormat> transcoded(
-        TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
-
-      if (transcoded.get() == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        if (transcoded->getDataset() == NULL)
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }          
-        
-        FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset());
-        return true;
-      }
-    }
-  };
-
-
-
-  class PluginDicomTranscoder: public IDicomTranscoder
-  {
-  private:
-    bool             tryDcmtk_;
-    DcmtkTranscoder  dcmtk_;
-
-  protected:
-    virtual bool TranscodeInternal(std::string& target,
-                                   const void* buffer,
-                                   size_t size,
-                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                   bool allowNewSopInstanceUid) = 0;
-    
-  public:
-    PluginDicomTranscoder(bool tryDcmtk) :
-      tryDcmtk_(tryDcmtk)
-    {
-    }
-    
-    virtual bool TranscodeToBuffer(std::string& target,
-                                   const void* buffer,
-                                   size_t size,
-                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                   bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      if (tryDcmtk_)
-      {
-        return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
-      }
-      else
-      {
-        return TranscodeInternal(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
-      }
-    }
-    
-    virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
-                                             size_t size,
-                                             const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                             bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      if (tryDcmtk_)
-      {
-        return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
-      }
-      else
-      {
-        std::string transcoded;
-        if (TranscodeInternal(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
-        {
-          return FromDcmtkBridge::LoadFromMemoryBuffer(
-            transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size());
-        }
-        else
-        {
-          return NULL;
-        }
-      }
-    }
-
-    virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE
-    {
-      return tryDcmtk_;
-    }
-    
-    virtual bool InplaceTranscode(DcmFileFormat& dicom,
-                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                  bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
-    {
-      if (tryDcmtk_)
-      {
-        return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid);
-      }
-      else
-      {
-        // "HasInplaceTranscode()" should have been called
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
-    }
-  };
-}
-
+#include "../Core/DicomParsing/DcmtkTranscoder.h"
 
 TEST(Toto, DISABLED_Transcode3)
 {
@@ -2483,7 +1955,7 @@
         std::string c, i;
         try
         {
-          IDicomTranscoder::Store(c, i, scu, transcoder, source.c_str(), source.size());
+          scu.Transcode(c, i, transcoder, source.c_str(), source.size());
         }
         catch (OrthancException& e)
         {
@@ -2501,8 +1973,6 @@
 }
 
 
-
-
 TEST(Toto, DISABLED_Transcode4)
 {
   std::string source;
@@ -2534,5 +2004,4 @@
   }
 }
 
-
 #endif