# HG changeset patch # User Sebastien Jodogne # Date 1589550495 -7200 # Node ID 09262122934ca43d535f47e19887a662c8ffce85 # Parent 0b59e2706366a94f95c1564b3e7973114aa72317 Moved the GDCM sample plugin out of the Orthanc repository as a separate plugin diff -r 0b59e2706366 -r 09262122934c NEWS --- a/NEWS Fri May 15 15:15:58 2020 +0200 +++ b/NEWS Fri May 15 15:48:15 2020 +0200 @@ -48,6 +48,7 @@ Maintenance ----------- +* Moved the GDCM sample plugin out of the Orthanc repository as a separate plugin * Fix missing body in "OrthancPluginHttpPost()" and "OrthancPluginHttpPut()" * Fix issue #169 (TransferSyntaxUID change from Explicit to Implicit during C-STORE SCU) * Upgraded dependencies for static builds (notably on Windows and LSB): diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/CMakeLists.txt --- a/Plugins/Samples/GdcmDecoder/CMakeLists.txt Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -# Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., Belgium -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU Affero 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 -# Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - -cmake_minimum_required(VERSION 2.8) - -project(GdcmDecoder) - -SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin") - - -# Parameters of the build -set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -set(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") -set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") - -# Advanced parameters to fine-tune linking against system libraries -set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)") -set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") - -# Setup the Orthanc framework -set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) - -set(ORTHANC_FRAMEWORK_PLUGIN ON) -include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake) - -set(ENABLE_LOCALE OFF CACHE INTERNAL "") # Disable support for locales (notably in Boost) -set(ENABLE_MODULE_IMAGES OFF CACHE INTERNAL "") -set(ENABLE_MODULE_JOBS OFF CACHE INTERNAL "") - -include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake) - -include(GdcmConfiguration.cmake) - - -# Check that the Orthanc SDK headers are available -if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) - #include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-0.9.5) - include_directories(${CMAKE_SOURCE_DIR}/../../Include) # TODO => SYNC 0.9.5 -else () - CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) - if (NOT HAVE_ORTHANC_H) - message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") - endif() -endif() - - -include_directories(${ORTHANC_ROOT}) - -add_definitions( - -DPLUGIN_VERSION="${PLUGIN_VERSION}" - -DHAS_ORTHANC_EXCEPTION=1 - -DORTHANC_ENABLE_LOGGING_PLUGIN=1 - ) - -add_library(GdcmDecoder SHARED - GdcmDecoderCache.cpp - GdcmImageDecoder.cpp - Plugin.cpp - ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp - ${ORTHANC_CORE_SOURCES} - ) - -target_link_libraries(GdcmDecoder ${GDCM_LIBRARIES}) - -if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM) - add_dependencies(GdcmDecoder GDCM) -endif() diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/GdcmConfiguration.cmake --- a/Plugins/Samples/GdcmDecoder/GdcmConfiguration.cmake Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -# Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., Belgium -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU Affero 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 -# Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - -if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") - # If using gcc, build GDCM with the "-fPIC" argument to allow its - # embedding into the shared library containing the Orthanc plugin - set(AdditionalCFlags "-fPIC") - set(AdditionalCxxFlags ${AdditionalCFlags}) - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND - CMAKE_COMPILER_IS_GNUCXX) - # Prevents error: "jump to label ‘err’ crosses initialization" of some variable - # within "Source/Common/gdcmCAPICryptographicMessageSyntax.cxx" if using MinGW - set(AdditionalCxxFlags "-fpermissive") - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") - # This definition is necessary to compile - # "Source/MediaStorageAndFileFormat/gdcmFileStreamer.cxx" - set(AdditionalCFlags "-Doff64_t=off_t") - set(AdditionalCxxFlags ${AdditionalCFlags}) - endif() - - set(Flags - "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} ${AdditionalCFlags}" - "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${AdditionalCxxFlags}" - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL} - -DCMAKE_CXX_FLAGS_MINSIZEREL=${CMAKE_CXX_FLAGS_MINSIZEREL} - -DCMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO} - -DCMAKE_CXX_FLAGS_RELWITHDEBINFO=${CMAKE_CXX_FLAGS_RELWITHDEBINFO} - ) - - if (CMAKE_TOOLCHAIN_FILE) - # Take absolute path to the toolchain - get_filename_component(TMP ${CMAKE_TOOLCHAIN_FILE} REALPATH BASE ${CMAKE_SOURCE_DIR}) - list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${TMP}) - endif() - - # Don't build manpages (since gdcm 2.8.4) - list(APPEND Flags -DGDCM_BUILD_DOCBOOK_MANPAGES=OFF) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - # Trick to disable the compilation of socket++ by gdcm, which is - # incompatible with LSB, but fortunately only required for DICOM - # Networking - list(APPEND Flags -DGDCM_USE_SYSTEM_SOCKETXX=ON) - - # Detect the number of CPU cores to run "make" with as much - # parallelism as possible - include(ProcessorCount) - ProcessorCount(N) - if (NOT N EQUAL 0) - set(MAKE_PARALLEL -j${N}) - endif() - - # For Linux Standard Base, avoid building incompatible target gdcmMEXD (*) - set(BUILD_COMMAND BUILD_COMMAND - ${CMAKE_MAKE_PROGRAM} ${MAKE_PARALLEL} - gdcmMSFF gdcmcharls gdcmDICT gdcmDSED gdcmIOD gdcmjpeg8 - gdcmjpeg12 gdcmjpeg16 gdcmopenjp2 gdcmzlib gdcmCommon gdcmexpat) - endif() - - include(ExternalProject) - externalproject_add(GDCM - URL "http://orthanc.osimis.io/ThirdPartyDownloads/gdcm-3.0.4.tar.gz" - URL_MD5 "f12dbded708356d5fa0b5ed37ccdb66e" - TIMEOUT 60 - CMAKE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ${Flags} - ${BUILD_COMMAND} # Customize "make", only for Linux Standard Base (*) - INSTALL_COMMAND "" # Skip the install step - ) - - if(MSVC) - set(Suffix ".lib") - set(Prefix "") - else() - set(Suffix ".a") - list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix) - endif() - - set(GDCM_LIBRARIES - # WARNING: The order of the libraries below *is* important! - ${Prefix}gdcmMSFF${Suffix} - ${Prefix}gdcmcharls${Suffix} - ${Prefix}gdcmDICT${Suffix} - ${Prefix}gdcmDSED${Suffix} - ${Prefix}gdcmIOD${Suffix} - ${Prefix}gdcmjpeg8${Suffix} - ${Prefix}gdcmjpeg12${Suffix} - ${Prefix}gdcmjpeg16${Suffix} - ${Prefix}gdcmopenjp2${Suffix} - ${Prefix}gdcmzlib${Suffix} - ${Prefix}gdcmCommon${Suffix} - ${Prefix}gdcmexpat${Suffix} - - #${Prefix}socketxx${Suffix} - #${Prefix}gdcmMEXD${Suffix} # DICOM Networking, unneeded by Orthanc plugins - #${Prefix}gdcmgetopt${Suffix} - #${Prefix}gdcmuuid${Suffix} - ) - - ExternalProject_Get_Property(GDCM binary_dir) - include_directories(${binary_dir}/Source/Common) - link_directories(${binary_dir}/bin) - - ExternalProject_Get_Property(GDCM source_dir) - include_directories( - ${source_dir}/Source/Common - ${source_dir}/Source/MediaStorageAndFileFormat - ${source_dir}/Source/DataStructureAndEncodingDefinition - ) - -else() - find_package(GDCM REQUIRED) - if (GDCM_FOUND) - include(${GDCM_USE_FILE}) - set(GDCM_LIBRARIES gdcmCommon gdcmMSFF) - else(GDCM_FOUND) - message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?") - endif(GDCM_FOUND) -endif() diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp --- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "GdcmDecoderCache.h" - -#include "../../../Core/Compatibility.h" - -namespace OrthancPlugins -{ - std::string GdcmDecoderCache::ComputeMd5(const void* dicom, - size_t size) - { - std::string result; - - char* md5 = OrthancPluginComputeMd5(OrthancPlugins::GetGlobalContext(), dicom, size); - - if (md5 == NULL) - { - throw std::runtime_error("Cannot compute MD5 hash"); - } - - bool ok = false; - try - { - result.assign(md5); - ok = true; - } - catch (...) - { - } - - OrthancPluginFreeString(OrthancPlugins::GetGlobalContext(), md5); - - if (!ok) - { - throw std::runtime_error("Not enough memory"); - } - else - { - return result; - } - } - - - OrthancImage* GdcmDecoderCache::Decode(const void* dicom, - const uint32_t size, - uint32_t frameIndex) - { - std::string md5 = ComputeMd5(dicom, size); - - // First check whether the previously decoded image is the same - // as this one - { - boost::mutex::scoped_lock lock(mutex_); - - if (decoder_.get() != NULL && - size_ == size && - md5_ == md5) - { - // This is the same image: Reuse the previous decoding - return new OrthancImage(decoder_->Decode(frameIndex)); - } - } - - // This is not the same image - std::unique_ptr decoder(new GdcmImageDecoder(dicom, size)); - std::unique_ptr image(new OrthancImage(decoder->Decode(frameIndex))); - - { - // Cache the newly created decoder for further use - boost::mutex::scoped_lock lock(mutex_); - decoder_.reset(decoder.release()); - size_ = size; - md5_ = md5; - } - - return image.release(); - } -} diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h --- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include "../../../Core/Compatibility.h" -#include "GdcmImageDecoder.h" -#include "../Common/OrthancPluginCppWrapper.h" - -#include - - -namespace OrthancPlugins -{ - class GdcmDecoderCache : public boost::noncopyable - { - private: - boost::mutex mutex_; - std::unique_ptr decoder_; - size_t size_; - std::string md5_; - - static std::string ComputeMd5(const void* dicom, - size_t size); - - public: - GdcmDecoderCache() : size_(0) - { - } - - OrthancImage* Decode(const void* dicom, - const uint32_t size, - uint32_t frameIndex); - }; -} diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp --- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,408 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "GdcmImageDecoder.h" - -#include "../../../Core/Compatibility.h" - -#include -#include -#include -#include -#include -#include -#include - - -namespace OrthancPlugins -{ - struct GdcmImageDecoder::PImpl - { - const void* dicom_; - size_t size_; - - gdcm::ImageReader reader_; - std::unique_ptr lut_; - std::unique_ptr photometric_; - std::unique_ptr interleaved_; - std::string decoded_; - - PImpl(const void* dicom, - size_t size) : - dicom_(dicom), - size_(size) - { - } - - - const gdcm::DataSet& GetDataSet() const - { - return reader_.GetFile().GetDataSet(); - } - - - const gdcm::Image& GetImage() const - { - if (interleaved_.get() != NULL) - { - return interleaved_->GetOutput(); - } - - if (lut_.get() != NULL) - { - return lut_->GetOutput(); - } - - if (photometric_.get() != NULL) - { - return photometric_->GetOutput(); - } - - return reader_.GetImage(); - } - - - void Decode() - { - // Change photometric interpretation or apply LUT, if required - { - const gdcm::Image& image = GetImage(); - if (image.GetPixelFormat().GetSamplesPerPixel() == 1 && - image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::PALETTE_COLOR) - { - lut_.reset(new gdcm::ImageApplyLookupTable()); - lut_->SetInput(image); - if (!lut_->Apply()) - { - throw std::runtime_error( "GDCM cannot apply the lookup table"); - } - } - else if (image.GetPixelFormat().GetSamplesPerPixel() == 1) - { - if (image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME1 && - image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2) - { - photometric_.reset(new gdcm::ImageChangePhotometricInterpretation()); - photometric_->SetInput(image); - photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2); - if (!photometric_->Change() || - GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2) - { - throw std::runtime_error("GDCM cannot change the photometric interpretation"); - } - } - } - else - { - if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && - image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB && - image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::YBR_FULL && - (image.GetTransferSyntax() != gdcm::TransferSyntax::JPEG2000Lossless || - image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::YBR_RCT)) - { - photometric_.reset(new gdcm::ImageChangePhotometricInterpretation()); - photometric_->SetInput(image); - photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::RGB); - if (!photometric_->Change() || - GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB) - { - throw std::runtime_error("GDCM cannot change the photometric interpretation"); - } - } - } - } - - // Possibly convert planar configuration to interleaved - { - const gdcm::Image& image = GetImage(); - if (image.GetPlanarConfiguration() != 0 && - image.GetPixelFormat().GetSamplesPerPixel() != 1) - { - interleaved_.reset(new gdcm::ImageChangePlanarConfiguration()); - interleaved_->SetInput(image); - if (!interleaved_->Change() || - GetImage().GetPlanarConfiguration() != 0) - { - throw std::runtime_error("GDCM cannot change the planar configuration to interleaved"); - } - } - } - } - }; - - GdcmImageDecoder::GdcmImageDecoder(const void* dicom, - size_t size) : - pimpl_(new PImpl(dicom, size)) - { - // Setup a stream to the memory buffer - using namespace boost::iostreams; - basic_array_source source(reinterpret_cast(dicom), size); - stream > stream(source); - - // Parse the DICOM instance using GDCM - pimpl_->reader_.SetStream(stream); - if (!pimpl_->reader_.Read()) - { - throw std::runtime_error("Bad file format"); - } - - pimpl_->Decode(); - } - - - OrthancPluginPixelFormat GdcmImageDecoder::GetFormat() const - { - const gdcm::Image& image = pimpl_->GetImage(); - - if (image.GetPixelFormat().GetSamplesPerPixel() == 1 && - (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME1 || - image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME2)) - { - switch (image.GetPixelFormat()) - { - case gdcm::PixelFormat::UINT16: - return OrthancPluginPixelFormat_Grayscale16; - - case gdcm::PixelFormat::INT16: - return OrthancPluginPixelFormat_SignedGrayscale16; - - case gdcm::PixelFormat::UINT8: - return OrthancPluginPixelFormat_Grayscale8; - - default: - throw std::runtime_error("Unsupported pixel format"); - } - } - else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && - (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::RGB || - image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::YBR_FULL || - image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::YBR_RCT)) - { - switch (image.GetPixelFormat()) - { - case gdcm::PixelFormat::UINT8: - return OrthancPluginPixelFormat_RGB24; - - case gdcm::PixelFormat::UINT16: -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 1) - return OrthancPluginPixelFormat_RGB48; -#else - throw std::runtime_error("RGB48 pixel format is only supported if compiled against Orthanc SDK >= 1.3.1"); -#endif - - default: - break; - } - } - - throw std::runtime_error("Unsupported pixel format"); - } - - - unsigned int GdcmImageDecoder::GetWidth() const - { - return pimpl_->GetImage().GetColumns(); - } - - - unsigned int GdcmImageDecoder::GetHeight() const - { - return pimpl_->GetImage().GetRows(); - } - - - unsigned int GdcmImageDecoder::GetFramesCount() const - { - return pimpl_->GetImage().GetDimension(2); - } - - - size_t GdcmImageDecoder::GetBytesPerPixel(OrthancPluginPixelFormat format) - { - switch (format) - { - case OrthancPluginPixelFormat_Grayscale8: - return 1; - - case OrthancPluginPixelFormat_Grayscale16: - case OrthancPluginPixelFormat_SignedGrayscale16: - return 2; - - case OrthancPluginPixelFormat_RGB24: - return 3; - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 1) - case OrthancPluginPixelFormat_RGB48: - return 6; -#endif - - default: - throw std::runtime_error("Unsupport pixel format"); - } - } - - static void ConvertYbrToRgb(uint8_t rgb[3], - const uint8_t ybr[3]) - { - // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 - // https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion - - // TODO - Check out the outcome of Mathieu's discussion about - // truncation of YCbCr-to-RGB conversion: - // https://groups.google.com/forum/#!msg/comp.protocols.dicom/JHuGeyWbTz8/ARoTWrJzAQAJ - - const float Y = ybr[0]; - const float Cb = ybr[1]; - const float Cr = ybr[2]; - - const float result[3] = { - Y + 1.402f * (Cr - 128.0f), - Y - 0.344136f * (Cb - 128.0f) - 0.714136f * (Cr - 128.0f), - Y + 1.772f * (Cb - 128.0f) - }; - - for (uint8_t i = 0; i < 3 ; i++) - { - if (result[i] < 0) - { - rgb[i] = 0; - } - else if (result[i] > 255) - { - rgb[i] = 255; - } - else - { - rgb[i] = static_cast(result[i]); - } - } - } - - - static void FixPhotometricInterpretation(OrthancImage& image, - gdcm::PhotometricInterpretation interpretation) - { - switch (interpretation) - { - case gdcm::PhotometricInterpretation::MONOCHROME1: - case gdcm::PhotometricInterpretation::MONOCHROME2: - case gdcm::PhotometricInterpretation::RGB: - return; - - case gdcm::PhotometricInterpretation::YBR_FULL: - { - // Fix for Osimis issue WVB-319: Some images are not loading in US_MF - - uint32_t width = image.GetWidth(); - uint32_t height = image.GetHeight(); - uint32_t pitch = image.GetPitch(); - uint8_t* buffer = reinterpret_cast(image.GetBuffer()); - - if (image.GetPixelFormat() != OrthancPluginPixelFormat_RGB24 || - pitch < 3 * width) - { - throw std::runtime_error("Internal error"); - } - - for (uint32_t y = 0; y < height; y++) - { - uint8_t* p = buffer + y * pitch; - for (uint32_t x = 0; x < width; x++, p += 3) - { - const uint8_t ybr[3] = { p[0], p[1], p[2] }; - uint8_t rgb[3]; - ConvertYbrToRgb(rgb, ybr); - p[0] = rgb[0]; - p[1] = rgb[1]; - p[2] = rgb[2]; - } - } - - return; - } - - default: - throw std::runtime_error("Unsupported output photometric interpretation"); - } - } - - - OrthancPluginImage* GdcmImageDecoder::Decode(unsigned int frameIndex) const - { - unsigned int frames = GetFramesCount(); - unsigned int width = GetWidth(); - unsigned int height = GetHeight(); - OrthancPluginPixelFormat format = GetFormat(); - size_t bpp = GetBytesPerPixel(format); - - if (frameIndex >= frames) - { - throw std::runtime_error("Inexistent frame index"); - } - - std::string& decoded = pimpl_->decoded_; - OrthancImage target(format, width, height); - - if (width == 0 || - height == 0) - { - return target.Release(); - } - - if (decoded.empty()) - { - decoded.resize(pimpl_->GetImage().GetBufferLength()); - if (!pimpl_->GetImage().GetBuffer(&decoded[0])) - { - throw std::runtime_error("Image not properly decoded to a memory buffer"); - } - } - - const void* sourceBuffer = &decoded[0]; - - if (target.GetPitch() == bpp * width && - frames == 1) - { - assert(decoded.size() == target.GetPitch() * target.GetHeight()); - memcpy(target.GetBuffer(), sourceBuffer, decoded.size()); - } - else - { - size_t targetPitch = target.GetPitch(); - size_t sourcePitch = width * bpp; - - const uint8_t* a = (reinterpret_cast(decoded.c_str()) + - sourcePitch * height * frameIndex); - uint8_t* b = reinterpret_cast(target.GetBuffer()); - - for (uint32_t y = 0; y < height; y++) - { - memcpy(b, a, sourcePitch); - a += sourcePitch; - b += targetPitch; - } - } - - FixPhotometricInterpretation(target, pimpl_->GetImage().GetPhotometricInterpretation()); - - return target.Release(); - } -} diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h --- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include "../Common/OrthancPluginCppWrapper.h" - -#include -#include -#include - - -namespace OrthancPlugins -{ - class GdcmImageDecoder : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr pimpl_; - - public: - GdcmImageDecoder(const void* dicom, - size_t size); - - OrthancPluginPixelFormat GetFormat() const; - - unsigned int GetWidth() const; - - unsigned int GetHeight() const; - - unsigned int GetFramesCount() const; - - static size_t GetBytesPerPixel(OrthancPluginPixelFormat format); - - OrthancPluginImage* Decode(unsigned int frameIndex) const; - }; -} diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/Plugin.cpp --- a/Plugins/Samples/GdcmDecoder/Plugin.cpp Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,443 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "../../../Core/Compatibility.h" -#include "../../../Core/DicomFormat/DicomMap.h" -#include "../../../Core/MultiThreading/Semaphore.h" -#include "../../../Core/Toolbox.h" -#include "GdcmDecoderCache.h" - -#include -#include -#include -#include -#include - - -static OrthancPlugins::GdcmDecoderCache cache_; -static bool restrictTransferSyntaxes_ = false; -static std::set enabledTransferSyntaxes_; -static bool hasThrottling_ = false; -static std::unique_ptr throttlingSemaphore_; - -static bool ExtractTransferSyntax(std::string& transferSyntax, - const void* dicom, - const uint32_t size) -{ - Orthanc::DicomMap header; - if (!Orthanc::DicomMap::ParseDicomMetaInformation(header, reinterpret_cast(dicom), size)) - { - return false; - } - - const Orthanc::DicomValue* tag = header.TestAndGetValue(0x0002, 0x0010); - if (tag == NULL || - tag->IsNull() || - tag->IsBinary()) - { - return false; - } - else - { - // Stripping spaces should not be required, as this is a UI value - // representation whose stripping is supported by the Orthanc - // core, but let's be careful... - transferSyntax = Orthanc::Toolbox::StripSpaces(tag->GetContent()); - return true; - } -} - - -static bool IsTransferSyntaxEnabled(const void* dicom, - const uint32_t size) -{ - std::string formattedSize; - - { - char tmp[16]; - sprintf(tmp, "%0.1fMB", static_cast(size) / (1024.0f * 1024.0f)); - formattedSize.assign(tmp); - } - - if (!restrictTransferSyntaxes_) - { - LOG(INFO) << "Decoding one DICOM instance of " << formattedSize << " using GDCM"; - return true; - } - - std::string transferSyntax; - if (!ExtractTransferSyntax(transferSyntax, dicom, size)) - { - LOG(INFO) << "Cannot extract the transfer syntax of this instance of " - << formattedSize << ", will use GDCM to decode it"; - return true; - } - else if (enabledTransferSyntaxes_.find(transferSyntax) != enabledTransferSyntaxes_.end()) - { - // Decoding for this transfer syntax is enabled - LOG(INFO) << "Using GDCM to decode this instance of " << formattedSize - << " with transfer syntax " << transferSyntax; - return true; - } - else - { - LOG(INFO) << "Won't use GDCM to decode this instance of " << formattedSize - << ", as its transfer syntax " << transferSyntax << " is disabled"; - return false; - } -} - - -static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target, - const void* dicom, - const uint32_t size, - uint32_t frameIndex) -{ - try - { - std::unique_ptr locker; - - if (hasThrottling_) - { - if (throttlingSemaphore_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - locker.reset(new Orthanc::Semaphore::Locker(*throttlingSemaphore_)); - } - } - - if (!IsTransferSyntaxEnabled(dicom, size)) - { - *target = NULL; - return OrthancPluginErrorCode_Success; - } - - std::unique_ptr image; - -#if 0 - // Do not use the cache - OrthancPlugins::GdcmImageDecoder decoder(dicom, size); - image.reset(new OrthancPlugins::OrthancImage(decoder.Decode(frameIndex))); -#else - image.reset(cache_.Decode(dicom, size, frameIndex)); -#endif - - *target = image->Release(); - - return OrthancPluginErrorCode_Success; - } - catch (Orthanc::OrthancException& e) - { - *target = NULL; - - LOG(WARNING) << "Cannot decode image using GDCM: " << e.What(); - return OrthancPluginErrorCode_Plugin; - } - catch (std::runtime_error& e) - { - *target = NULL; - - LOG(WARNING) << "Cannot decode image using GDCM: " << e.what(); - return OrthancPluginErrorCode_Plugin; - } - catch (...) - { - *target = NULL; - - LOG(WARNING) << "Native exception while decoding image using GDCM"; - return OrthancPluginErrorCode_Plugin; - } -} - - -OrthancPluginErrorCode TranscoderCallback( - OrthancPluginMemoryBuffer* transcoded /* out */, - uint8_t* hasSopInstanceUidChanged /* out */, - const void* buffer, - uint64_t size, - const char* const* allowedSyntaxes, - uint32_t countSyntaxes, - uint8_t allowNewSopInstanceUid) -{ - try - { - std::unique_ptr locker; - - if (hasThrottling_) - { - if (throttlingSemaphore_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - locker.reset(new Orthanc::Semaphore::Locker(*throttlingSemaphore_)); - } - } - - std::string dicom(reinterpret_cast(buffer), size); - std::stringstream stream(dicom); - - gdcm::ImageReader reader; - reader.SetStream(stream); - if (!reader.Read()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "GDCM cannot decode the image"); - } - - // First check that transcoding is mandatory - for (uint32_t i = 0; i < countSyntaxes; i++) - { - gdcm::TransferSyntax syntax(gdcm::TransferSyntax::GetTSType(allowedSyntaxes[i])); - if (syntax.IsValid() && - reader.GetImage().GetTransferSyntax() == syntax) - { - // Same transfer syntax as in the source, return a copy of the - // source buffer - OrthancPlugins::MemoryBuffer orthancBuffer(buffer, size); - *transcoded = orthancBuffer.Release(); - *hasSopInstanceUidChanged = false; - return OrthancPluginErrorCode_Success; - } - } - - for (uint32_t i = 0; i < countSyntaxes; i++) - { - gdcm::TransferSyntax syntax(gdcm::TransferSyntax::GetTSType(allowedSyntaxes[i])); - if (syntax.IsValid()) - { - gdcm::ImageChangeTransferSyntax change; - change.SetTransferSyntax(syntax); - change.SetInput(reader.GetImage()); - - if (change.Change()) - { - if (syntax == gdcm::TransferSyntax::JPEGBaselineProcess1 || - syntax == gdcm::TransferSyntax::JPEGExtendedProcess2_4 || - syntax == gdcm::TransferSyntax::JPEGLSNearLossless || - syntax == gdcm::TransferSyntax::JPEG2000 || - syntax == gdcm::TransferSyntax::JPEG2000Part2) - { - // In the case of a lossy compression, generate new SOP instance UID - gdcm::UIDGenerator generator; - std::string uid = generator.Generate(); - if (uid.size() == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "GDCM cannot generate a UID"); - } - - gdcm::Attribute<0x0008,0x0018> sopInstanceUid; - sopInstanceUid.SetValue(uid); - reader.GetFile().GetDataSet().Replace(sopInstanceUid.GetAsDataElement()); - *hasSopInstanceUidChanged = 1; - } - else - { - *hasSopInstanceUidChanged = 0; - } - - // GDCM was able to change the transfer syntax, serialize it - // to the output buffer - gdcm::ImageWriter writer; - writer.SetImage(change.GetOutput()); - writer.SetFile(reader.GetFile()); - - std::stringstream ss; - writer.SetStream(ss); - if (writer.Write()) - { - std::string s = ss.str(); - OrthancPlugins::MemoryBuffer orthancBuffer(s.empty() ? NULL : s.c_str(), s.size()); - *transcoded = orthancBuffer.Release(); - - return OrthancPluginErrorCode_Success; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "GDCM cannot serialize the image"); - } - } - } - } - - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - catch (Orthanc::OrthancException& e) - { - LOG(INFO) << "Cannot transcode image using GDCM: " << e.What(); - return OrthancPluginErrorCode_Plugin; - } - catch (std::runtime_error& e) - { - LOG(INFO) << "Cannot transcode image using GDCM: " << e.what(); - return OrthancPluginErrorCode_Plugin; - } - catch (...) - { - LOG(INFO) << "Native exception while decoding image using GDCM"; - return OrthancPluginErrorCode_Plugin; - } -} - - -/** - * We force the redefinition of the "ORTHANC_PLUGINS_API" macro, that - * was left empty with gcc until Orthanc SDK 1.5.7 (no "default" - * visibility). This causes the version script, if run from "Holy - * Build Box", to make private the 4 global functions of the plugin. - **/ - -#undef ORTHANC_PLUGINS_API - -#ifdef WIN32 -# define ORTHANC_PLUGINS_API __declspec(dllexport) -#elif __GNUC__ >= 4 -# define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default"))) -#else -# define ORTHANC_PLUGINS_API -#endif - - -extern "C" -{ - ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) - { - static const char* const KEY_GDCM = "Gdcm"; - static const char* const KEY_ENABLE_GDCM = "Enable"; - static const char* const KEY_THROTTLING = "Throttling"; - static const char* const KEY_RESTRICT_TRANSFER_SYNTAXES = "RestrictTransferSyntaxes"; - - try - { - OrthancPlugins::SetGlobalContext(context); - Orthanc::Logging::Initialize(context); - LOG(INFO) << "Initializing the decoder/transcoder of medical images using GDCM"; - - /* Check the version of the Orthanc core */ - if (!OrthancPlugins::CheckMinimalOrthancVersion(0, 9, 5)) - { - LOG(ERROR) << "Your version of Orthanc (" << std::string(context->orthancVersion) - << ") must be above 0.9.5 to run this plugin"; - return -1; - } - - OrthancPluginSetDescription(context, "Decoder/transcoder of medical images using GDCM."); - - OrthancPlugins::OrthancConfiguration global; - - bool enabled = true; - hasThrottling_ = false; - - if (global.IsSection(KEY_GDCM)) - { - OrthancPlugins::OrthancConfiguration config; - global.GetSection(config, KEY_GDCM); - - enabled = config.GetBooleanValue(KEY_ENABLE_GDCM, true); - - if (enabled && - config.LookupSetOfStrings(enabledTransferSyntaxes_, KEY_RESTRICT_TRANSFER_SYNTAXES, false)) - { - restrictTransferSyntaxes_ = true; - - for (std::set::const_iterator it = enabledTransferSyntaxes_.begin(); - it != enabledTransferSyntaxes_.end(); ++it) - { - LOG(WARNING) << "Orthanc will use GDCM to decode transfer syntax: " << *it; - } - } - - unsigned int throttling; - if (enabled && - config.LookupUnsignedIntegerValue(throttling, KEY_THROTTLING)) - { - if (throttling == 0) - { - LOG(ERROR) << "Bad value for option \"" << KEY_THROTTLING - << "\": Must be a strictly positive integer"; - return -1; - } - else - { - LOG(WARNING) << "Throttling GDCM to " << throttling << " concurrent thread(s)"; - hasThrottling_ = true; - throttlingSemaphore_.reset(new Orthanc::Semaphore(throttling)); - } - } - } - - if (enabled) - { - if (!hasThrottling_) - { - LOG(WARNING) << "GDCM throttling is disabled"; - } - - OrthancPluginRegisterDecodeImageCallback(context, DecodeImageCallback); - - if (OrthancPlugins::CheckMinimalOrthancVersion(1, 7, 0)) - { - OrthancPluginRegisterTranscoderCallback(context, TranscoderCallback); - } - else - { - LOG(WARNING) << "Your version of Orthanc (" << std::string(context->orthancVersion) - << ") must be above 1.7.0 to benefit from transcoding"; - } - } - else - { - LOG(WARNING) << "The decoder/transcoder of medical images using GDCM is disabled"; - } - - return 0; - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception while initializing the GDCM plugin: " << e.What(); - return -1; - } - } - - - ORTHANC_PLUGINS_API void OrthancPluginFinalize() - { - LOG(INFO) << "Finalizing the decoder/transcoder of medical images using GDCM"; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetName() - { - return "gdcm"; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() - { - return PLUGIN_VERSION; - } -} diff -r 0b59e2706366 -r 09262122934c Plugins/Samples/GdcmDecoder/README --- a/Plugins/Samples/GdcmDecoder/README Fri May 15 15:15:58 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -This sample shows how to replace the decoder of DICOM images that is -built in Orthanc, by the GDCM library. - -A production-ready version of this sample, is available in the -offical Web viewer plugin: -http://www.orthanc-server.com/static.php?page=web-viewer