changeset 4851:abb0e1c2b88d

merge received-instance-callback
author Alain Mazy <am@osimis.io>
date Tue, 14 Dec 2021 22:52:59 +0100
parents cab8d689a9a1 (current diff) 5ec96ada5a53 (diff)
children b3d0a3a4d890
files
diffstat 11 files changed, 434 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue Dec 14 22:52:59 2021 +0100
@@ -734,10 +734,15 @@
           if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
           {
             Uint8* data = NULL;
+            Uint16* data16 = NULL;
             if (element.getUint8Array(data) == EC_Normal)
             {
               return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
             }
+            else if (element.getUint16Array(data16) == EC_Normal)
+            {
+              return new DicomValue(reinterpret_cast<const char*>(data16), element.getLength(), true);
+            }
           }
 
           return new DicomValue;
@@ -1851,6 +1856,23 @@
           break;
         }
 
+        case EVR_xs: // unsigned short, signed short or multiple values
+        {
+          if (decoded->find('\\') != std::string::npos)
+          {
+            ok = element.putString(decoded->c_str()).good();
+          }
+          else if (decoded->find('-') != std::string::npos)
+          {
+            ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          }
+          else
+          {
+            ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();  
+          }
+          break;
+        }
+
         case EVR_US:  // unsigned short
         {
           ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
@@ -1902,7 +1924,6 @@
          **/ 
 
         case EVR_ox:  // OB or OW depending on context
-        case EVR_xs:  // SS or US depending on context
         case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
         case EVR_na:  // na="not applicable", for data which has no VR
         case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue Dec 14 22:52:59 2021 +0100
@@ -1195,6 +1195,10 @@
         EmbedImage(mime, content);
         break;
 
+      case MimeType_Binary:
+        EmbedImage(mime, content);
+        break;
+
       case MimeType_Pdf:
         EmbedPdf(content);
         break;
@@ -1254,6 +1258,12 @@
         break;
       }
 
+      case MimeType_Binary:
+      {
+        EmbedRawPixelData(content);
+        break;
+      }
+
       default:
         throw OrthancException(ErrorCode_NotImplemented);
     }
@@ -1407,7 +1417,24 @@
     }    
   }
 
-  
+  void ParsedDicomFile::EmbedRawPixelData(const std::string& content)
+  {
+    DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), 
+               DICOM_TAG_PIXEL_DATA.GetElement());
+
+    std::unique_ptr<DcmPixelData> pixels(new DcmPixelData(key));
+
+    Uint8* target = NULL;
+    pixels->createUint8Array(content.size(), target);
+    memcpy(target, content.c_str(), content.size());
+
+    if (!GetDcmtkObject().getDataset()->insert(pixels.release(), false, false).good())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
   Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const
   {
     return FromDcmtkBridge::DetectEncoding(hasCodeExtensions,
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Tue Dec 14 22:52:59 2021 +0100
@@ -212,6 +212,8 @@
     void EmbedImage(MimeType mime,
                     const std::string& content);
 
+    void EmbedRawPixelData(const std::string& content);
+
     Encoding DetectEncoding(bool& hasCodeExtensions) const;
 
     // WARNING: This function only sets the encoding, it will not
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Tue Dec 14 22:52:59 2021 +0100
@@ -756,6 +756,15 @@
   ASSERT_FALSE(d > d);
 }
 
+TEST(ParsedDicomFile, canIncludeXsVrTags)
+{
+  Json::Value tags;
+  tags["0028,0034"] = "1\\1";         // PixelAspectRatio
+  tags["0028,1101"] = "256\\0\\16";   // RedPaletteColorLookupTableDescriptor which is declared as xs VR in dicom.dic
+
+  std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(tags, DicomFromJsonFlags_DecodeDataUriScheme, ""));
+  // simply make sure it does not throw !
+}
 
 
 #if ORTHANC_SANDBOXED != 1
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue Dec 14 22:52:59 2021 +0100
@@ -1168,6 +1168,7 @@
     typedef std::list<OrthancPluginIncomingHttpRequestFilter2>  IncomingHttpRequestFilters2;
     typedef std::list<OrthancPluginIncomingDicomInstanceFilter>  IncomingDicomInstanceFilters;
     typedef std::list<OrthancPluginIncomingCStoreInstanceFilter>  IncomingCStoreInstanceFilters;
+    typedef std::list<OrthancPluginReceivedInstanceCallback>  ReceivedInstanceCallbacks;
     typedef std::list<OrthancPluginDecodeImageCallback>  DecodeImageCallbacks;
     typedef std::list<OrthancPluginTranscoderCallback>  TranscoderCallbacks;
     typedef std::list<OrthancPluginJobsUnserializer>  JobsUnserializers;
@@ -1191,6 +1192,7 @@
     IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
     IncomingDicomInstanceFilters  incomingDicomInstanceFilters_;
     IncomingCStoreInstanceFilters  incomingCStoreInstanceFilters_;  // New in Orthanc 1.9.8
+    ReceivedInstanceCallbacks  receivedInstanceCallbacks_;  // New in Orthanc 1.9.8
     RefreshMetricsCallbacks refreshMetricsCallbacks_;
     StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
     std::unique_ptr<StorageAreaFactory>  storageArea_;
@@ -2295,6 +2297,74 @@
   }
 
 
+  bool OrthancPlugins::ApplyReceivedInstanceCallbacks(const void* receivedDicom,
+                                                      size_t receivedDicomSize,
+                                                      void** modifiedDicomBufferData,
+                                                      size_t& modifiedDicomBufferSize)
+  {
+    uint64_t modifiedDicomSize64 = 0;
+    *modifiedDicomBufferData = NULL;
+
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    
+    for (PImpl::ReceivedInstanceCallbacks::const_iterator
+           callback = pimpl_->receivedInstanceCallbacks_.begin();
+         callback != pimpl_->receivedInstanceCallbacks_.end(); ++callback)
+    {
+      OrthancPluginReceivedInstanceCallbackResult callbackResult = (*callback) (receivedDicom,
+                                                                                receivedDicomSize,
+                                                                                modifiedDicomBufferData,
+                                                                                &modifiedDicomSize64);
+
+      if (callbackResult == OrthancPluginReceivedInstanceCallbackResult_Discard)
+      {
+        if (modifiedDicomSize64 > 0 || *modifiedDicomBufferData != NULL)
+        {
+          free(modifiedDicomBufferData);
+          throw OrthancException(ErrorCode_Plugin, "The ReceivedInstanceCallback plugin is returning a modified buffer while it has discarded the instance");
+        }
+
+        CLOG(INFO, PLUGINS) << "A plugin has discarded the instance in its ReceivedInstanceCallback";        
+        return false;
+      }
+      else if (callbackResult == OrthancPluginReceivedInstanceCallbackResult_KeepAsIs)
+      {
+        if (modifiedDicomSize64 > 0 || *modifiedDicomBufferData != NULL)
+        {
+          free(modifiedDicomBufferData);
+          throw OrthancException(ErrorCode_Plugin, "The ReceivedInstanceCallback plugin is returning a modified buffer while it has not modified the instance");
+        }
+        return true; 
+      }
+      else if (callbackResult == OrthancPluginReceivedInstanceCallbackResult_Modified)
+      {
+        if (modifiedDicomSize64 > 0 && modifiedDicomBufferData != NULL)
+        {
+          if (static_cast<size_t>(modifiedDicomSize64) != modifiedDicomSize64)  // Orthanc is running in 32bits and has received a > 4GB buffer
+          {
+            free(modifiedDicomBufferData);
+            throw OrthancException(ErrorCode_Plugin, "The Plugin has returned a > 4GB which is too large for Orthanc running in 32bits");
+          }
+
+          modifiedDicomBufferSize = static_cast<size_t>(modifiedDicomSize64);
+
+          CLOG(INFO, PLUGINS) << "A plugin has modified the instance in its ReceivedInstanceCallback";        
+          return true;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_Plugin, "The ReceivedInstanceCallback plugin is not returning a modified buffer while it has modified the instance");
+        }
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_Plugin, "The ReceivedInstanceCallback has returned an invalid value");
+      }
+    }
+
+    return STATUS_Success;
+  }
+
   void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType,
                                             OrthancPluginResourceType resourceType,
                                             const char* resource)
@@ -2521,6 +2591,14 @@
     pimpl_->incomingCStoreInstanceFilters_.push_back(p.callback);
   }
 
+  void OrthancPlugins::RegisterReceivedInstanceCallback(const void* parameters)
+  {
+    const _OrthancPluginReceivedInstanceCallback& p = 
+      *reinterpret_cast<const _OrthancPluginReceivedInstanceCallback*>(parameters);
+
+    CLOG(INFO, PLUGINS) << "Plugin has registered a received instance callback";
+    pimpl_->receivedInstanceCallbacks_.push_back(p.callback);
+  }
 
   void OrthancPlugins::RegisterRefreshMetricsCallback(const void* parameters)
   {
@@ -5004,6 +5082,10 @@
         RegisterIncomingCStoreInstanceFilter(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterReceivedInstanceCallback:
+        RegisterReceivedInstanceCallback(parameters);
+        return true;
+
       case _OrthancPluginService_RegisterRefreshMetricsCallback:
         RegisterRefreshMetricsCallback(parameters);
         return true;
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Tue Dec 14 22:52:59 2021 +0100
@@ -136,6 +136,8 @@
 
     void RegisterIncomingCStoreInstanceFilter(const void* parameters);
 
+    void RegisterReceivedInstanceCallback(const void* parameters);
+
     void RegisterRefreshMetricsCallback(const void* parameters);
 
     void RegisterStorageCommitmentScpCallback(const void* parameters);
@@ -285,6 +287,11 @@
     virtual uint16_t FilterIncomingCStoreInstance(const DicomInstanceToStore& instance,
                                                   const Json::Value& simplified) ORTHANC_OVERRIDE;
 
+    virtual bool ApplyReceivedInstanceCallbacks(const void* receivedDicomBuffer,
+                                                size_t receivedDicomBufferSize,
+                                                void** modifiedDicomBufferData,
+                                                size_t& modifiedDicomBufferSize);
+
     bool HasStorageArea() const;
 
     IStorageArea* CreateStorageArea();  // To be freed after use
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Dec 14 22:52:59 2021 +0100
@@ -463,6 +463,7 @@
     _OrthancPluginService_RegisterTranscoderCallback = 1015,   /* New in Orthanc 1.7.0 */
     _OrthancPluginService_RegisterStorageArea2 = 1016,         /* New in Orthanc 1.9.0 */
     _OrthancPluginService_RegisterIncomingCStoreInstanceFilter = 1017,  /* New in Orthanc 1.9.8 */
+    _OrthancPluginService_RegisterReceivedInstanceCallback = 1018,  /* New in Orthanc 1.9.8 */
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -1001,7 +1002,19 @@
       is already in use */
   } OrthancPluginStorageCommitmentFailureReason;
 
-  
+
+  /**
+   * The return value of ReceivedInstanceCallback
+   **/
+  typedef enum
+  {
+    OrthancPluginReceivedInstanceCallbackResult_KeepAsIs = 1,           /*!< Keep the instance as is */
+    OrthancPluginReceivedInstanceCallbackResult_Modified = 2,           /*!< Modified the instance */
+    OrthancPluginReceivedInstanceCallbackResult_Discard = 3,            /*!< Tell Orthanc to discard the instance */
+
+    _OrthancPluginReceivedInstanceCallbackResult_INTERNAL = 0x7fffffff
+  } OrthancPluginReceivedInstanceCallbackResult;
+
 
   /**
    * @brief A 32-bit memory buffer allocated by the core system of Orthanc.
@@ -7823,6 +7836,73 @@
   }
 
   /**
+   * @brief Callback to possibly modify a DICOM instance received
+   * by Orthanc through any source (C-Store or Rest API)
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a new DICOM instance (through DICOM protocol or 
+   * Rest API), and that answers a possibly modified version of the 
+   * DICOM that should be stored in Orthanc.  
+   *
+   * This callback is called immediately after receiption: before 
+   * transcoding and before filtering (FilterIncomingInstance).
+   *
+   * @param receivedDicomBuffer A buffer containing the received DICOM (input).
+   * @param receivedDicomBufferSize The size of the received DICOM (input)
+   * @param modifiedDicomBuffer A buffer containing the modified DICOM (output).
+   *                            This buffer will be freed by the Orthanc Core and must have
+   *                            been allocated by malloc in your plugin or by Orthanc core through
+   *                            a plugin method.
+   * @param modifiedDicomBufferSize The size of the modified DICOM (output)
+   * @return OrthancPluginReceivedInstanceCallbackResult_KeepAsIs to accept the instance as is
+   *         OrthancPluginReceivedInstanceCallbackResult_Modified to store the modified DICOM
+   *         OrthancPluginReceivedInstanceCallbackResult_Discard to tell Orthanc to discard the instance
+   * @ingroup Callback
+   **/
+  typedef OrthancPluginReceivedInstanceCallbackResult (*OrthancPluginReceivedInstanceCallback) (
+    const void* receivedDicomBuffer,
+    uint64_t receivedDicomBufferSize,
+    void** modifiedDicomBuffer,
+    uint64_t* modifiedDicomBufferSize
+    );
+
+
+  typedef struct
+  {
+    OrthancPluginReceivedInstanceCallback callback;
+  } _OrthancPluginReceivedInstanceCallback;
+
+  /**
+   * @brief Register a callback to possibly modify a DICOM instance received
+   * by Orthanc through any source (C-Store or Rest API)
+   *
+   *
+   * @warning Your callback function will be called synchronously with
+   * the core of Orthanc. This implies that deadlocks might emerge if
+   * you call other core primitives of Orthanc in your callback (such
+   * deadlocks are particular visible in the presence of other plugins
+   * or Lua scripts). It is thus strongly advised to avoid any call to
+   * the REST API of Orthanc in the callback. If you have to call
+   * other primitives of Orthanc, you should make these calls in a
+   * separate thread, passing the pending events to be processed
+   * through a message queue.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterReceivedInstanceCallback(
+    OrthancPluginContext*                     context,
+    OrthancPluginReceivedInstanceCallback     callback)
+  {
+    _OrthancPluginReceivedInstanceCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterReceivedInstanceCallback, &params);
+  }
+
+  /**
    * @brief Get the transfer syntax of a DICOM file.
    *
    * This function returns a pointer to a newly created string that
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/Sanitizer/CMakeLists.txt	Tue Dec 14 22:52:59 2021 +0100
@@ -0,0 +1,59 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2021 Osimis S.A., Belgium
+# Copyright (C) 2021-2021 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+cmake_minimum_required(VERSION 2.8)
+
+project(Sanitizer)
+
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
+SET(ORTHANC_FRAMEWORK_SOURCE path)
+SET(ORTHANC_FRAMEWORK_ROOT ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources)
+
+include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake)
+include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/JsonCppConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/BoostConfiguration.cmake)
+
+include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+set(ENABLE_LOCALE ON)
+set(ENABLE_DCMTK ON)
+include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include_directories(${ORTHANC_FRAMEWORK_ROOT})
+
+
+add_library(Sanitizer SHARED 
+    ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp
+    ${JSONCPP_SOURCES}
+    ${BOOST_SOURCES}
+    Plugin.cpp
+    ${ORTHANC_CORE_SOURCES}
+    ${ORTHANC_DICOM_SOURCES}
+    )
+
+
+install(
+  TARGETS Sanitizer
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/Sanitizer/Plugin.cpp	Tue Dec 14 22:52:59 2021 +0100
@@ -0,0 +1,100 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ * Copyright (C) 2021-2021 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../../../OrthancFramework/Sources/Compatibility.h"
+#include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
+#include "../../../../OrthancFramework/Sources/OrthancFramework.h"
+#include "../Common/OrthancPluginCppWrapper.h"
+
+#include <boost/filesystem.hpp>
+#include <json/value.h> 
+#include <string.h>
+#include <iostream>
+
+
+
+
+OrthancPluginReceivedInstanceCallbackResult ReceivedInstanceCallback(const void* receivedDicomBuffer,
+                                                                     uint64_t receivedDicomBufferSize,
+                                                                     void** modifiedDicomBuffer,
+                                                                     uint64_t* modifiedDicomBufferSize)
+{
+  Orthanc::ParsedDicomFile dicom(receivedDicomBuffer, receivedDicomBufferSize);
+  std::string institutionName = "My institution";
+
+  dicom.Replace(Orthanc::DICOM_TAG_INSTITUTION_NAME, institutionName, false, Orthanc::DicomReplaceMode_InsertIfAbsent, "");
+  
+  std::string modifiedDicom;
+  dicom.SaveToMemoryBuffer(modifiedDicom);
+
+  *modifiedDicomBuffer = malloc(modifiedDicom.size());
+  *modifiedDicomBufferSize = modifiedDicom.size();
+  memcpy(*modifiedDicomBuffer, modifiedDicom.c_str(), modifiedDicom.size());
+  
+  return OrthancPluginReceivedInstanceCallbackResult_Modified;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    OrthancPlugins::SetGlobalContext(c);
+
+    Orthanc::InitializeFramework("", true);
+
+    /* Check the version of the Orthanc core */
+    // if (OrthancPluginCheckVersion(c) == 0)
+    // {
+    //   OrthancPlugins::ReportMinimalOrthancVersion(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+    //                                               ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+    //                                               ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+    //   return -1;
+    // }
+
+    OrthancPlugins::LogWarning("Sanitizer plugin is initializing");
+    OrthancPluginSetDescription(c, "Sample plugin to sanitize incoming DICOM instances.");
+
+    OrthancPluginRegisterReceivedInstanceCallback(c, ReceivedInstanceCallback);
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPlugins::LogWarning("Sanitizer plugin is finalizing");
+    Orthanc::FinalizeFramework();
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "sanitizer";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "0.1";
+  }
+}
--- a/OrthancServer/Sources/ServerContext.cpp	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue Dec 14 22:52:59 2021 +0100
@@ -47,6 +47,7 @@
 #include "../../OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.h"
 #include "../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h"
 #include "../../OrthancFramework/Sources/Logging.h"
+#include "../../OrthancFramework/Sources/MallocMemoryBuffer.h"
 #include "../../OrthancFramework/Sources/MetricsRegistry.h"
 #include "../Plugins/Engine/OrthancPlugins.h"
 
@@ -694,13 +695,47 @@
 
 
   ServerContext::StoreResult ServerContext::Store(std::string& resultPublicId,
-                                                  DicomInstanceToStore& dicom,
+                                                  DicomInstanceToStore& receivedDicom,
                                                   StoreInstanceMode mode)
-  {
+  { 
+    DicomInstanceToStore* dicom = &receivedDicom;
+    std::unique_ptr<DicomInstanceToStore> modifiedDicom;
+
+    void* modifiedDicomBuffer = NULL;
+    size_t modifiedDicomBufferSize = 0;
+
+    std::unique_ptr<MallocMemoryBuffer> raii(new MallocMemoryBuffer);
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins())
+    {
+
+      bool store = GetPlugins().ApplyReceivedInstanceCallbacks(receivedDicom.GetBufferData(), 
+                                                               receivedDicom.GetBufferSize(),
+                                                               &modifiedDicomBuffer,
+                                                               modifiedDicomBufferSize);
+      raii->Assign(modifiedDicomBuffer, modifiedDicomBufferSize, ::free);
+
+      if (!store)
+      {
+        StoreResult result;
+        result.SetStatus(StoreStatus_FilteredOut);
+        return result;
+      }
+
+      if (modifiedDicomBufferSize > 0 && modifiedDicomBuffer != NULL)
+      {
+        modifiedDicom.reset(DicomInstanceToStore::CreateFromBuffer(modifiedDicomBuffer, modifiedDicomBufferSize));
+        modifiedDicom->SetOrigin(dicom->GetOrigin());
+        dicom = modifiedDicom.get();
+      }
+    }
+#endif
+
     if (!isIngestTranscoding_)
     {
       // No automated transcoding. This was the only path in Orthanc <= 1.6.1.
-      return StoreAfterTranscoding(resultPublicId, dicom, mode);
+      return StoreAfterTranscoding(resultPublicId, *dicom, mode);
     }
     else
     {
@@ -709,7 +744,7 @@
       bool transcode = false;
 
       DicomTransferSyntax sourceSyntax;
-      if (!dicom.LookupTransferSyntax(sourceSyntax) ||
+      if (!dicom->LookupTransferSyntax(sourceSyntax) ||
           sourceSyntax == ingestTransferSyntax_)
       {
         // Don't transcode if the incoming DICOM is already in the proper transfer syntax
@@ -736,7 +771,7 @@
       if (!transcode)
       {
         // No transcoding
-        return StoreAfterTranscoding(resultPublicId, dicom, mode);
+        return StoreAfterTranscoding(resultPublicId, *dicom, mode);
       }
       else
       {
@@ -745,7 +780,7 @@
         syntaxes.insert(ingestTransferSyntax_);
         
         IDicomTranscoder::DicomImage source;
-        source.SetExternalBuffer(dicom.GetBufferData(), dicom.GetBufferSize());
+        source.SetExternalBuffer(dicom->GetBufferData(), dicom->GetBufferSize());
         
         IDicomTranscoder::DicomImage transcoded;
         if (Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */))
@@ -753,7 +788,7 @@
           std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile());
 
           std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*tmp));
-          toStore->SetOrigin(dicom.GetOrigin());
+          toStore->SetOrigin(dicom->GetOrigin());
 
           StoreResult result = StoreAfterTranscoding(resultPublicId, *toStore, mode);
           assert(resultPublicId == tmp->GetHasher().HashInstance());
@@ -763,7 +798,7 @@
         else
         {
           // Cannot transcode => store the original file
-          return StoreAfterTranscoding(resultPublicId, dicom, mode);
+          return StoreAfterTranscoding(resultPublicId, *dicom, mode);
         }
       }
     }
--- a/OrthancServer/Sources/ServerEnumerations.h	Sat Dec 11 16:00:38 2021 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.h	Tue Dec 14 22:52:59 2021 +0100
@@ -61,7 +61,7 @@
     StoreStatus_Success,
     StoreStatus_AlreadyStored,
     StoreStatus_Failure,
-    StoreStatus_FilteredOut     // Removed by NewInstanceFilter
+    StoreStatus_FilteredOut     // Removed by NewInstanceFilter or ReceivedInstanceCallback
   };
 
   enum DicomTagType