changeset 3930:b99acc213937 transcoding

transcoder plugins and GDCM transcoding are working
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 14 May 2020 19:20:40 +0200
parents 7dc5e7e0045d
children e6606d3ec892
files Core/DicomParsing/MemoryBufferTranscoder.cpp Core/DicomParsing/MemoryBufferTranscoder.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerJobs/ArchiveJob.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/Common/OrthancPluginCppWrapper.h Plugins/Samples/GdcmDecoder/Plugin.cpp
diffstat 12 files changed, 425 insertions(+), 128 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomParsing/MemoryBufferTranscoder.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp	Thu May 14 19:20:40 2020 +0200
@@ -43,30 +43,6 @@
 
 namespace Orthanc
 {
-  MemoryBufferTranscoder::MemoryBufferTranscoder()
-  {
-#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    useDcmtk_ = true;
-#else
-    useDcmtk_ = false;
-#endif
-  }
-
-
-  void MemoryBufferTranscoder::SetDcmtkUsed(bool used)
-  {
-#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1
-    if (useDcmtk)
-    {
-      throw OrthancException(ErrorCode_NotImplemented,
-                             "Orthanc was built without support for DMCTK transcoding");
-    }
-#endif    
-
-    useDcmtk_ = used;
-  }
-
-
   static void CheckTargetSyntax(const std::string& transcoded,
                                 const std::set<DicomTransferSyntax>& allowedSyntaxes)
   {
@@ -110,21 +86,16 @@
     std::set<DicomTransferSyntax> allowedSyntaxes;
     allowedSyntaxes.insert(targetSyntax);
 
-    bool success = Transcode(target, hasSopInstanceUidChanged,
-                             data, source.size(), allowedSyntaxes, allowNewSopInstanceUid);
-
-#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    if (!success &&
-        useDcmtk_ &&
-        dcmtk_.TranscodeParsedToBuffer(
-          target, hasSopInstanceUidChanged, dicom, targetSyntax, allowNewSopInstanceUid))
+    if (Transcode(target, hasSopInstanceUidChanged,
+                  data, source.size(), allowedSyntaxes, allowNewSopInstanceUid))
     {
-      success = true;
+      CheckTargetSyntax(target, allowedSyntaxes);
+      return true;
     }
-#endif
-
-    CheckTargetSyntax(target, allowedSyntaxes);
-    return success;
+    else
+    {
+      return false;
+    }    
   }
   
 
@@ -147,12 +118,6 @@
       return IDicomTranscoder::TranscodedDicom::CreateFromInternal(
         FromDcmtkBridge::LoadFromMemoryBuffer(data, target.size()), hasSopInstanceUidChanged);
     }
-#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    else if (useDcmtk_)
-    {
-      return dcmtk_.TranscodeToParsed(dicom, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
-    }
-#endif
     else
     {
       return NULL;
--- a/Core/DicomParsing/MemoryBufferTranscoder.h	Thu May 14 14:40:13 2020 +0200
+++ b/Core/DicomParsing/MemoryBufferTranscoder.h	Thu May 14 19:20:40 2020 +0200
@@ -33,26 +33,13 @@
 
 #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
+#include "IDicomTranscoder.h"
 
 namespace Orthanc
 {
   // This is the basis class for transcoding plugins
   class MemoryBufferTranscoder : public IDicomTranscoder
   {
-  private:
-    bool  useDcmtk_;
-
-#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
-    DcmtkTranscoder  dcmtk_;
-#endif
-
   protected:
     virtual bool Transcode(std::string& target,
                            bool& hasSopInstanceUidChanged /* out */,
@@ -62,19 +49,6 @@
                            bool allowNewSopInstanceUid) = 0;
     
   public:
-    /**
-     * If "useDcmtk" is "true", the transcoder will first try and call
-     * DCMTK, before calling its own "Transcode()" implementation.
-     **/
-    MemoryBufferTranscoder();
-
-    void SetDcmtkUsed(bool used);
-
-    bool IsDcmtkUsed() const
-    {
-      return useDcmtk_;
-    }
-    
     virtual bool TranscodeParsedToBuffer(std::string& target /* out */,
                                          bool& hasSopInstanceUidChanged /* out */,
                                          DcmFileFormat& dicom /* in, possibly modified */,
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu May 14 19:20:40 2020 +0200
@@ -133,9 +133,8 @@
       std::string transcoded;
       bool hasSopInstanceUidChanged;
 
-      if (context.GetTranscoder().TranscodeParsedToBuffer(
-            transcoded, hasSopInstanceUidChanged,
-            modified->GetDcmtkObject(), targetSyntax, true))
+      if (context.TranscodeParsedToBuffer(transcoded, hasSopInstanceUidChanged,
+                                          modified->GetDcmtkObject(), targetSyntax, true))
       {      
         call.GetOutput().AnswerBuffer(transcoded, MimeType_Dicom);
       }
--- a/OrthancServer/ServerContext.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Thu May 14 19:20:40 2020 +0200
@@ -532,10 +532,10 @@
         syntaxes.insert(option);
 
         std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded(
-          GetTranscoder().TranscodeToParsed(dicom.GetParsedDicomFile().GetDcmtkObject(),
-                                            dicom.GetBufferData(), dicom.GetBufferSize(),
-                                            syntaxes, true /* allow new SOP instance UID */));
-
+          TranscodeToParsed(dicom.GetParsedDicomFile().GetDcmtkObject(),
+                            dicom.GetBufferData(), dicom.GetBufferSize(),
+                            syntaxes, true /* allow new SOP instance UID */));
+        
         if (transcoded.get() == NULL)
         {
           // Cannot transcode => store the original file
@@ -1172,28 +1172,6 @@
   }
 
 
-  IDicomTranscoder& ServerContext::GetTranscoder()
-  {
-    IDicomTranscoder* transcoder = dcmtkTranscoder_.get();
-
-#if ORTHANC_ENABLE_PLUGINS == 1
-    if (HasPlugins())
-    {
-      transcoder = &GetPlugins();
-    }
-#endif
-
-    if (transcoder == NULL)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-    else
-    {
-      return *transcoder;
-    }
-  }   
-
-
   ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId,
                                                  unsigned int frameIndex)
   {
@@ -1216,7 +1194,7 @@
       else
       {
         LOG(INFO) << "The installed image decoding plugins cannot handle an image, "
-                  << "fallback to the built-in decoder";
+                  << "fallback to the built-in DCMTK decoder";
       }
     }
 #endif
@@ -1248,7 +1226,7 @@
       else
       {
         LOG(INFO) << "The installed image decoding plugins cannot handle an image, "
-                  << "fallback to the built-in decoder";
+                  << "fallback to the built-in DCMTK decoder";
       }
     }
 #endif
@@ -1275,8 +1253,65 @@
     }
     else
     {
-      connection.Transcode(sopClassUid, sopInstanceUid, GetTranscoder(), data, dicom.size(),
+      connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(),
                            hasMoveOriginator, moveOriginatorAet, moveOriginatorId);
     }
   }
+
+
+  bool ServerContext::TranscodeParsedToBuffer(std::string& target /* out */,
+                                              bool& hasSopInstanceUidChanged /* out */,
+                                              DcmFileFormat& dicom /* in, possibly modified */,
+                                              DicomTransferSyntax targetSyntax,
+                                              bool allowNewSopInstanceUid)
+  {
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins())
+    {
+      if (GetPlugins().TranscodeParsedToBuffer(target, hasSopInstanceUidChanged, dicom,
+                                               targetSyntax, allowNewSopInstanceUid))
+      {
+        return true;
+      }
+      else
+      {
+        LOG(INFO) << "The installed transcoding plugins cannot handle an image, "
+                  << "fallback to the built-in DCMTK transcoder";
+      }
+    }
+#endif
+
+    return dcmtkTranscoder_->TranscodeParsedToBuffer(target, hasSopInstanceUidChanged, dicom,
+                                                     targetSyntax, allowNewSopInstanceUid);
+  }
+      
+
+  IDicomTranscoder::TranscodedDicom*
+  ServerContext::TranscodeToParsed(DcmFileFormat& dicom,
+                                   const void* buffer,
+                                   size_t size,
+                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                   bool allowNewSopInstanceUid)
+  {
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins())
+    {
+      std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcoded(
+        GetPlugins().TranscodeToParsed(dicom, buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
+
+      if (transcoded.get() != NULL)
+      {
+        return transcoded.release();
+      }
+      else
+      {
+        LOG(INFO) << "The installed transcoding plugins cannot handle an image, "
+                  << "fallback to the built-in DCMTK transcoder";
+      }
+    }
+#endif
+
+    return dcmtkTranscoder_->TranscodeToParsed(
+      dicom, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
+  }
 }
--- a/OrthancServer/ServerContext.h	Thu May 14 14:40:13 2020 +0200
+++ b/OrthancServer/ServerContext.h	Thu May 14 19:20:40 2020 +0200
@@ -65,6 +65,7 @@
    **/
   class ServerContext :
     public IStorageCommitmentFactory,
+    public IDicomTranscoder,
     private JobsRegistry::IObserver
   {
   public:
@@ -473,8 +474,21 @@
                               const std::string& moveOriginatorAet,
                               uint16_t moveOriginatorId);
 
-    // This accessor can be used even if the global option
+    // This method can be used even if the global option
     // "TranscodeDicomProtocol" is set to "false"
-    IDicomTranscoder& GetTranscoder();
+    virtual bool TranscodeParsedToBuffer(std::string& target /* out */,
+                                         bool& hasSopInstanceUidChanged /* out */,
+                                         DcmFileFormat& dicom /* in, possibly modified */,
+                                         DicomTransferSyntax targetSyntax,
+                                         bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    // This method can be used even if the global option
+    // "TranscodeDicomProtocol" is set to "false"
+    virtual IDicomTranscoder::TranscodedDicom* TranscodeToParsed(
+      DcmFileFormat& dicom /* in, possibly modified */,
+      const void* buffer /* in, same DICOM file as "dicom" */,
+      size_t size,
+      const std::set<DicomTransferSyntax>& allowedSyntaxes,
+      bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/ServerJobs/ArchiveJob.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/OrthancServer/ServerJobs/ArchiveJob.cpp	Thu May 14 19:20:40 2020 +0200
@@ -447,9 +447,8 @@
               const char* data = content.empty() ? NULL : content.c_str();
               
               std::unique_ptr<IDicomTranscoder::TranscodedDicom> transcodedDicom(
-                context.GetTranscoder().TranscodeToParsed(
-                  parsed->GetDcmtkObject(), data, content.size(),
-                  syntaxes, true /* allow new SOP instance UID */));
+                context.TranscodeToParsed(parsed->GetDcmtkObject(), data, content.size(),
+                                          syntaxes, true /* allow new SOP instance UID */));
 
               if (transcodedDicom.get() != NULL &&
                   transcodedDicom->GetDicom().getDataset() != NULL)
--- a/Plugins/Engine/OrthancPlugins.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Thu May 14 19:20:40 2020 +0200
@@ -890,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;
@@ -904,6 +905,7 @@
     OrthancPluginFindCallback  findCallback_;
     OrthancPluginWorklistCallback  worklistCallback_;
     DecodeImageCallbacks  decodeImageCallbacks_;
+    TranscoderCallbacks  transcoderCallbacks_;
     JobsUnserializers  jobsUnserializers_;
     _OrthancPluginMoveCallback moveCallbacks_;
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
@@ -918,7 +920,7 @@
     boost::recursive_mutex changeCallbackMutex_;
     boost::mutex findCallbackMutex_;
     boost::mutex worklistCallbackMutex_;
-    boost::shared_mutex decodeImageCallbackMutex_;  // Changed from "boost::mutex" in Orthanc 1.7.0
+    boost::shared_mutex decoderTranscoderMutex_;  // Changed from "boost::mutex" in Orthanc 1.7.0
     boost::mutex jobsUnserializersMutex_;
     boost::mutex refreshMetricsMutex_;
     boost::mutex storageCommitmentScpMutex_;
@@ -2109,7 +2111,7 @@
     const _OrthancPluginDecodeImageCallback& p = 
       *reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters);
 
-    boost::unique_lock<boost::shared_mutex> 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 (" 
@@ -2117,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 = 
@@ -4363,7 +4378,7 @@
           
           {
             PImpl::ServerContextLock lock(*pimpl_);
-            transcoded.reset(lock.GetContext().GetTranscoder().TranscodeToParsed(
+            transcoded.reset(lock.GetContext().TranscodeToParsed(
                                dicom.GetDcmtkObject(), p.buffer, p.size,
                                syntaxes, true /* allow new sop */));
           }
@@ -4380,6 +4395,25 @@
           }
         }
       }
+
+      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;
@@ -4434,6 +4468,10 @@
         RegisterDecodeImageCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterTranscoderCallback:
+        RegisterTranscoderCallback(parameters);
+        return true;
+
       case _OrthancPluginService_RegisterJobsUnserializer:
         RegisterJobsUnserializer(parameters);
         return true;
@@ -4818,7 +4856,7 @@
 
   bool OrthancPlugins::HasCustomImageDecoder()
   {
-    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decodeImageCallbackMutex_);
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
     return !pimpl_->decodeImageCallbacks_.empty();
   }
 
@@ -4827,7 +4865,7 @@
                                         size_t size,
                                         unsigned int frame)
   {
-    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decodeImageCallbackMutex_);
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
 
     for (PImpl::DecodeImageCallbacks::const_iterator
            decoder = pimpl_->decodeImageCallbacks_.begin();
@@ -5130,6 +5168,43 @@
   }
 
 
+  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::Transcode(std::string& target,
                                  bool& hasSopInstanceUidChanged /* out */,
                                  const void* buffer,
@@ -5137,7 +5212,37 @@
                                  const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                  bool allowNewSopInstanceUid)
   {
-    // TODO
+    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;
+      uint8_t b;
+
+      if ((*transcoder) (a.GetObject(), &b, 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	Thu May 14 14:40:13 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Thu May 14 19:20:40 2020 +0200
@@ -124,6 +124,8 @@
 
     void RegisterDecodeImageCallback(const void* parameters);
 
+    void RegisterTranscoderCallback(const void* parameters);
+
     void RegisterJobsUnserializer(const void* parameters);
 
     void RegisterIncomingHttpRequestFilter(const void* parameters);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Thu May 14 14:40:13 2020 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Thu May 14 19:20:40 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.
@@ -441,6 +442,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,
@@ -458,7 +460,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 */
@@ -7951,6 +7954,54 @@
       return target;
     }
   }
+
+
+
+
+  typedef OrthancPluginErrorCode (*OrthancPluginTranscoderCallback) (
+    OrthancPluginMemoryBuffer* transcoded /* out */,
+    uint8_t*                   hasSopInstanceUidChanged /* out */,
+    const void*                buffer,
+    uint64_t                   size,
+    const char* const*         allowedSyntaxes,
+    uint32_t                   countSyntaxes,
+    uint8_t                    allowNewSopInstanceUid);
+
+
+  typedef struct
+  {
+    OrthancPluginTranscoderCallback callback;
+  } _OrthancPluginTranscoderCallback;
+
+  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;
+
+  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
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu May 14 19:20:40 2020 +0200
@@ -130,6 +130,26 @@
   }
 
 
+  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);
+    }
+  }
+
+
   void MemoryBuffer::Clear()
   {
     if (buffer_.data != NULL)
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu May 14 14:40:13 2020 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu May 14 19:20:40 2020 +0200
@@ -146,6 +146,9 @@
   public:
     MemoryBuffer();
 
+    MemoryBuffer(const void* buffer,
+                 size_t size);
+
     ~MemoryBuffer()
     {
       Clear();
--- a/Plugins/Samples/GdcmDecoder/Plugin.cpp	Thu May 14 14:40:13 2020 +0200
+++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp	Thu May 14 19:20:40 2020 +0200
@@ -24,6 +24,13 @@
 #include "../../../Core/Toolbox.h"
 #include "GdcmDecoderCache.h"
 
+#include <gdcmImageChangeTransferSyntax.h>
+#include <gdcmImageReader.h>
+#include <gdcmImageWriter.h>
+#include <gdcmUIDGenerator.h>
+#include <gdcmAttribute.h>
+
+
 static OrthancPlugins::GdcmDecoderCache  cache_;
 static bool restrictTransferSyntaxes_ = false;
 static std::set<std::string> enabledTransferSyntaxes_;
@@ -148,6 +155,124 @@
 }
 
 
+OrthancPluginErrorCode TranscoderCallback(
+  OrthancPluginMemoryBuffer* transcoded /* out */,
+  uint8_t*                   hasSopInstanceUidChanged /* out */,
+  const void*                buffer,
+  uint64_t                   size,
+  const char* const*         allowedSyntaxes,
+  uint32_t                   countSyntaxes,
+  uint8_t                    allowNewSopInstanceUid)
+{
+  try
+  {
+    std::string dicom(reinterpret_cast<const char*>(buffer), size);
+    std::stringstream stream(dicom);
+
+    gdcm::ImageReader reader;
+    reader.SetStream(stream);
+    if (!reader.Read())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "GDCM cannot decode the image");
+    }
+
+    // First check that transcoding is mandatory
+    for (uint32_t i = 0; i < countSyntaxes; i++)
+    {
+      gdcm::TransferSyntax syntax(gdcm::TransferSyntax::GetTSType(allowedSyntaxes[i]));
+      if (syntax.IsValid() &&
+          reader.GetImage().GetTransferSyntax() == syntax)
+      {
+        // Same transfer syntax as in the source, return a copy of the
+        // source buffer
+        OrthancPlugins::MemoryBuffer orthancBuffer(buffer, size);
+        *transcoded = orthancBuffer.Release();
+        *hasSopInstanceUidChanged = false;
+        return OrthancPluginErrorCode_Success;
+      }
+    }
+
+    for (uint32_t i = 0; i < countSyntaxes; i++)
+    {
+      gdcm::TransferSyntax syntax(gdcm::TransferSyntax::GetTSType(allowedSyntaxes[i]));
+      if (syntax.IsValid())
+      {
+        gdcm::ImageChangeTransferSyntax change;
+        change.SetTransferSyntax(syntax);
+        change.SetInput(reader.GetImage());
+
+        if (change.Change())
+        {
+          if (syntax == gdcm::TransferSyntax::JPEGBaselineProcess1 ||
+              syntax == gdcm::TransferSyntax::JPEGExtendedProcess2_4 ||
+              syntax == gdcm::TransferSyntax::JPEGLSNearLossless ||
+              syntax == gdcm::TransferSyntax::JPEG2000 ||
+              syntax == gdcm::TransferSyntax::JPEG2000Part2)
+          {
+            // In the case of a lossy compression, generate new SOP instance UID
+            gdcm::UIDGenerator generator;
+            std::string uid = generator.Generate();
+            if (uid.size() == 0)
+            {
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                              "GDCM cannot generate a UID");
+            }
+
+            gdcm::Attribute<0x0008,0x0018> sopInstanceUid;
+            sopInstanceUid.SetValue(uid);
+            reader.GetFile().GetDataSet().Replace(sopInstanceUid.GetAsDataElement());
+            *hasSopInstanceUidChanged = 1;
+          }
+          else
+          {
+            *hasSopInstanceUidChanged = 0;
+          }
+      
+          // GDCM was able to change the transfer syntax, serialize it
+          // to the output buffer
+          gdcm::ImageWriter writer;
+          writer.SetImage(change.GetOutput());
+          writer.SetFile(reader.GetFile());
+
+          std::stringstream ss;
+          writer.SetStream(ss);
+          if (writer.Write())
+          {
+            std::string s = ss.str();
+            OrthancPlugins::MemoryBuffer orthancBuffer(s.empty() ? NULL : s.c_str(), s.size());
+            *transcoded = orthancBuffer.Release();
+
+            return OrthancPluginErrorCode_Success;
+          }
+          else
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                            "GDCM cannot serialize the image");
+          }
+        }
+      }
+    }
+    
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(INFO) << "Cannot transcode image using GDCM: " << e.What();
+    return OrthancPluginErrorCode_Plugin;
+  }
+  catch (std::runtime_error& e)
+  {
+    LOG(INFO) << "Cannot transcode image using GDCM: " << e.what();
+    return OrthancPluginErrorCode_Plugin;
+  }
+  catch (...)
+  {
+    LOG(INFO) << "Native exception while decoding image using GDCM";
+    return OrthancPluginErrorCode_Plugin;
+  }
+}
+
 
 /**
  * We force the redefinition of the "ORTHANC_PLUGINS_API" macro, that
@@ -176,23 +301,18 @@
     static const char* const KEY_RESTRICT_TRANSFER_SYNTAXES = "RestrictTransferSyntaxes";
 
     OrthancPlugins::SetGlobalContext(context);
-    LOG(INFO) << "Initializing the advanced decoder of medical images using GDCM";
-
+    Orthanc::Logging::Initialize(context);
+    LOG(INFO) << "Initializing the decoder/transcoder of medical images using GDCM";
 
     /* Check the version of the Orthanc core */
-    if (OrthancPluginCheckVersion(context) == 0)
+    if (!OrthancPlugins::CheckMinimalOrthancVersion(0, 9, 5))
     {
-      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);
+      LOG(ERROR) << "Your version of Orthanc (" << std::string(context->orthancVersion)
+                 << ") must be above 0.9.5 to run this plugin";
       return -1;
     }
 
-    OrthancPluginSetDescription(context, "Advanced decoder of medical images using GDCM.");
+    OrthancPluginSetDescription(context, "Decoder/transcoder of medical images using GDCM.");
 
     OrthancPlugins::OrthancConfiguration global;
 
@@ -220,10 +340,20 @@
     if (enabled)
     {
       OrthancPluginRegisterDecodeImageCallback(context, DecodeImageCallback);
+
+      if (OrthancPlugins::CheckMinimalOrthancVersion(1, 7, 0))
+      {
+        OrthancPluginRegisterTranscoderCallback(context, TranscoderCallback);
+      }
+      else
+      {
+        LOG(ERROR) << "Your version of Orthanc (" << std::string(context->orthancVersion)
+                   << ") must be above 1.7.0 to benefit from transcoding";
+      }
     }
     else
     {
-      LOG(WARNING) << "The advanced decoder of medical images using GDCM is disabled";
+      LOG(WARNING) << "The decoder/transcoder of medical images using GDCM is disabled";
     }
     
     return 0;
@@ -232,13 +362,13 @@
 
   ORTHANC_PLUGINS_API void OrthancPluginFinalize()
   {
-    LOG(INFO) << "Finalizing the advanced decoder of medical images using GDCM";
+    LOG(INFO) << "Finalizing the decoder/transcoder of medical images using GDCM";
   }
 
 
   ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
   {
-    return "gdcm-decoder";
+    return "gdcm";
   }