Mercurial > hg > orthanc
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").
