# HG changeset patch # User Sebastien Jodogne # Date 1448625212 -3600 # Node ID b1a6f49b21dddb1062ed40b1a61bc42d70248949 # Parent 47d032c4881863fa78d4e58b994292d09e59dfbd GDCM decoder sample diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/CMakeLists.txt Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.8) + +project(GdcmDecoder) + +SET(GDCM_DECODER_VERSION "0.0" CACHE STRING "Version of the plugin") +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_BOOST ON CACHE BOOL "Use the system version of boost") + +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) + +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) + +add_definitions(-DGDCM_DECODER_VERSION="${GDCM_DECODER_VERSION}") + +add_library(GdcmDecoder SHARED + ${BOOST_SOURCES} + GdcmDecoderCache.cpp + GdcmImageDecoder.cpp + OrthancImageWrapper.cpp + Plugin.cpp + ) + +target_link_libraries(GdcmDecoder ${GDCM_LIBRARIES}) diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,98 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "OrthancImageWrapper.h" + +namespace OrthancPlugins +{ + std::string GdcmDecoderCache::ComputeMd5(OrthancPluginContext* context, + const void* dicom, + size_t size) + { + std::string result; + + char* md5 = OrthancPluginComputeMd5(context, dicom, size); + + if (md5 == NULL) + { + throw std::runtime_error("Cannot compute MD5 hash"); + } + + bool ok = false; + try + { + result.assign(md5); + ok = true; + } + catch (...) + { + } + + OrthancPluginFreeString(context, md5); + + if (!ok) + { + throw std::runtime_error("Not enough memory"); + } + else + { + return result; + } + } + + + OrthancPluginImage* GdcmDecoderCache::Decode(OrthancPluginContext* context, + const void* dicom, + const uint32_t size, + uint32_t frameIndex) + { + std::string md5 = ComputeMd5(context, 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 decoder_->Decode(context, frameIndex); + } + } + + // This is not the same image + std::auto_ptr decoder(new GdcmImageDecoder(dicom, size)); + OrthancImageWrapper image(context, decoder->Decode(context, frameIndex)); + + { + // Cache the newly created decoder for further use + boost::mutex::scoped_lock lock(mutex_); + decoder_ = decoder; + size_ = size; + md5_ = md5; + } + + return image.Release(); + } +} diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "GdcmImageDecoder.h" + +#include + + +namespace OrthancPlugins +{ + class GdcmDecoderCache : public boost::noncopyable + { + private: + boost::mutex mutex_; + std::auto_ptr decoder_; + size_t size_; + std::string md5_; + + static std::string ComputeMd5(OrthancPluginContext* context, + const void* dicom, + size_t size); + + public: + GdcmDecoderCache() : size_(0) + { + } + + OrthancPluginImage* Decode(OrthancPluginContext* context, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + }; +} diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,300 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "OrthancImageWrapper.h" + +#include +#include +#include +#include +#include +#include +#include + + +namespace OrthancPlugins +{ + struct GdcmImageDecoder::PImpl + { + const void* dicom_; + size_t size_; + + gdcm::ImageReader reader_; + std::auto_ptr lut_; + std::auto_ptr photometric_; + std::auto_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) + { + 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) + { + switch (image.GetPixelFormat()) + { + case gdcm::PixelFormat::UINT8: + return OrthancPluginPixelFormat_RGB24; + + 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; + + default: + throw std::runtime_error("Unsupport pixel format"); + } + } + + + OrthancPluginImage* GdcmImageDecoder::Decode(OrthancPluginContext* context, + 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_; + OrthancImageWrapper target(context, format, width, height); + + if (width == 0 || + height == 0) + { + return target.Release(); + } + + if (decoded.empty()) + { + decoded.resize(pimpl_->GetImage().GetBufferLength()); + pimpl_->GetImage().GetBuffer(&decoded[0]); + } + + 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 char* a = &decoded[sourcePitch * height * frameIndex]; + char* b = target.GetBuffer(); + + for (uint32_t y = 0; y < height; y++) + { + memcpy(b, a, sourcePitch); + a += sourcePitch; + b += targetPitch; + } + } + + return target.Release(); + } +} diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 +#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(OrthancPluginContext* context, + unsigned int frameIndex) const; + }; +} diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "OrthancImageWrapper.h" + +#include + +namespace OrthancPlugins +{ + OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) : + context_(context) + { + image_ = OrthancPluginCreateImage(context_, format, width, height); + if (image_ == NULL) + { + throw std::runtime_error("Cannot create an image"); + } + } + + + OrthancImageWrapper::~OrthancImageWrapper() + { + if (image_ != NULL) + { + OrthancPluginFreeImage(context_, image_); + } + } + + + OrthancPluginImage* OrthancImageWrapper::Release() + { + OrthancPluginImage* tmp = image_; + image_ = NULL; + return tmp; + } + + + uint32_t OrthancImageWrapper::GetWidth() + { + return OrthancPluginGetImageWidth(context_, image_); + } + + + uint32_t OrthancImageWrapper::GetHeight() + { + return OrthancPluginGetImageHeight(context_, image_); + } + + + uint32_t OrthancImageWrapper::GetPitch() + { + return OrthancPluginGetImagePitch(context_, image_); + } + + + OrthancPluginPixelFormat OrthancImageWrapper::GetFormat() + { + return OrthancPluginGetImagePixelFormat(context_, image_); + } + + + char* OrthancImageWrapper::GetBuffer() + { + return reinterpret_cast(OrthancPluginGetImageBuffer(context_, image_)); + } +} diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,61 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 + +namespace OrthancPlugins +{ + class OrthancImageWrapper + { + private: + OrthancPluginContext* context_; + OrthancPluginImage* image_; + + public: + OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height); + + // Takes the ownership + OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginImage* image) : + context_(context), + image_(image) + { + } + + ~OrthancImageWrapper(); + + OrthancPluginImage* Release(); + + uint32_t GetWidth(); + + uint32_t GetHeight(); + + uint32_t GetPitch(); + + OrthancPluginPixelFormat GetFormat(); + + char* GetBuffer(); + }; +} diff -r 47d032c48818 -r b1a6f49b21dd Plugins/Samples/GdcmDecoder/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp Fri Nov 27 12:53:32 2015 +0100 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 + +static OrthancPluginContext* context_ = NULL; +static OrthancPlugins::GdcmDecoderCache cache_; + + +static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex) +{ + try + { +#if 0 + // Do not use the cache + OrthancPlugins::GdcmImageDecoder decoder(dicom, size); + *target = decoder.Decode(context_, frameIndex); +#else + *target = cache_.Decode(context_, dicom, size, frameIndex); +#endif + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + *target = NULL; + + std::string s = "Cannot decode image using GDCM: " + std::string(e.what()); + OrthancPluginLogError(context_, s.c_str()); + return OrthancPluginErrorCode_Plugin; + } +} + + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + context_ = context; + OrthancPluginLogWarning(context_, "Initializing the advanced decoder of medical images using GDCM"); + + + /* 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; + } + + OrthancPluginSetDescription(context_, "Advanced decoder of medical images using GDCM."); + OrthancPluginRegisterDecodeImageCallback(context_, DecodeImageCallback); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "gdcm-decoder"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return GDCM_DECODER_VERSION; + } +}