changeset 3944:aae045f802f4 transcoding

preparing simplified interface for IDicomTranscoder
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 19 May 2020 10:17:06 +0200
parents b26d25d3c1c7
children 0b3256c3ee14
files Core/DicomParsing/DcmtkTranscoder.cpp Core/DicomParsing/DcmtkTranscoder.h Core/DicomParsing/IDicomTranscoder.cpp Core/DicomParsing/IDicomTranscoder.h Core/DicomParsing/MemoryBufferTranscoder.cpp Core/DicomParsing/MemoryBufferTranscoder.h Core/DicomParsing/ParsedDicomFile.cpp Core/DicomParsing/ParsedDicomFile.h OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h
diffstat 12 files changed, 437 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomParsing/DcmtkTranscoder.cpp	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/DcmtkTranscoder.cpp	Tue May 19 10:17:06 2020 +0200
@@ -398,4 +398,51 @@
       return NULL;
     }
   }
+
+
+  bool DcmtkTranscoder::Transcode(DicomImage& target,
+                                  bool& hasSopInstanceUidChanged /* out */,
+                                  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 (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end())
+    {
+      // No transcoding is needed
+      target.AcquireParsed(source);
+      target.AcquireBuffer(source);
+      return true;
+    }
+    else if (InplaceTranscode(hasSopInstanceUidChanged, source.GetParsed(),
+                              allowedSyntaxes, allowNewSopInstanceUid))
+    {
+      // Sanity check
+      DicomTransferSyntax targetSyntax;
+      if (FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, source.GetParsed()) &&
+          allowedSyntaxes.find(targetSyntax) != allowedSyntaxes.end())
+      {
+        target.AcquireParsed(source);
+        source.Clear();
+        return true;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }  
+    }
+    else
+    {
+      // Cannot transcode
+      return false;
+    }
+  }
 }
--- a/Core/DicomParsing/DcmtkTranscoder.h	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/DcmtkTranscoder.h	Tue May 19 10:17:06 2020 +0200
@@ -82,5 +82,11 @@
       size_t size,
       const std::set<DicomTransferSyntax>& allowedSyntaxes,
       bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    virtual bool Transcode(DicomImage& target,
+                           bool& hasSopInstanceUidChanged /* out */,
+                           DicomImage& source /* in, "GetParsed()" possibly modified */,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
   };
 }
--- a/Core/DicomParsing/IDicomTranscoder.cpp	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/IDicomTranscoder.cpp	Tue May 19 10:17:06 2020 +0200
@@ -35,11 +35,187 @@
 #include "IDicomTranscoder.h"
 
 #include "../OrthancException.h"
+#include "FromDcmtkBridge.h"
+#include "ParsedDicomFile.h"
 
 #include <dcmtk/dcmdata/dcfilefo.h>
 
 namespace Orthanc
 {
+  void IDicomTranscoder::DicomImage::Parse()
+  {
+    if (parsed_.get() != NULL ||
+        buffer_.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(
+                      buffer_->empty() ? NULL : buffer_->c_str(), buffer_->size()));
+
+      if (parsed_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }      
+    }
+  }
+  
+  
+  void IDicomTranscoder::DicomImage::Serialize()
+  {
+    if (parsed_.get() == NULL ||
+        buffer_.get() != NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (parsed_->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      buffer_.reset(new std::string);
+      FromDcmtkBridge::SaveToMemoryBuffer(*buffer_, *parsed_->getDataset());
+    }
+  }
+
+  
+  void IDicomTranscoder::DicomImage::Clear()
+  {
+    parsed_.reset(NULL);
+    buffer_.reset(NULL);
+  }
+
+  
+  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_.get() != NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (parsed->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    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)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      buffer_.reset(new std::string);
+      buffer_->swap(buffer);
+    }
+  }
+
+
+  void IDicomTranscoder::DicomImage::AcquireBuffer(DicomImage& other)
+  {
+    if (buffer_.get() != NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (other.buffer_.get() == NULL)
+    {
+      buffer_.reset(NULL);
+    }
+    else
+    {
+      buffer_.reset(other.buffer_.release());
+    }    
+  }
+
+  
+  DcmFileFormat& IDicomTranscoder::DicomImage::GetParsed()
+  {
+    if (parsed_.get() != NULL)
+    {
+      return *parsed_;
+    }
+    else if (buffer_.get() != NULL)
+    {
+      Parse();
+      return *parsed_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "AcquireParsed() or AcquireBuffer() should have been called");
+    }
+  }
+  
+
+  DcmFileFormat* IDicomTranscoder::DicomImage::ReleaseParsed()
+  {
+    if (parsed_.get() != NULL)
+    {
+      buffer_.reset(NULL);
+      return parsed_.release();
+    }
+    else if (buffer_.get() != NULL)
+    {
+      Parse();
+      buffer_.reset(NULL);
+      return parsed_.release();
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                             "AcquireParsed() or AcquireBuffer() should have been called");
+    }
+  }
+
+  
+  const void* IDicomTranscoder::DicomImage::GetBufferData()
+  {
+    if (buffer_.get() == NULL)
+    {
+      Serialize();
+    }
+
+    assert(buffer_.get() != NULL);
+    return buffer_->empty() ? NULL : buffer_->c_str();
+  }
+
+  
+  size_t IDicomTranscoder::DicomImage::GetBufferSize()
+  {
+    if (buffer_.get() == NULL)
+    {
+      Serialize();
+    }
+
+    assert(buffer_.get() != NULL);
+    return buffer_->size();
+  }
+
+
   IDicomTranscoder::TranscodedDicom::TranscodedDicom(bool hasSopInstanceUidChanged) :
     external_(NULL),
     hasSopInstanceUidChanged_(hasSopInstanceUidChanged)
--- a/Core/DicomParsing/IDicomTranscoder.h	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/IDicomTranscoder.h	Tue May 19 10:17:06 2020 +0200
@@ -47,14 +47,59 @@
    * 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_;
+
+      void Parse();
+
+      void Serialize();
+
+      DcmFileFormat* ReleaseParsed();
+
+    public:
+      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);
+
+      DcmFileFormat& GetParsed();
+
+      const void* GetBufferData();
+
+      size_t GetBufferSize();
+    };
+
+    
     virtual ~IDicomTranscoder()
     {
     }
 
+
+    virtual bool Transcode(DicomImage& target,
+                           bool& hasSopInstanceUidChanged /* out */,
+                           DicomImage& source /* in, "GetParsed()" possibly modified */,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) = 0;
+                           
+
+
     virtual bool TranscodeParsedToBuffer(std::string& target /* out */,
                                          bool& hasSopInstanceUidChanged /* out */,
                                          DcmFileFormat& dicom /* in, possibly modified */,
--- a/Core/DicomParsing/MemoryBufferTranscoder.cpp	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp	Tue May 19 10:17:06 2020 +0200
@@ -86,8 +86,8 @@
     std::set<DicomTransferSyntax> allowedSyntaxes;
     allowedSyntaxes.insert(targetSyntax);
 
-    if (Transcode(target, hasSopInstanceUidChanged,
-                  data, source.size(), allowedSyntaxes, allowNewSopInstanceUid))
+    if (TranscodeBuffer(target, hasSopInstanceUidChanged,
+                        data, source.size(), allowedSyntaxes, allowNewSopInstanceUid))
     {
       CheckTargetSyntax(target, allowedSyntaxes);
       return true;
@@ -109,8 +109,8 @@
     bool hasSopInstanceUidChanged;
     
     std::string target;
-    if (Transcode(target, hasSopInstanceUidChanged,
-                  buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
+    if (TranscodeBuffer(target, hasSopInstanceUidChanged,
+                        buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
     {
       CheckTargetSyntax(target, allowedSyntaxes);
       
@@ -123,4 +123,27 @@
       return NULL;
     }
   }
+
+
+  bool MemoryBufferTranscoder::Transcode(DicomImage& target,
+                                         bool& hasSopInstanceUidChanged /* out */,
+                                         DicomImage& source,
+                                         const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                         bool allowNewSopInstanceUid)
+  {
+    target.Clear();
+    
+    std::string buffer;
+    if (TranscodeBuffer(buffer, hasSopInstanceUidChanged, source.GetBufferData(),
+                        source.GetBufferSize(), allowedSyntaxes, allowNewSopInstanceUid))
+    {
+      CheckTargetSyntax(buffer, allowedSyntaxes);  // For debug only
+      target.AcquireBuffer(buffer);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/Core/DicomParsing/MemoryBufferTranscoder.h	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/MemoryBufferTranscoder.h	Tue May 19 10:17:06 2020 +0200
@@ -41,12 +41,12 @@
   class MemoryBufferTranscoder : public IDicomTranscoder
   {
   protected:
-    virtual bool Transcode(std::string& target,
-                           bool& hasSopInstanceUidChanged /* out */,
-                           const void* buffer,
-                           size_t size,
-                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                           bool allowNewSopInstanceUid) = 0;
+    virtual bool TranscodeBuffer(std::string& target,
+                                 bool& hasSopInstanceUidChanged /* out */,
+                                 const void* buffer,
+                                 size_t size,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid) = 0;
     
   public:
     virtual bool TranscodeParsedToBuffer(std::string& target /* out */,
@@ -61,5 +61,11 @@
       size_t size,
       const std::set<DicomTransferSyntax>& allowedSyntaxes,
       bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    virtual bool Transcode(DicomImage& target /* out */,
+                           bool& hasSopInstanceUidChanged /* out */,
+                           DicomImage& source,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
   };
 }
--- a/Core/DicomParsing/ParsedDicomFile.cpp	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Tue May 19 10:17:06 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()->getCurrentXfer();
+    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,7 +867,7 @@
   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_Dicom);
     }
@@ -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)
     {
@@ -1121,7 +1122,30 @@
 
   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();
+    }
   }
 
 
@@ -1354,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);
     }    
@@ -1364,7 +1388,7 @@
   Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const
   {
     return FromDcmtkBridge::DetectEncoding(hasCodeExtensions,
-                                           *pimpl_->file_->getDataset(),
+                                           *GetDcmtkObject().getDataset(),
                                            GetDefaultDicomEncoding());
   }
 
@@ -1388,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);
   }
@@ -1400,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);
   }
@@ -1409,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);
   }
 
 
@@ -1472,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())
     {
@@ -1564,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()->getCurrentXfer();
+    E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer();
     switch (transferSyntax)
     {
       case EXS_JPEGProcess1:
@@ -1598,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());
   }
 
 
@@ -1611,21 +1635,21 @@
     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);
   }
 
 
@@ -1635,9 +1659,8 @@
     // using the meta header?
     const char* value = NULL;
 
-    assert(pimpl_->file_ != NULL);
-    if (pimpl_->file_->getMetaInfo() != NULL &&
-        pimpl_->file_->getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
+    if (GetDcmtkObject().getMetaInfo() != NULL &&
+        GetDcmtkObject().getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
         value != NULL)
     {
       result.assign(value);
@@ -1655,7 +1678,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() &&
@@ -1673,6 +1696,6 @@
 
   void ParsedDicomFile::Apply(ITagVisitor& visitor)
   {
-    FromDcmtkBridge::Apply(*pimpl_->file_->getDataset(), visitor, GetDefaultDicomEncoding());
+    FromDcmtkBridge::Apply(*GetDcmtkObject().getDataset(), visitor, GetDefaultDicomEncoding());
   }
 }
--- a/Core/DicomParsing/ParsedDicomFile.h	Mon May 18 21:37:31 2020 +0200
+++ b/Core/DicomParsing/ParsedDicomFile.h	Tue May 19 10:17:06 2020 +0200
@@ -127,6 +127,9 @@
 
     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/OrthancServer/ServerContext.cpp	Mon May 18 21:37:31 2020 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue May 19 10:17:06 2020 +0200
@@ -1413,4 +1413,48 @@
       return NULL;
     }
   }
+
+
+  bool ServerContext::Transcode(DicomImage& target,
+                                bool& hasSopInstanceUidChanged /* out */,
+                                DicomImage& source /* in, "GetParsed()" possibly modified */,
+                                const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                bool allowNewSopInstanceUid)
+  {
+    if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before)
+    {
+      if (dcmtkTranscoder_->Transcode(target, hasSopInstanceUidChanged, source,
+                                      allowedSyntaxes, allowNewSopInstanceUid))
+      {
+        return true;
+      }
+    }
+      
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins() &&
+        GetPlugins().HasCustomTranscoder())
+    {
+      if (GetPlugins().Transcode(target, hasSopInstanceUidChanged, 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, hasSopInstanceUidChanged, source,
+                                         allowedSyntaxes, allowNewSopInstanceUid);
+    }
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/OrthancServer/ServerContext.h	Mon May 18 21:37:31 2020 +0200
+++ b/OrthancServer/ServerContext.h	Tue May 19 10:17:06 2020 +0200
@@ -493,5 +493,11 @@
       size_t size,
       const std::set<DicomTransferSyntax>& allowedSyntaxes,
       bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    virtual bool Transcode(DicomImage& target,
+                           bool& hasSopInstanceUidChanged /* out */,
+                           DicomImage& source /* in, "GetParsed()" possibly modified */,
+                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
   };
 }
--- a/Plugins/Engine/OrthancPlugins.cpp	Mon May 18 21:37:31 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue May 19 10:17:06 2020 +0200
@@ -5212,12 +5212,12 @@
   };
   
 
-  bool OrthancPlugins::Transcode(std::string& target,
-                                 bool& hasSopInstanceUidChanged /* out */,
-                                 const void* buffer,
-                                 size_t size,
-                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                                 bool allowNewSopInstanceUid)
+  bool OrthancPlugins::TranscodeBuffer(std::string& target,
+                                       bool& hasSopInstanceUidChanged /* out */,
+                                       const void* buffer,
+                                       size_t size,
+                                       const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                       bool allowNewSopInstanceUid)
   {
     boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
 
--- a/Plugins/Engine/OrthancPlugins.h	Mon May 18 21:37:31 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue May 19 10:17:06 2020 +0200
@@ -239,12 +239,12 @@
 
   protected:
     // From "MemoryBufferTranscoder"
-    virtual bool Transcode(std::string& target,
-                           bool& hasSopInstanceUidChanged /* out */,
-                           const void* buffer,
-                           size_t size,
-                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
-                           bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+    virtual bool TranscodeBuffer(std::string& target,
+                                 bool& hasSopInstanceUidChanged /* out */,
+                                 const void* buffer,
+                                 size_t size,
+                                 const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
     
   public:
     OrthancPlugins();