changeset 6195:2944662388fd attach-custom-data

added AdoptDicomInstance sample plugin
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 13 Jun 2025 13:03:02 +0200
parents a9272b0cc439
children a5208163307b
files OrthancServer/Plugins/Samples/AdoptDicomInstance/CMakeLists.txt OrthancServer/Plugins/Samples/AdoptDicomInstance/Plugin.cpp OrthancServer/Plugins/Samples/AdoptDicomInstance/README
diffstat 3 files changed, 367 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/AdoptDicomInstance/CMakeLists.txt	Fri Jun 13 13:03:02 2025 +0200
@@ -0,0 +1,88 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2023 Osimis S.A., Belgium
+# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2025 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...4.0)
+cmake_policy(SET CMP0058 NEW)
+
+project(AdoptDicomInstance)
+
+SET(PLUGIN_NAME "sample-adopt" CACHE STRING "Name of the plugin")
+SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin")
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake)
+
+set(ENABLE_SQLITE OFF)
+set(ENABLE_MODULE_IMAGES OFF)
+set(ENABLE_MODULE_JOBS OFF)
+set(ENABLE_MODULE_DICOM OFF)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+
+include(${CMAKE_CURRENT_LIST_DIR}/../Common/OrthancPluginsExports.cmake)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND
+    ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/WindowsResources.py
+    ${PLUGIN_VERSION} AdoptDicomInstance AdoptDicomInstance.dll "Sample Orthanc plugin illustrating how to adopt DICOM instances"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/AdoptDicomInstance.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND ADDITIONAL_RESOURCES ${AUTOGENERATED_DIR}/AdoptDicomInstance.rc)
+endif()
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DPLUGIN_NAME="${PLUGIN_NAME}"
+  -DPLUGIN_VERSION="${PLUGIN_VERSION}"
+  -DORTHANC_ENABLE_LOGGING=1
+  -DORTHANC_ENABLE_PLUGINS=1
+  )
+
+include_directories(
+  ${CMAKE_SOURCE_DIR}/../../Include/
+  ${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Sources/
+  )
+
+add_library(AdoptDicomInstance SHARED
+  ${ADDITIONAL_RESOURCES}
+  ${ORTHANC_CORE_SOURCES}
+  ${CMAKE_SOURCE_DIR}/Plugin.cpp
+  ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp
+  )
+
+DefineSourceBasenameForTarget(AdoptDicomInstance)
+
+set_target_properties(
+  AdoptDicomInstance PROPERTIES
+  VERSION ${PLUGIN_VERSION}
+  SOVERSION ${PLUGIN_VERSION}
+  )
+
+install(
+  TARGETS AdoptDicomInstance
+  DESTINATION .
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/AdoptDicomInstance/Plugin.cpp	Fri Jun 13 13:03:02 2025 +0200
@@ -0,0 +1,266 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 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/Logging.h"
+#include "../../../../OrthancFramework/Sources/SystemToolbox.h"
+#include "../../../../OrthancFramework/Sources/Toolbox.h"
+#include "../Common/OrthancPluginCppWrapper.h"
+
+#include <boost/filesystem.hpp>
+
+
+static boost::filesystem::path storageDirectory_;
+
+
+static std::string GetStorageDirectoryPath(const char* uuid)
+{
+  if (uuid == NULL)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+  }
+  else
+  {
+    return (storageDirectory_ / std::string(uuid)).string();
+  }
+}
+
+
+#define CATCH_EXCEPTIONS(errorValue)                    \
+  catch (Orthanc::OrthancException& e)                  \
+  {                                                     \
+    LOG(ERROR) << "Orthanc exception: " << e.What();    \
+    return errorValue;                                  \
+  }                                                     \
+  catch (std::runtime_error& e)                         \
+  {                                                     \
+    LOG(ERROR) << "Native exception: " << e.what();     \
+    return errorValue;                                  \
+  }                                                     \
+  catch (...)                                           \
+  {                                                     \
+    return errorValue;                                  \
+  }
+
+
+OrthancPluginErrorCode StorageCreate(OrthancPluginMemoryBuffer* customData,
+                                     const char* uuid,
+                                     const void* content,
+                                     uint64_t size,
+                                     OrthancPluginContentType type,
+                                     OrthancPluginCompressionType compressionType,
+                                     const OrthancPluginDicomInstance* dicomInstance)
+{
+  try
+  {
+    Json::Value info;
+    info["IsAdopted"] = false;
+
+    OrthancPlugins::MemoryBuffer buffer;
+    buffer.Assign(info.toStyledString());
+    *customData = buffer.Release();
+
+    const std::string path = GetStorageDirectoryPath(uuid);
+    LOG(WARNING) << "Creating non-adopted file: " << path;
+    Orthanc::SystemToolbox::WriteFile(content, size, path);
+
+    return OrthancPluginErrorCode_Success;
+  }
+  CATCH_EXCEPTIONS(OrthancPluginErrorCode_Plugin);
+}
+
+
+OrthancPluginErrorCode StorageReadRange(OrthancPluginMemoryBuffer64* target,
+                                        const char* uuid,
+                                        OrthancPluginContentType type,
+                                        uint64_t rangeStart,
+                                        const void* customData,
+                                        uint32_t customDataSize)
+{
+  try
+  {
+    Json::Value info;
+    if (!Orthanc::Toolbox::ReadJson(info, customData, customDataSize))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    std::string path;
+
+    if (info["IsAdopted"].asBool())
+    {
+      path = info["AdoptedPath"].asString();
+      LOG(WARNING) << "Reading adopted file from: " << path;
+    }
+    else
+    {
+      path = GetStorageDirectoryPath(uuid);
+      LOG(WARNING) << "Reading non-adopted file from: " << path;
+    }
+
+    std::string range;
+    Orthanc::SystemToolbox::ReadFileRange(range, path, rangeStart, rangeStart + target->size, true);
+
+    assert(range.size() == target->size);
+
+    if (target->size != 0)
+    {
+      memcpy(target->data, range.c_str(), target->size);
+    }
+
+    return OrthancPluginErrorCode_Success;
+  }
+  CATCH_EXCEPTIONS(OrthancPluginErrorCode_Plugin);
+}
+
+
+OrthancPluginErrorCode StorageRemove(const char* uuid,
+                                     OrthancPluginContentType type,
+                                     const void* customData,
+                                     uint32_t customDataSize)
+{
+  try
+  {
+    Json::Value info;
+    if (!Orthanc::Toolbox::ReadJson(info, customData, customDataSize))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    // Only remove non-adopted files (i.e., whose for which Orthanc has the ownership)
+    if (info["IsAdopted"].asBool())
+    {
+      LOG(WARNING) << "Don't removing adopted file: " << info["AdoptedPath"].asString();
+    }
+    else
+    {
+      const std::string path = GetStorageDirectoryPath(uuid);
+      LOG(WARNING) << "Removing non-adopted file from: " << path;
+      Orthanc::SystemToolbox::RemoveFile(path);
+    }
+
+    return OrthancPluginErrorCode_Success;
+  }
+  CATCH_EXCEPTIONS(OrthancPluginErrorCode_Plugin);
+}
+
+
+OrthancPluginErrorCode Adopt(OrthancPluginRestOutput* output,
+                             const char* url,
+                             const OrthancPluginHttpRequest* request)
+{
+  try
+  {
+    if (request->method != OrthancPluginHttpMethod_Post)
+    {
+      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "POST");
+      return OrthancPluginErrorCode_Success;
+    }
+    else
+    {
+      const std::string path(reinterpret_cast<const char*>(request->body), request->bodySize);
+      LOG(WARNING) << "Adopting DICOM instance from path: " << path;
+
+      std::string dicom;
+      Orthanc::SystemToolbox::ReadFile(dicom, path);
+
+      Json::Value info;
+      info["IsAdopted"] = true;
+      info["AdoptedPath"] = path;
+
+      const std::string customData = info.toStyledString();
+
+      OrthancPluginStoreStatus status;
+      OrthancPlugins::MemoryBuffer instanceId, attachmentUuid;
+
+      OrthancPluginErrorCode code = OrthancPluginAdoptDicomInstance(
+        OrthancPlugins::GetGlobalContext(), *instanceId, *attachmentUuid, &status,
+        dicom.empty() ? NULL : dicom.c_str(), dicom.size(),
+        customData.empty() ? NULL : customData.c_str(), customData.size());
+
+      if (code == OrthancPluginErrorCode_Success)
+      {
+        const std::string answer = "OK\n";
+        OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, answer.c_str(), answer.size(), "text/plain");
+        return OrthancPluginErrorCode_Success;
+      }
+      else
+      {
+        return code;
+      }
+    }
+  }
+  CATCH_EXCEPTIONS(OrthancPluginErrorCode_Plugin);
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    OrthancPlugins::SetGlobalContext(context, PLUGIN_NAME);
+    Orthanc::Logging::InitializePluginContext(context, PLUGIN_NAME);
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context, info);
+      return -1;
+    }
+
+    OrthancPluginSetDescription2(context, PLUGIN_NAME, "Sample plugin illustrating the adoption of DICOM instances.");
+    OrthancPluginRegisterStorageArea3(context, StorageCreate, StorageReadRange, StorageRemove);
+
+    try
+    {
+      OrthancPlugins::OrthancConfiguration config;
+      storageDirectory_ = config.GetStringValue("StorageDirectory", "OrthancStorage");
+
+      Orthanc::SystemToolbox::MakeDirectory(storageDirectory_.string());
+
+      OrthancPluginRegisterRestCallback(context, "/adopt", Adopt);
+    }
+    CATCH_EXCEPTIONS(-1)
+
+    return 0;
+  }
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return PLUGIN_NAME;
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/AdoptDicomInstance/README	Fri Jun 13 13:03:02 2025 +0200
@@ -0,0 +1,13 @@
+This sample plugin illustrates how to use the
+"OrthancPluginAdoptDicomInstance()" primitive in the Orthanc SDK.
+
+The plugin replaces the built-in storage area of Orthanc, by a flat
+directory "./OrthancStorage" containing the attachments.
+
+DICOM instances can then be adopted by typing:
+
+$ curl http://localhost:8042/adopt -d /tmp/sample.dcm
+
+An adopted DICOM instance is not copied inside the "OrthancStorage"
+folder, but is read from its original location (in the example above,
+from "/tmp/sample.dcm").