changeset 3916:0e3849268a55 transcoding

new plugin SDK primitives related to OrthancPluginDicomInstance
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 11 May 2020 21:07:36 +0200
parents 7e33516965f8
children 04fb907dfc85
files NEWS OrthancServer/IServerListener.h OrthancServer/LuaScripting.cpp OrthancServer/LuaScripting.h OrthancServer/ServerContext.h 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/CMakeLists.txt
diffstat 11 files changed, 561 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon May 11 15:13:16 2020 +0200
+++ b/NEWS	Mon May 11 21:07:36 2020 +0200
@@ -17,6 +17,17 @@
   - "/ordered-slices": reverted the change introduced in 1.5.8 and go-back 
     to 1.5.7 behaviour.
 
+Plugins
+-------
+
+* New functions in the SDK:
+  - OrthancPluginCreateDicomInstance()
+  - OrthancPluginFreeDicomInstance()
+  - OrthancPluginGetInstanceFramesCount()
+  - OrthancPluginGetInstanceRawFrame()
+  - OrthancPluginGetInstanceDecodedFrame()
+* "OrthancPluginDicomInstance" structure wrapped in "OrthancPluginCppWrapper.h"
+
 Maintenance
 -----------
 
--- a/OrthancServer/IServerListener.h	Mon May 11 15:13:16 2020 +0200
+++ b/OrthancServer/IServerListener.h	Mon May 11 21:07:36 2020 +0200
@@ -48,7 +48,7 @@
     }
 
     virtual void SignalStoredInstance(const std::string& publicId,
-                                      DicomInstanceToStore& instance,
+                                      const DicomInstanceToStore& instance,
                                       const Json::Value& simplifiedTags) = 0;
     
     virtual void SignalChange(const ServerIndexChange& change) = 0;
--- a/OrthancServer/LuaScripting.cpp	Mon May 11 15:13:16 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Mon May 11 21:07:36 2020 +0200
@@ -821,7 +821,7 @@
 
 
   void LuaScripting::SignalStoredInstance(const std::string& publicId,
-                                          DicomInstanceToStore& instance,
+                                          const DicomInstanceToStore& instance,
                                           const Json::Value& simplifiedTags)
   {
     Json::Value metadata = Json::objectValue;
--- a/OrthancServer/LuaScripting.h	Mon May 11 15:13:16 2020 +0200
+++ b/OrthancServer/LuaScripting.h	Mon May 11 21:07:36 2020 +0200
@@ -121,7 +121,7 @@
     void Stop();
     
     void SignalStoredInstance(const std::string& publicId,
-                              DicomInstanceToStore& instance,
+                              const DicomInstanceToStore& instance,
                               const Json::Value& simplifiedTags);
 
     void SignalChange(const ServerIndexChange& change);
--- a/OrthancServer/ServerContext.h	Mon May 11 15:13:16 2020 +0200
+++ b/OrthancServer/ServerContext.h	Mon May 11 21:07:36 2020 +0200
@@ -99,7 +99,7 @@
       }
 
       virtual void SignalStoredInstance(const std::string& publicId,
-                                        DicomInstanceToStore& instance,
+                                        const DicomInstanceToStore& instance,
                                         const Json::Value& simplifiedTags)
       {
         context_.mainLua_.SignalStoredInstance(publicId, instance, simplifiedTags);
--- a/Plugins/Engine/OrthancPlugins.cpp	Mon May 11 15:13:16 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Mon May 11 21:07:36 2020 +0200
@@ -1761,19 +1761,84 @@
   }
 
 
+  class OrthancPlugins::IDicomInstance : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomInstance()
+    {
+    }
+
+    virtual bool CanBeFreed() const = 0;
+
+    virtual const DicomInstanceToStore& GetInstance() const = 0;
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromCallback : public IDicomInstance
+  {
+  private:
+    const DicomInstanceToStore&  instance_;
+
+  public:
+    DicomInstanceFromCallback(const DicomInstanceToStore& instance) :
+      instance_(instance)
+    {
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromBuffer : public IDicomInstance
+  {
+  private:
+    std::string           buffer_;
+    DicomInstanceToStore  instance_;
+
+  public:
+    DicomInstanceFromBuffer(const void* buffer,
+                            size_t size)
+    {
+      buffer_.assign(reinterpret_cast<const char*>(buffer), size);
+      instance_.SetBuffer(buffer_.empty() ? NULL : buffer_.c_str(), buffer_.size());
+      instance_.SetOrigin(DicomInstanceOrigin::FromPlugins());
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
   void OrthancPlugins::SignalStoredInstance(const std::string& instanceId,
-                                            DicomInstanceToStore& instance,
+                                            const DicomInstanceToStore& instance,
                                             const Json::Value& simplifiedTags)
   {
+    DicomInstanceFromCallback wrapped(instance);
+    
     boost::recursive_mutex::scoped_lock lock(pimpl_->storedCallbackMutex_);
 
     for (PImpl::OnStoredCallbacks::const_iterator
            callback = pimpl_->onStoredCallbacks_.begin(); 
          callback != pimpl_->onStoredCallbacks_.end(); ++callback)
     {
-      OrthancPluginErrorCode error = (*callback) 
-        (reinterpret_cast<OrthancPluginDicomInstance*>(&instance),
-         instanceId.c_str());
+      OrthancPluginErrorCode error = (*callback) (
+        reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped),
+        instanceId.c_str());
 
       if (error != OrthancPluginErrorCode_Success)
       {
@@ -1787,14 +1852,15 @@
   bool OrthancPlugins::FilterIncomingInstance(const DicomInstanceToStore& instance,
                                               const Json::Value& simplified)
   {
+    DicomInstanceFromCallback wrapped(instance);
+    
     boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
     
     for (PImpl::IncomingDicomInstanceFilters::const_iterator
            filter = pimpl_->incomingDicomInstanceFilters_.begin();
          filter != pimpl_->incomingDicomInstanceFilters_.end(); ++filter)
     {
-      int32_t allowed = (*filter) (
-        reinterpret_cast<const OrthancPluginDicomInstance*>(&instance));
+      int32_t allowed = (*filter) (reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
 
       if (allowed == 0)
       {
@@ -2451,14 +2517,19 @@
   }
 
 
-  static void AccessDicomInstance(_OrthancPluginService service,
-                                  const void* parameters)
+  void OrthancPlugins::AccessDicomInstance(_OrthancPluginService service,
+                                           const void* parameters)
   {
     const _OrthancPluginAccessDicomInstance& p = 
       *reinterpret_cast<const _OrthancPluginAccessDicomInstance*>(parameters);
 
+    if (p.instance == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
     const DicomInstanceToStore& instance =
-      *reinterpret_cast<const DicomInstanceToStore*>(p.instance);
+      reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
 
     switch (service)
     {
@@ -2523,6 +2594,10 @@
         *p.resultInt64 = instance.HasPixelData();
         return;
 
+      case _OrthancPluginService_GetInstanceFramesCount:  // New in Orthanc 1.7.0
+        *p.resultInt64 = instance.GetParsedDicomFile().GetFramesCount();
+        return;
+        
       default:
         throw OrthancException(ErrorCode_InternalError);
     }
@@ -2606,6 +2681,79 @@
   }
 
 
+  void OrthancPlugins::AccessDicomInstance2(_OrthancPluginService service,
+                                            const void* parameters)
+  {
+    const _OrthancPluginAccessDicomInstance2& p = 
+      *reinterpret_cast<const _OrthancPluginAccessDicomInstance2*>(parameters);
+
+    if (p.instance == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    const DicomInstanceToStore& instance =
+      reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
+
+    switch (service)
+    {
+      case _OrthancPluginService_GetInstanceFramesCount:
+        *p.targetUint32 = instance.GetParsedDicomFile().GetFramesCount();
+        return;
+        
+      case _OrthancPluginService_GetInstanceRawFrame:
+      {
+        if (p.targetBuffer == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        p.targetBuffer->data = NULL;
+        p.targetBuffer->size = 0;
+        
+        MimeType mime;
+        std::string frame;
+        instance.GetParsedDicomFile().GetRawFrame(frame, mime, p.frameIndex);
+        CopyToMemoryBuffer(*p.targetBuffer, frame);
+        return;
+      }
+        
+      case _OrthancPluginService_GetInstanceDecodedFrame:
+      {
+        bool hasDecoderPlugin;
+
+        {
+          boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
+          hasDecoderPlugin = !pimpl_->decodeImageCallbacks_.empty();
+        }
+
+        std::unique_ptr<ImageAccessor> decoded;
+        if (p.targetImage == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        else if (hasDecoderPlugin)
+        {
+          // TODO - This call could be speeded up the future, if a
+          // "decoding context" gets introduced in the decoder plugins
+          
+          decoded.reset(Decode(instance.GetBufferData(), instance.GetBufferSize(), p.frameIndex));
+        }
+        else
+        {
+          decoded.reset(DicomImageDecoder::Decode(instance.GetParsedDicomFile(), p.frameIndex));
+        }
+
+        *(p.targetImage) = ReturnImage(decoded);
+        return;
+      }
+        
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
   void OrthancPlugins::UncompressImage(const void* parameters)
   {
     const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
@@ -3481,6 +3629,12 @@
         AccessDicomInstance(service, parameters);
         return true;
 
+      case _OrthancPluginService_GetInstanceFramesCount:
+      case _OrthancPluginService_GetInstanceRawFrame:
+      case _OrthancPluginService_GetInstanceDecodedFrame:
+        AccessDicomInstance2(service, parameters);
+        return true;
+
       case _OrthancPluginService_SetGlobalProperty:
       {
         const _OrthancPluginGlobalProperty& p = 
@@ -4027,6 +4181,37 @@
         GetTagName(parameters);
         return true;
 
+      case _OrthancPluginService_CreateDicomInstance:
+      {
+        const _OrthancPluginCreateDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
+        *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
+          new DicomInstanceFromBuffer(p.buffer, p.size));
+        return true;
+      }
+        
+      case _OrthancPluginService_FreeDicomInstance:
+      {
+        const _OrthancPluginFreeDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginFreeDicomInstance*>(parameters);
+
+        if (p.dicom != NULL)
+        {
+          IDicomInstance* obj = reinterpret_cast<IDicomInstance*>(p.dicom);
+          
+          if (obj->CanBeFreed())
+          {
+            delete obj;
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_Plugin, "Cannot free a DICOM instance provided to a callback");
+          }
+        }
+
+        return true;
+      }
+
       default:
         return false;
     }
--- a/Plugins/Engine/OrthancPlugins.h	Mon May 11 15:13:16 2020 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Mon May 11 21:07:36 2020 +0200
@@ -96,6 +96,9 @@
     class HttpClientChunkedRequest;
     class HttpClientChunkedAnswer;
     class HttpServerChunkedReader;
+    class IDicomInstance;
+    class DicomInstanceFromCallback;
+    class DicomInstanceFromBuffer;
     
     void RegisterRestCallback(const void* parameters,
                               bool lock);
@@ -157,6 +160,12 @@
     void LookupResource(_OrthancPluginService service,
                         const void* parameters);
 
+    void AccessDicomInstance(_OrthancPluginService service,
+                             const void* parameters);
+    
+    void AccessDicomInstance2(_OrthancPluginService service,
+                              const void* parameters);
+    
     void SendHttpStatusCode(const void* parameters);
 
     void SendHttpStatus(const void* parameters);
@@ -263,7 +272,7 @@
     virtual void SignalChange(const ServerIndexChange& change) ORTHANC_OVERRIDE;
     
     virtual void SignalStoredInstance(const std::string& instanceId,
-                                      DicomInstanceToStore& instance,
+                                      const DicomInstanceToStore& instance,
                                       const Json::Value& simplifiedTags) ORTHANC_OVERRIDE;
 
     virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance,
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Mon May 11 15:13:16 2020 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Mon May 11 21:07:36 2020 +0200
@@ -124,8 +124,8 @@
 #endif
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     6
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     7
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -502,7 +502,12 @@
     _OrthancPluginService_GetInstanceOrigin = 4007,
     _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008,
     _OrthancPluginService_HasInstancePixelData = 4009,
-
+    _OrthancPluginService_CreateDicomInstance = 4010,      /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_FreeDicomInstance = 4011,        /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceFramesCount = 4012,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceRawFrame = 4013,      /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_GetInstanceDecodedFrame = 4014,  /* New in Orthanc 1.7.0 */
+    
     /* Services for plugins implementing a database back-end */
     _OrthancPluginService_RegisterDatabaseBackend = 5000,
     _OrthancPluginService_DatabaseAnswer = 5001,
@@ -7538,6 +7543,125 @@
   }
 
 
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance**  target;
+    const void*                   buffer;
+    uint32_t                      size;
+  } _OrthancPluginCreateDicomInstance;
+
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginCreateDicomInstance(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginCreateDicomInstance params;
+    params.target = &target;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance*   dicom;
+  } _OrthancPluginFreeDicomInstance;
+
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeDicomInstance(
+    OrthancPluginContext*        context, 
+    OrthancPluginDicomInstance*  dicom)
+  {
+    _OrthancPluginFreeDicomInstance params;
+    params.dicom = dicom;
+
+    context->InvokeService(context, _OrthancPluginService_FreeDicomInstance, &params);
+  }
+
+
+  typedef struct
+  {
+    uint32_t*                          targetUint32;
+    OrthancPluginMemoryBuffer*         targetBuffer;
+    OrthancPluginImage**               targetImage;
+    const OrthancPluginDicomInstance*  instance;
+    uint32_t                           frameIndex;
+  } _OrthancPluginAccessDicomInstance2;
+
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetInstanceFramesCount(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance)
+  {
+    uint32_t count;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetUint32 = &count;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceFramesCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetInstanceRawFrame(
+    OrthancPluginContext*             context,
+    OrthancPluginMemoryBuffer*        target,
+    const OrthancPluginDicomInstance* instance,
+    uint32_t                          frameIndex)
+  {
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetBuffer = target;
+    params.instance = instance;
+    params.frameIndex = frameIndex;
+
+    return context->InvokeService(context, _OrthancPluginService_GetInstanceRawFrame, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginGetInstanceDecodedFrame(
+    OrthancPluginContext*             context,
+    const OrthancPluginDicomInstance* instance,
+    uint32_t                          frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginAccessDicomInstance2 params;
+    memset(&params, 0, sizeof(params));
+    params.targetImage = &target;
+    params.instance = instance;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceDecodedFrame, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Mon May 11 15:13:16 2020 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Mon May 11 21:07:36 2020 +0200
@@ -40,6 +40,11 @@
 #include <json/writer.h>
 
 
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
+#endif
+
+
 namespace OrthancPlugins
 {
   static OrthancPluginContext* globalContext_ = NULL;
@@ -3185,4 +3190,140 @@
     delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
   }
 #endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#else
+  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
+    toFree_(false),
+    instance_(instance)
+  {
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  DicomInstance::DicomInstance(const void* buffer,
+                               size_t size) :
+    toFree_(true),
+    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
+  {
+    if (instance_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
+    }
+  }
+#endif
+
+
+  DicomInstance::~DicomInstance()
+  {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    if (toFree_ &&
+        instance_ != NULL)
+    {
+      OrthancPluginFreeDicomInstance(
+        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
+    }
+#endif
+  }
+
+  
+  std::string DicomInstance::GetRemoteAet() const
+  {
+    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return std::string(s);
+    }
+  }
+
+
+  void DicomInstance::GetJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+  
+
+  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
+    s.ToJson(target);
+  }
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  std::string DicomInstance::GetTransferSyntaxUid() const
+  {
+    OrthancString s;
+    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));
+
+    std::string result;
+    s.ToString(result);
+    return result;
+  }
+#endif
+
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+  bool DicomInstance::HasPixelData() const
+  {
+    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
+    if (result < 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  void DicomInstance::GetRawFrame(std::string& target,
+                                  unsigned int frameIndex) const
+  {
+    OrthancPluginMemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
+      GetGlobalContext(), &buffer, instance_, frameIndex);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
+  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
+  {
+    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
+      GetGlobalContext(), instance_, frameIndex);
+
+    if (image == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return new OrthancImage(image);
+    }
+  }
+#endif  
 }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Mon May 11 15:13:16 2020 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Mon May 11 21:07:36 2020 +0200
@@ -386,8 +386,7 @@
                  uint32_t                  width,
                  uint32_t                  height,
                  uint32_t                  pitch,
-                 void*                     buffer
-      );
+                 void*                     buffer);
 
     ~OrthancImage()
     {
@@ -1139,4 +1138,76 @@
     static void Destructor(void* rawHandler);
   };
 #endif
+
+
+  class DicomInstance : public boost::noncopyable
+  {
+  private:
+    bool toFree_;
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    const OrthancPluginDicomInstance*  instance_;
+#else
+    OrthancPluginDicomInstance*  instance_;
+#endif
+    
+  public:
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
+    DicomInstance(const OrthancPluginDicomInstance* instance);
+#else
+    DicomInstance(OrthancPluginDicomInstance* instance);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    DicomInstance(const void* buffer,
+                  size_t size);
+#endif
+
+    ~DicomInstance();
+
+    std::string GetRemoteAet() const;
+
+    const void* GetBuffer() const
+    {
+      return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
+    }
+
+    size_t GetSize() const
+    {
+      return OrthancPluginGetInstanceSize(GetGlobalContext(), instance_);
+    }
+
+    void GetJson(Json::Value& target) const;
+
+    void GetSimplifiedJson(Json::Value& target) const;
+
+    OrthancPluginInstanceOrigin GetOrigin() const
+    {
+      return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
+    }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    std::string GetTransferSyntaxUid() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
+    bool HasPixelData() const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    unsigned int GetFramesCount() const
+    {
+      return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void GetRawFrame(std::string& target,
+                     unsigned int frameIndex) const;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
+#endif
+  };
 }
--- a/Plugins/Samples/GdcmDecoder/CMakeLists.txt	Mon May 11 15:13:16 2020 +0200
+++ b/Plugins/Samples/GdcmDecoder/CMakeLists.txt	Mon May 11 21:07:36 2020 +0200
@@ -21,7 +21,7 @@
 
 project(GdcmDecoder)
 
-SET(PLUGIN_VERSION "0.0" CACHE STRING "Version of the plugin")
+SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin")
 
 
 # Parameters of the build