changeset 5319:f2e1ad71e49c

added "OrthancPluginLoadDicomInstance()" to load DICOM instances from the database
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 24 Jun 2023 12:18:58 +0200
parents 68e15471b408
children e4c3950345e9
files NEWS OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Samples/Basic/Plugin.c
diffstat 8 files changed, 409 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Jun 23 18:01:55 2023 +0200
+++ b/NEWS	Sat Jun 24 12:18:58 2023 +0200
@@ -5,8 +5,13 @@
 --------
 
 * API version upgraded to 21
-* added a route to delete the output of an asynchronous job (right now only for archive jobs):
-  e.g. DELETE /jobs/../archive
+* Added a route to delete the output of an asynchronous job (right now
+  only for archive jobs): e.g. DELETE /jobs/../archive
+
+Plugins
+-------
+
+* Added "OrthancPluginLoadDicomInstance()" to load DICOM instances from the database
 
 Maintenance
 -----------
@@ -15,8 +20,6 @@
   tag (0028,0006) equals 1
 * Made Orthanc more resilient to common spelling errors in SpecificCharacterSet
 * Modality worklists plugin: allow searching on private tags (exact match only)
-* Upgraded dependencies for static builds:
-  - boost 1.82.0
 * Fix orphan files remaining in storage when working with MaximumStorageSize
   (https://discourse.orthanc-server.org/t/issue-with-deleting-incoming-dicoms-when-maximumstoragesize-is-reached/3510)
 * When deleting a resource, its parents LastUpdate metadata are now updated.
@@ -25,6 +28,8 @@
   (https://discourse.orthanc-server.org/t/orthanc-convert-ybr-to-rgb-but-does-not-change-metadata/3533).
   This might have an impact on the image returned by /dicom-web/studies/../series/../instances/../frames/1; the
   image format is now consistent with the PhotometricIntepretation DICOM Tag.
+* Upgraded dependencies for static builds:
+  - boost 1.82.0
 
 
 Version 1.12.0 (2023-04-14)
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sat Jun 24 12:18:58 2023 +0200
@@ -2124,27 +2124,24 @@
   
   void ParsedDicomFile::InjectEmptyPixelData(ValueRepresentation vr)
   {
-    DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
-             DICOM_TAG_PIXEL_DATA.GetElement());
-
-    DcmItem& dataset = *GetDcmtkObjectConst().getDataset();
+    DcmItem& dataset = *GetDcmtkObject().getDataset();
 
     DcmElement *element = NULL;
-    if (!dataset.findAndGetElement(k, element).good() ||
+    if (!dataset.findAndGetElement(DCM_PixelData, element).good() ||
         element == NULL)
     {
       // The pixel data is indeed nonexistent, insert it now
       switch (vr)
       {
         case ValueRepresentation_OtherByte:
-          if (!dataset.putAndInsertUint8Array(k, NULL, 0).good())
+          if (!dataset.putAndInsertUint8Array(DCM_PixelData, NULL, 0).good())
           {
             throw OrthancException(ErrorCode_InternalError);
           }
           break;
 
         case ValueRepresentation_OtherWord:
-          if (!dataset.putAndInsertUint16Array(k, NULL, 0).good())
+          if (!dataset.putAndInsertUint16Array(DCM_PixelData, NULL, 0).good())
           {
             throw OrthancException(ErrorCode_InternalError);
           }
@@ -2157,6 +2154,81 @@
   }
 
 
+  void ParsedDicomFile::RemoveFromPixelData()
+  {
+    DcmItem& dataset = *GetDcmtkObject().getDataset();
+
+    // We need to go backward, otherwise "dataset.card()" is invalidated
+    for (unsigned long i = dataset.card(); i > 0; i--)
+    {
+      DcmElement* element = dataset.getElement(i - 1);
+      if (element == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (element->getTag().getGroup() > DCM_PixelData.getGroup() ||
+          (element->getTag().getGroup() == DCM_PixelData.getGroup() &&
+           element->getTag().getElement() >= DCM_PixelData.getElement()))
+      {
+        std::unique_ptr<DcmElement> removal(dataset.remove(i - 1));
+      }
+    }
+  }
+  
+
+  ValueRepresentation ParsedDicomFile::GuessPixelDataValueRepresentation() const
+  {
+    /**
+     * DICOM specification is at:
+     * https://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_d.html
+     *
+     * Our algorithm for guessing the pixel data VR is imperfect, and
+     * inspired from: https://forum.dcmtk.org/viewtopic.php?t=4961
+     *
+     * "The baseline for Little Endian Implicit/Explicit is: (a) if
+     * the TS is Explicit Little Endian and the pixeldata is <= 8bpp,
+     * VR of pixel data shall be VR_OB, and (b) in all other cases, VR
+     * of pixel data shall be VR_OW."
+     **/
+    
+    DicomTransferSyntax ts;
+    if (LookupTransferSyntax(ts))
+    {
+      if (ts == DicomTransferSyntax_LittleEndianExplicit ||
+          ts == DicomTransferSyntax_BigEndianExplicit)
+      {
+        DcmItem& dataset = *GetDcmtkObjectConst().getDataset();
+        
+        uint16_t bitsAllocated;
+        if (dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good() &&
+            bitsAllocated > 8)
+        {
+          return ValueRepresentation_OtherWord;
+        }
+        else
+        {
+          return ValueRepresentation_OtherByte;
+        }
+      }
+      else if (ts == DicomTransferSyntax_LittleEndianImplicit)
+      {
+        return ValueRepresentation_OtherWord;
+      }
+      else
+      {
+        // Assume "OB" for all the compressed transfer syntaxes
+        return ValueRepresentation_OtherByte;
+      }
+    }
+    else
+    {
+      // Assume "OB" if transfer syntax is not available
+      return ValueRepresentation_OtherByte;
+    }
+  }
+
+
 #if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
   // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
   void ParsedDicomFile::DatasetToJson(Json::Value& target,
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Sat Jun 24 12:18:58 2023 +0200
@@ -311,5 +311,10 @@
                                      int& originY) const;
 
     void InjectEmptyPixelData(ValueRepresentation vr);
+
+    // Remove all the tags after pixel data
+    void RemoveFromPixelData();
+
+    ValueRepresentation GuessPixelDataValueRepresentation() const;
   };
 }
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Sat Jun 24 12:18:58 2023 +0200
@@ -3264,6 +3264,54 @@
 }
 
 
+#include "../Sources/DicomFormat/DicomArray.h"
+TEST(ParsedDicomFile, RemoveFromPixelData)
+{
+  ParsedDicomFile dicom(true);
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0000), "").good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0009), "").good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint8Array(DcmTag(0x7fe0, 0x0010), NULL, 0).good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0011), "").good());
+  ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe1, 0x0000), "").good());
+
+  {
+    DicomMap m;
+    dicom.ExtractDicomSummary(m, 0);
+
+    ASSERT_EQ(10u, m.GetSize());
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0000));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0009));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_PIXEL_DATA));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0011));
+    ASSERT_TRUE(m.HasTag(0x7fe1, 0x0000));
+  }
+
+  dicom.RemoveFromPixelData();
+
+  {
+    DicomMap m;
+    dicom.ExtractDicomSummary(m, 0);
+
+    ASSERT_EQ(7u, m.GetSize());
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0000));
+    ASSERT_TRUE(m.HasTag(0x7fe0, 0x0009));
+    ASSERT_FALSE(m.HasTag(DICOM_TAG_PIXEL_DATA));
+    ASSERT_FALSE(m.HasTag(0x7fe0, 0x0011));
+    ASSERT_FALSE(m.HasTag(0x7fe1, 0x0000));
+  }
+}
+
+
 TEST(ParsedDicomFile, DISABLED_InjectEmptyPixelData2)
 {
   static const char* PIXEL_DATA = "7FE00010";
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sat Jun 24 12:18:58 2023 +0200
@@ -2060,6 +2060,7 @@
         sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
         sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
         sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction) ||
+        sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
@@ -2502,9 +2503,8 @@
     std::string                            buffer_;
     std::unique_ptr<DicomInstanceToStore>  instance_;
 
-  public:
-    DicomInstanceFromBuffer(const void* buffer,
-                            size_t size)
+    void Setup(const void* buffer,
+               size_t size)
     {
       buffer_.assign(reinterpret_cast<const char*>(buffer), size);
 
@@ -2512,6 +2512,18 @@
       instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
     }
 
+  public:
+    DicomInstanceFromBuffer(const void* buffer,
+                            size_t size)
+    {
+      Setup(buffer, size);
+    }
+
+    DicomInstanceFromBuffer(const std::string& buffer)
+    {
+      Setup(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
+    }
+
     virtual bool CanBeFreed() const ORTHANC_OVERRIDE
     {
       return true;
@@ -2524,23 +2536,36 @@
   };
 
 
-  class OrthancPlugins::DicomInstanceFromTranscoded : public IDicomInstance
+  class OrthancPlugins::DicomInstanceFromParsed : public IDicomInstance
   {
   private:
     std::unique_ptr<ParsedDicomFile>       parsed_;
     std::unique_ptr<DicomInstanceToStore>  instance_;
 
-  public:
-    explicit DicomInstanceFromTranscoded(IDicomTranscoder::DicomImage& transcoded) :
-      parsed_(transcoded.ReleaseAsParsedDicomFile())
-    {
+    void Setup(ParsedDicomFile* parsed)
+    {
+      parsed_.reset(parsed);
+      
       if (parsed_.get() == NULL)
       {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-      
-      instance_.reset(DicomInstanceToStore::CreateFromParsedDicomFile(*parsed_));
-      instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+      else
+      {
+        instance_.reset(DicomInstanceToStore::CreateFromParsedDicomFile(*parsed_));
+        instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
+      }
+    }
+
+  public:
+    explicit DicomInstanceFromParsed(IDicomTranscoder::DicomImage& transcoded)
+    {
+      Setup(transcoded.ReleaseAsParsedDicomFile());
+    }
+
+    explicit DicomInstanceFromParsed(ParsedDicomFile* parsed /* takes ownership */)
+    {
+      Setup(parsed);
     }
 
     virtual bool CanBeFreed() const ORTHANC_OVERRIDE
@@ -4386,7 +4411,99 @@
     
     reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
   }
-      
+
+
+  void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params)
+  {
+    std::unique_ptr<IDicomInstance> target;
+    
+    switch (params.mode)
+    {
+      case OrthancPluginLoadDicomInstanceMode_WholeDicom:
+      {
+        std::string buffer;
+
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          lock.GetContext().ReadDicom(buffer, params.instanceId);
+        }
+
+        target.reset(new DicomInstanceFromBuffer(buffer));
+        break;
+      }
+        
+      case OrthancPluginLoadDicomInstanceMode_UntilPixelData:
+      case OrthancPluginLoadDicomInstanceMode_EmptyPixelData:
+      {
+        std::unique_ptr<ParsedDicomFile> parsed;
+
+        {
+          std::string buffer;
+        
+          {
+            PImpl::ServerContextLock lock(*pimpl_);
+            if (!lock.GetContext().ReadDicomUntilPixelData(buffer, params.instanceId))
+            {
+              lock.GetContext().ReadDicom(buffer, params.instanceId);
+            }
+          }
+
+          parsed.reset(new ParsedDicomFile(buffer));
+        }
+
+        parsed->RemoveFromPixelData();
+
+        if (params.mode == OrthancPluginLoadDicomInstanceMode_EmptyPixelData)
+        {
+          ValueRepresentation vr = parsed->GuessPixelDataValueRepresentation();
+
+          // Try and retrieve the VR of pixel data from the metadata of the instance
+          {
+            PImpl::ServerContextLock lock(*pimpl_);
+
+            std::string s;
+            int64_t revision;  // unused
+            if (lock.GetContext().GetIndex().LookupMetadata(
+                  s, revision, params.instanceId,
+                  ResourceType_Instance, MetadataType_Instance_PixelDataVR))
+            {
+              if (s == "OB")
+              {
+                vr = ValueRepresentation_OtherByte;
+              }
+              else if (s == "OW")
+              {
+                vr = ValueRepresentation_OtherWord;
+              }
+              else
+              {
+                LOG(WARNING) << "Corrupted PixelDataVR metadata associated with instance "
+                             << params.instanceId << ": " << s;
+              }
+            }
+          }
+          
+          parsed->InjectEmptyPixelData(vr);
+        }
+
+        target.reset(new DicomInstanceFromParsed(parsed.release()));
+        break;
+      }
+        
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (target.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      *params.target = reinterpret_cast<OrthancPluginDicomInstance*>(target.release());
+    } 
+  }
+  
 
   void OrthancPlugins::DatabaseAnswer(const void* parameters)
   {
@@ -5279,7 +5396,7 @@
           if (success)
           {
             *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
-              new DicomInstanceFromTranscoded(transcoded));
+              new DicomInstanceFromParsed(transcoded));
             return true;
           }
           else
@@ -5341,6 +5458,14 @@
         RegisterIncomingHttpRequestFilter2(parameters);
         return true;
 
+      case _OrthancPluginService_LoadDicomInstance:
+      {
+        const _OrthancPluginLoadDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginLoadDicomInstance*>(parameters);
+        ApplyLoadDicomInstance(p);
+        return true;
+      }
+
       default:
         return false;
     }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Sat Jun 24 12:18:58 2023 +0200
@@ -89,7 +89,7 @@
     class IDicomInstance;
     class DicomInstanceFromCallback;
     class DicomInstanceFromBuffer;
-    class DicomInstanceFromTranscoded;
+    class DicomInstanceFromParsed;
     class WebDavCollection;
     
     void RegisterRestCallback(const void* parameters,
@@ -217,6 +217,8 @@
 
     void ApplySendMultipartItem2(const void* parameters);
 
+    void ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& parameters);
+
     void ComputeHash(_OrthancPluginService service,
                      const void* parameters);
 
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Sat Jun 24 12:18:58 2023 +0200
@@ -120,7 +120,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     12
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -526,6 +526,7 @@
     _OrthancPluginService_GetInstanceAdvancedJson = 4017,  /* New in Orthanc 1.7.0 */
     _OrthancPluginService_GetInstanceDicomWebJson = 4018,  /* New in Orthanc 1.7.0 */
     _OrthancPluginService_GetInstanceDicomWebXml = 4019,   /* New in Orthanc 1.7.0 */
+    _OrthancPluginService_LoadDicomInstance = 4020,        /* New in Orthanc 1.12.1 */
     
     /* Services for plugins implementing a database back-end */
     _OrthancPluginService_RegisterDatabaseBackend = 5000,    /* New in Orthanc 0.8.6 */
@@ -1024,6 +1025,28 @@
 
 
   /**
+   * Mode specifying how to load a DICOM instance.
+   * @see OrthancPluginLoadDicomInstance
+   **/
+  typedef enum
+  {
+    OrthancPluginLoadDicomInstanceMode_WholeDicom = 1,
+    /*!< Load the whole DICOM file, including pixel data */
+
+    OrthancPluginLoadDicomInstanceMode_UntilPixelData = 2,
+    /*!< Load the whole DICOM file until pixel data, which will speed
+      up the loading */
+
+    OrthancPluginLoadDicomInstanceMode_EmptyPixelData = 3,
+    /*!< Load the whole DICOM file until pixel data, and replace pixel
+      data by an empty tag whose VR (value representation) is the same
+      as those of the original DICOM file */
+
+    _OrthancPluginLoadDicomInstanceMode_INTERNAL = 0x7fffffff
+  } OrthancPluginLoadDicomInstanceMode;
+
+
+  /**
    * @brief A 32-bit memory buffer allocated by the core system of Orthanc.
    *
    * A memory buffer allocated by the core system of Orthanc. When the
@@ -1906,7 +1929,7 @@
         sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
         sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
-        sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction))
+        sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode))
     {
       /* Mismatch in the size of the enumerations */
       return 0;
@@ -9225,6 +9248,51 @@
     return context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV4, &params);
   }
 
+
+  typedef struct
+  {
+    OrthancPluginDicomInstance**        target;
+    const char*                         instanceId;
+    OrthancPluginLoadDicomInstanceMode  mode;
+  } _OrthancPluginLoadDicomInstance;
+
+  /**
+   * @brief Load a DICOM instance from the Orthanc server.
+   *
+   * This function loads a DICOM instance from the content of the
+   * Orthanc database. The function returns a new pointer to a data
+   * structure that is managed by the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @param mode Flag specifying how to deal with pixel data.
+   * @return The newly allocated DICOM instance. It must be freed with OrthancPluginFreeDicomInstance().
+   * @ingroup DicomInstance
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDicomInstance* OrthancPluginLoadDicomInstance(
+    OrthancPluginContext*               context,
+    const char*                         instanceId,
+    OrthancPluginLoadDicomInstanceMode  mode)
+  {
+    OrthancPluginDicomInstance* target = NULL;
+
+    _OrthancPluginLoadDicomInstance params;
+    params.target = &target;
+    params.instanceId = instanceId;
+    params.mode = mode;
+
+    if (context->InvokeService(context, _OrthancPluginService_LoadDicomInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Plugins/Samples/Basic/Plugin.c	Fri Jun 23 18:01:55 2023 +0200
+++ b/OrthancServer/Plugins/Samples/Basic/Plugin.c	Sat Jun 24 12:18:58 2023 +0200
@@ -282,6 +282,60 @@
 }
 
 
+OrthancPluginErrorCode CallbackDicomWeb(OrthancPluginRestOutput* output,
+                                        const char* url,
+                                        const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context, output, "GET");
+  }
+  else
+  {
+    OrthancPluginLoadDicomInstanceMode mode = OrthancPluginLoadDicomInstanceMode_WholeDicom;
+    if (request->getCount == 1)
+    {
+      if (strcmp(request->getKeys[0], "until-pixel-data") == 0)
+      {
+        mode = OrthancPluginLoadDicomInstanceMode_UntilPixelData;
+      }
+      else if (strcmp(request->getKeys[0], "empty-pixel-data") == 0)
+      {
+        mode = OrthancPluginLoadDicomInstanceMode_EmptyPixelData;
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;
+      }
+    }
+    
+    OrthancPluginDicomInstance* instance = OrthancPluginLoadDicomInstance(context, request->groups[0], mode);
+    if (instance == NULL)
+    {
+      return OrthancPluginErrorCode_UnknownResource;
+    }
+
+    char* json = OrthancPluginEncodeDicomWebXml(context,
+                                                OrthancPluginGetInstanceData(context, instance),
+                                                OrthancPluginGetInstanceSize(context, instance),
+                                                DicomWebBinaryCallback);
+    OrthancPluginFreeDicomInstance(context, instance);
+
+    if (json != NULL)
+    {
+      OrthancPluginAnswerBuffer(context, output, json, strlen(json), "application/json");
+      OrthancPluginFreeString(context, json);
+    }
+    else
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
 OrthancPluginErrorCode OnStoredCallback(const OrthancPluginDicomInstance* instance,
                                         const char* instanceId)
 {
@@ -511,6 +565,7 @@
   OrthancPluginRegisterRestCallback(context, "/forward/(built-in)(/.+)", Callback5);
   OrthancPluginRegisterRestCallback(context, "/forward/(plugins)(/.+)", Callback5);
   OrthancPluginRegisterRestCallback(context, "/plugin/create", CallbackCreateDicom);
+  OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/dicom-web", CallbackDicomWeb);
 
   OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback);
   OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);