# HG changeset patch # User Sebastien Jodogne # Date 1404208918 -7200 # Node ID 80d4f1618b3379eef40188c945e6eb077fef0e34 # Parent 5983e59ac670e53d3eb0ecd655660134c732d5ae Sample plugin to replace DCMTK by GDCM when decoding images diff -r 5983e59ac670 -r 80d4f1618b33 Core/ImageFormats/ImageAccessor.cpp --- a/Core/ImageFormats/ImageAccessor.cpp Mon Jun 30 17:44:38 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.cpp Tue Jul 01 12:01:58 2014 +0200 @@ -168,7 +168,7 @@ pitch_ = pitch; buffer_ = const_cast(buffer); - assert(GetBytesPerPixel(format_) * width_ <= pitch_); + assert(GetBytesPerPixel() * width_ <= pitch_); } @@ -185,7 +185,7 @@ pitch_ = pitch; buffer_ = buffer; - assert(GetBytesPerPixel(format_) * width_ <= pitch_); + assert(GetBytesPerPixel() * width_ <= pitch_); } diff -r 5983e59ac670 -r 80d4f1618b33 Core/ImageFormats/ImageAccessor.h --- a/Core/ImageFormats/ImageAccessor.h Mon Jun 30 17:44:38 2014 +0200 +++ b/Core/ImageFormats/ImageAccessor.h Tue Jul 01 12:01:58 2014 +0200 @@ -62,6 +62,11 @@ return format_; } + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + unsigned int GetWidth() const { return width_; diff -r 5983e59ac670 -r 80d4f1618b33 Plugins/Samples/GdcmDecoding/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt Tue Jul 01 12:01:58 2014 +0200 @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 2.8) + +project(GdcmDecoding) + +SET(ALLOW_DOWNLOADS ON CACHE BOOL "Allow CMake to download packages") +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_GOOGLE_LOG OFF CACHE BOOL "Use the system version of Google Log") + +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) + +if (${CMAKE_COMPILER_IS_GNUCXX}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") +endif() + +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake) + +find_package(GDCM REQUIRED) +if (GDCM_FOUND) + include(${GDCM_USE_FILE}) + set(GDCM_LIBRARIES + gdcmCommon + gdcmDICT + gdcmDSED + gdcmIOD + gdcmjpeg12 + gdcmjpeg16 + gdcmjpeg8 + gdcmMEXD + gdcmMSFF + ) +else(GDCM_FOUND) + message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?") +endif(GDCM_FOUND) + + +include_directories( + ${ORTHANC_ROOT}/Plugins/OrthancCPlugin/ + ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw + ) +add_library(GdcmDecoding SHARED + Plugin.cpp + OrthancContext.cpp + + # Sources from Orthanc + ${GOOGLE_LOG_SOURCES} + ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/ImageFormats/ImageAccessor.cpp + ${ORTHANC_ROOT}/Core/ImageFormats/ImageBuffer.cpp + ${ORTHANC_ROOT}/Core/ImageFormats/ImageProcessing.cpp + ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp + ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c + ${THIRD_PARTY_SOURCES} + ) +target_link_libraries(GdcmDecoding ${GDCM_LIBRARIES}) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_link_libraries(GdcmDecoding pthread dl rt) +endif() diff -r 5983e59ac670 -r 80d4f1618b33 Plugins/Samples/GdcmDecoding/OrthancContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoding/OrthancContext.cpp Tue Jul 01 12:01:58 2014 +0200 @@ -0,0 +1,158 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#include "OrthancContext.h" + +#include + + +void OrthancContext::Check() +{ + if (context_ == NULL) + { + throw std::runtime_error("The Orthanc plugin context is not initialized"); + } +} + + +OrthancContext& OrthancContext::GetInstance() +{ + static OrthancContext instance; + return instance; +} + + +OrthancContext::~OrthancContext() +{ + if (context_ != NULL) + { + throw std::runtime_error("The Orthanc plugin was not properly finalized"); + } +} + + +void OrthancContext::ExtractGetArguments(Arguments& arguments, + const OrthancPluginHttpRequest& request) +{ + Check(); + arguments.clear(); + + for (uint32_t i = 0; i < request.getCount; i++) + { + arguments[request.getKeys[i]] = request.getValues[i]; + } +} + + +void OrthancContext::LogError(const std::string& s) +{ + Check(); + OrthancPluginLogError(context_, s.c_str()); +} + + +void OrthancContext::LogWarning(const std::string& s) +{ + Check(); + OrthancPluginLogWarning(context_, s.c_str()); +} + + +void OrthancContext::LogInfo(const std::string& s) +{ + Check(); + OrthancPluginLogInfo(context_, s.c_str()); +} + + +void OrthancContext::Register(const std::string& uri, + OrthancPluginRestCallback callback) +{ + Check(); + OrthancPluginRegisterRestCallback(context_, uri.c_str(), callback); +} + + +void OrthancContext::GetDicomForInstance(std::string& result, + const std::string& instanceId) +{ + Check(); + OrthancPluginMemoryBuffer buffer; + + if (OrthancPluginGetDicomForInstance(context_, &buffer, instanceId.c_str())) + { + throw std::runtime_error("No DICOM instance with Orthanc ID: " + instanceId); + } + + if (buffer.size == 0) + { + result.clear(); + } + else + { + result.assign(reinterpret_cast(buffer.data), buffer.size); + } + + OrthancPluginFreeMemoryBuffer(context_, &buffer); +} + + +void OrthancContext::CompressAndAnswerPngImage(OrthancPluginRestOutput* output, + const Orthanc::ImageAccessor& accessor) +{ + Check(); + + OrthancPluginPixelFormat format; + switch (accessor.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + format = OrthancPluginPixelFormat_Grayscale8; + break; + + case Orthanc::PixelFormat_Grayscale16: + format = OrthancPluginPixelFormat_Grayscale16; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + format = OrthancPluginPixelFormat_SignedGrayscale16; + break; + + case Orthanc::PixelFormat_RGB24: + format = OrthancPluginPixelFormat_RGB24; + break; + + case Orthanc::PixelFormat_RGBA32: + format = OrthancPluginPixelFormat_RGBA32; + break; + + default: + throw std::runtime_error("Unsupported pixel format"); + } + + OrthancPluginCompressAndAnswerPngImage(context_, output, format, accessor.GetWidth(), + accessor.GetHeight(), accessor.GetPitch(), accessor.GetConstBuffer()); +} diff -r 5983e59ac670 -r 80d4f1618b33 Plugins/Samples/GdcmDecoding/OrthancContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoding/OrthancContext.h Tue Jul 01 12:01:58 2014 +0200 @@ -0,0 +1,84 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#pragma once + +#include + +#include "../../../Core/ImageFormats/ImageBuffer.h" + +#include +#include +#include + + +class OrthancContext : public boost::noncopyable +{ +private: + OrthancPluginContext* context_; + + OrthancContext() : context_(NULL) + { + } + + void Check(); + +public: + typedef std::map Arguments; + + static OrthancContext& GetInstance(); + + ~OrthancContext(); + + void Initialize(OrthancPluginContext* context) + { + context_ = context; + } + + void Finalize() + { + context_ = NULL; + } + + void ExtractGetArguments(Arguments& arguments, + const OrthancPluginHttpRequest& request); + + void LogError(const std::string& s); + + void LogWarning(const std::string& s); + + void LogInfo(const std::string& s); + + void Register(const std::string& uri, + OrthancPluginRestCallback callback); + + void GetDicomForInstance(std::string& result, + const std::string& instanceId); + + void CompressAndAnswerPngImage(OrthancPluginRestOutput* output, + const Orthanc::ImageAccessor& accessor); +}; diff -r 5983e59ac670 -r 80d4f1618b33 Plugins/Samples/GdcmDecoding/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoding/Plugin.cpp Tue Jul 01 12:01:58 2014 +0200 @@ -0,0 +1,221 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#include +#include +#include +#include + +#include "OrthancContext.h" +#include "../../../Core/ImageFormats/ImageProcessing.h" + +#include +#include + + +static bool GetOrthancPixelFormat(Orthanc::PixelFormat& format, + const gdcm::Image& image) +{ + if (image.GetPlanarConfiguration() != 0 && + image.GetPixelFormat().GetSamplesPerPixel() != 1) + { + OrthancContext::GetInstance().LogError("Planar configurations are not supported"); + return false; + } + + if (image.GetPixelFormat().GetSamplesPerPixel() == 1) + { + switch (image.GetPixelFormat().GetScalarType()) + { + case gdcm::PixelFormat::UINT8: + format = Orthanc::PixelFormat_Grayscale8; + return true; + + case gdcm::PixelFormat::UINT16: + format = Orthanc::PixelFormat_Grayscale16; + return true; + + case gdcm::PixelFormat::INT16: + format = Orthanc::PixelFormat_SignedGrayscale16; + return true; + + default: + return false; + } + } + else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && + image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8) + { + format = Orthanc::PixelFormat_RGB24; + } + else if (image.GetPixelFormat().GetSamplesPerPixel() == 4 && + image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8) + { + format = Orthanc::PixelFormat_RGBA32; + } + + return false; +} + + +ORTHANC_PLUGINS_API int32_t DecodeImage(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + std::string instance(request->groups[0]); + std::string outputFormat(request->groups[1]); + OrthancContext::GetInstance().LogWarning("Using GDCM to decode instance " + instance); + + // Download the request DICOM instance from Orthanc into a memory buffer + std::string dicom; + OrthancContext::GetInstance().GetDicomForInstance(dicom, instance); + + // Prepare a memory stream over the DICOM instance + std::stringstream stream(dicom); + + // Parse the DICOM instance using GDCM + gdcm::ImageReader imageReader; + imageReader.SetStream(stream); + if (!imageReader.Read()) + { + OrthancContext::GetInstance().LogError("GDCM cannot extract an image from this DICOM instance"); + return -1; // Error + } + + gdcm::Image& image = imageReader.GetImage(); + + // Log information about the decoded image + char tmp[1024]; + sprintf(tmp, "Image format: %dx%d %s with %d color channel(s)", image.GetRows(), image.GetColumns(), + image.GetPixelFormat().GetScalarTypeAsString(), image.GetPixelFormat().GetSamplesPerPixel()); + OrthancContext::GetInstance().LogWarning(tmp); + + // Create a read-only accessor to the bitmap decoded by GDCM + Orthanc::PixelFormat format; + if (!GetOrthancPixelFormat(format, image)) + { + OrthancContext::GetInstance().LogError("This sample plugin does not support this image format"); + return -1; // Error + } + + Orthanc::ImageAccessor decodedImage; + std::vector decodedBuffer(image.GetBufferLength()); + + if (decodedBuffer.size()) + { + image.GetBuffer(&decodedBuffer[0]); + unsigned int pitch = image.GetColumns() * ::Orthanc::GetBytesPerPixel(format); + decodedImage.AssignWritable(format, image.GetColumns(), image.GetRows(), pitch, &decodedBuffer[0]); + } + else + { + // Empty image + decodedImage.AssignWritable(format, 0, 0, 0, NULL); + } + + + // Convert the pixel format from GDCM to the format requested by the REST query + Orthanc::ImageBuffer converted; + converted.SetWidth(decodedImage.GetWidth()); + converted.SetHeight(decodedImage.GetHeight()); + + if (outputFormat == "preview") + { + if (format == Orthanc::PixelFormat_RGB24 || + format == Orthanc::PixelFormat_RGBA32) + { + // Do not rescale color image + converted.SetFormat(Orthanc::PixelFormat_RGB24); + } + else + { + converted.SetFormat(Orthanc::PixelFormat_Grayscale8); + + // Rescale the image to the [0,255] range + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, decodedImage); + + float offset = -a; + float scaling = 255.0f / static_cast(b - a); + Orthanc::ImageProcessing::ShiftScale(decodedImage, offset, scaling); + } + } + else if (outputFormat == "image-uint8") + { + converted.SetFormat(Orthanc::PixelFormat_Grayscale8); + } + else if (outputFormat == "image-uint16") + { + converted.SetFormat(Orthanc::PixelFormat_Grayscale16); + } + else if (outputFormat == "image-int16") + { + converted.SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + OrthancContext::GetInstance().LogError("Unknown output format: " + outputFormat); + return -1; + } + + Orthanc::ImageAccessor convertedAccessor(converted.GetAccessor()); + Orthanc::ImageProcessing::Convert(convertedAccessor, decodedImage); + + + // Compress the converted image as a PNG file + OrthancContext::GetInstance().CompressAndAnswerPngImage(output, convertedAccessor); + + return 0; // Success +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + OrthancContext::GetInstance().Initialize(context); + OrthancContext::GetInstance().LogWarning("Initializing GDCM decoding"); + OrthancContext::GetInstance().Register("/instances/([^/]+)/(preview|image-uint8|image-uint16|image-int16)", DecodeImage); + return 0; + } + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + OrthancContext::GetInstance().LogWarning("Finalizing GDCM decoding"); + OrthancContext::GetInstance().Finalize(); + } + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "gdcm-decoding"; + } + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return "1.0"; + } +} diff -r 5983e59ac670 -r 80d4f1618b33 Resources/CMake/GoogleLogConfiguration.cmake --- a/Resources/CMake/GoogleLogConfiguration.cmake Mon Jun 30 17:44:38 2014 +0200 +++ b/Resources/CMake/GoogleLogConfiguration.cmake Tue Jul 01 12:01:58 2014 +0200 @@ -65,22 +65,22 @@ if (CMAKE_COMPILER_IS_GNUCXX) if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") execute_process( - COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities-lsb.diff + COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities-lsb.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src ) else() execute_process( - COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities.diff + COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src ) endif() execute_process( - COMMAND patch port.h ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-h.diff + COMMAND patch port.h ${ORTHANC_ROOT}/Resources/Patches/glog-port-h.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows ) execute_process( - COMMAND patch port.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-cc.diff + COMMAND patch port.cc ${ORTHANC_ROOT}/Resources/Patches/glog-port-cc.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows ) endif() @@ -91,18 +91,18 @@ if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") # Install the specific configuration for LSB SDK configure_file( - ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationLSB.h + ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationLSB.h ${GOOGLE_LOG_SOURCES_DIR}/src/config.h COPYONLY) elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") # Install the specific configuration for Mac OS configure_file( - ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationDarwin.h + ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationDarwin.h ${GOOGLE_LOG_SOURCES_DIR}/src/config.h COPYONLY) else() configure_file( - ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h + ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.h ${GOOGLE_LOG_SOURCES_DIR}/src/config.h COPYONLY) endif()