# HG changeset patch # User Sebastien Jodogne # Date 1448645215 -3600 # Node ID a6ba21a083e588472de6f75849c6ed429f261a6a # Parent dbe7c97b6b4a7d35a914806b7951ff4cb390036c major refactoring diff -r dbe7c97b6b4a -r a6ba21a083e5 NEWS --- a/NEWS Fri Nov 27 16:19:09 2015 +0100 +++ b/NEWS Fri Nov 27 18:26:55 2015 +0100 @@ -6,6 +6,13 @@ * The GDCM decoder replaces the built-in Orthanc decoder +TODO : Remove any dependency on GDCM in "Plugin" +TODO : Use "/series/ordered-slices" +TODO : Support cine and multi-frame images +TODO : Remove class "ParsedDicomFile" +TODO : Interpolation does not work at least with Firefox + + Version 1.3 (2015-11-27) ======================== diff -r dbe7c97b6b4a -r a6ba21a083e5 Plugin/Cache/CacheScheduler.cpp --- a/Plugin/Cache/CacheScheduler.cpp Fri Nov 27 16:19:09 2015 +0100 +++ b/Plugin/Cache/CacheScheduler.cpp Fri Nov 27 18:26:55 2015 +0100 @@ -254,6 +254,11 @@ content.clear(); return factory_->Create(content, item); } + + ICacheFactory& GetFactory() + { + return *factory_; + } }; @@ -401,4 +406,9 @@ policy_.reset(policy); } + + ICacheFactory& CacheScheduler::GetFactory(int bundle) + { + return GetBundleScheduler(bundle).GetFactory(); + } } diff -r dbe7c97b6b4a -r a6ba21a083e5 Plugin/Cache/CacheScheduler.h --- a/Plugin/Cache/CacheScheduler.h Fri Nov 27 16:19:09 2015 +0100 +++ b/Plugin/Cache/CacheScheduler.h Fri Nov 27 18:26:55 2015 +0100 @@ -79,5 +79,7 @@ void Prefetch(int bundle, const std::string& item); + + ICacheFactory& GetFactory(int bundle); }; } diff -r dbe7c97b6b4a -r a6ba21a083e5 Plugin/DecodedImageAdapter.cpp --- a/Plugin/DecodedImageAdapter.cpp Fri Nov 27 16:19:09 2015 +0100 +++ b/Plugin/DecodedImageAdapter.cpp Fri Nov 27 18:26:55 2015 +0100 @@ -20,8 +20,13 @@ #include "DecodedImageAdapter.h" +#include "../Orthanc/Core/Images/ImageBuffer.h" +#include "../Orthanc/Core/Images/ImageProcessing.h" +#include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h" +#include "../Orthanc/Resources/ThirdParty/base64/base64.h" +#include "ParsedDicomImage.h" #include "ViewerToolbox.h" -#include "ParsedDicomImage.h" #include #include @@ -84,10 +89,38 @@ return false; } + + bool ok = false; + +#if 1 + Json::Value tags; + std::string dicom; + if (!GetStringFromOrthanc(dicom, context_, "/instances/" + instanceId + "/file") || + !GetJsonFromOrthanc(tags, context_, "/instances/" + instanceId + "/tags")) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + std::auto_ptr image(decoderCache_.Decode(context_, dicom.c_str(), dicom.size(), 0 /* TODO Frame */)); + + Json::Value json; + if (GetCornerstoneMetadata(json, tags, *image)) + { + if (type == CompressionType_Deflate) + { + ok = EncodeUsingDeflate(json, *image, 9); + } + else if (type == CompressionType_Jpeg) + { + ok = EncodeUsingJpeg(json, *image, level); + } + } + +#else + ParsedDicomImage image(context_, instanceId); Json::Value json; - bool ok = false; if (type == CompressionType_Deflate) { @@ -97,6 +130,7 @@ { ok = image.EncodeUsingJpeg(json, level); } +#endif if (ok) { @@ -112,4 +146,266 @@ return false; } } + + + static bool GetTagValue(std::string& value, + const Json::Value& tags, + const std::string& tag) + { + if (tags.type() == Json::objectValue && + tags.isMember(tag) && + tags[tag].type() == Json::objectValue && + tags[tag].isMember("Type") && + tags[tag].isMember("Value") && + tags[tag]["Type"].type() == Json::stringValue && + tags[tag]["Value"].type() == Json::stringValue && + tags[tag]["Type"].asString() == "String") + { + value = tags[tag]["Value"].asString(); + return true; + } + else + { + return false; + } + } + + + + bool DecodedImageAdapter::GetCornerstoneMetadata(Json::Value& result, + const Json::Value& tags, + OrthancImageWrapper& image) + { + using namespace Orthanc; + + float windowCenter, windowWidth; + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), + image.GetHeight(), image.GetPitch(), image.GetBuffer()); + + switch (accessor.GetFormat()) + { + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); + result["minPixelValue"] = (a < 0 ? static_cast(a) : 0); + result["maxPixelValue"] = (b > 0 ? static_cast(b) : 1); + result["color"] = false; + + windowCenter = static_cast(a + b) / 2.0f; + + if (a == b) + { + windowWidth = 256.0f; // Arbitrary value + } + else + { + windowWidth = static_cast(b - a) / 2.0f; + } + + break; + } + + case PixelFormat_RGB24: + result["minPixelValue"] = 0; + result["maxPixelValue"] = 255; + result["color"] = true; + windowCenter = 127.5f; + windowWidth = 256.0f; + break; + + default: + return false; + } + + result["slope"] = image.GetSlope(); + result["intercept"] = image.GetIntercept(); + result["rows"] = image.GetHeight(); + result["columns"] = image.GetWidth(); + result["height"] = image.GetHeight(); + result["width"] = image.GetWidth(); + result["columnPixelSpacing"] = image.GetColumnPixelSpacing(); + result["rowPixelSpacing"] = image.GetRowPixelSpacing(); + + result["windowCenter"] = windowCenter * image.GetSlope() + image.GetIntercept(); + result["windowWidth"] = windowWidth * image.GetSlope(); + + try + { + std::string width, center; + if (GetTagValue(center, tags, "0028,1050" /*DICOM_TAG_WINDOW_CENTER*/) && + GetTagValue(width, tags, "0028,1051" /*DICOM_TAG_WINDOW_WIDTH*/)) + { + float a = boost::lexical_cast(width); + float b = boost::lexical_cast(center); + result["windowWidth"] = a; + result["windowCenter"] = b; + } + } + catch (boost::bad_lexical_cast&) + { + } + + return true; + } + + + + bool DecodedImageAdapter::EncodeUsingDeflate(Json::Value& result, + OrthancImageWrapper& image, + uint8_t compressionLevel /* between 0 and 9 */) + { + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), + image.GetHeight(), image.GetPitch(), image.GetBuffer()); + + Orthanc::ImageBuffer buffer; + buffer.SetMinimalPitchForced(true); + + Orthanc::ImageAccessor converted; + + switch (accessor.GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + converted = accessor; + break; + + case Orthanc::PixelFormat_Grayscale8: + case Orthanc::PixelFormat_Grayscale16: + buffer.SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + buffer.SetWidth(accessor.GetWidth()); + buffer.SetHeight(accessor.GetHeight()); + converted = buffer.GetAccessor(); + Orthanc::ImageProcessing::Convert(converted, accessor); + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + converted = accessor; + break; + + default: + // Unsupported pixel format + return false; + } + + // Sanity check: The pitch must be minimal + assert(converted.GetSize() == converted.GetWidth() * converted.GetHeight() * + GetBytesPerPixel(converted.GetFormat())); + result["Orthanc"]["Compression"] = "Deflate"; + result["sizeInBytes"] = converted.GetSize(); + + std::string z; + CompressUsingDeflate(z, image.GetContext(), converted.GetConstBuffer(), converted.GetSize()); + + result["Orthanc"]["PixelData"] = base64_encode(z); + + return true; + } + + + + template + static void ChangeDynamics(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + SourceType source1, TargetType target1, + SourceType source2, TargetType target2) + { + if (source.GetWidth() != target.GetWidth() || + source.GetHeight() != target.GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + float scale = static_cast(target2 - target1) / static_cast(source2 - source1); + float offset = static_cast(target1) - scale * static_cast(source1); + + const float minValue = static_cast(std::numeric_limits::min()); + const float maxValue = static_cast(std::numeric_limits::max()); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const SourceType* p = reinterpret_cast(source.GetConstRow(y)); + TargetType* q = reinterpret_cast(target.GetRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++, q++) + { + float v = (scale * static_cast(*p)) + offset; + + if (v > maxValue) + { + *q = std::numeric_limits::max(); + } + else if (v < minValue) + { + *q = std::numeric_limits::min(); + } + else + { + *q = static_cast(boost::math::iround(v)); + } + } + } + } + + + bool DecodedImageAdapter::EncodeUsingJpeg(Json::Value& result, + OrthancImageWrapper& image, + uint8_t quality /* between 0 and 100 */) + { + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), + image.GetHeight(), image.GetPitch(), image.GetBuffer()); + + Orthanc::ImageBuffer buffer; + buffer.SetMinimalPitchForced(true); + + Orthanc::ImageAccessor converted; + + if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale8 || + accessor.GetFormat() == Orthanc::PixelFormat_RGB24) + { + result["Orthanc"]["Stretched"] = false; + converted = accessor; + } + else if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16 || + accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16) + { + result["Orthanc"]["Stretched"] = true; + buffer.SetFormat(Orthanc::PixelFormat_Grayscale8); + buffer.SetWidth(accessor.GetWidth()); + buffer.SetHeight(accessor.GetHeight()); + converted = buffer.GetAccessor(); + + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); + result["Orthanc"]["StretchLow"] = static_cast(a); + result["Orthanc"]["StretchHigh"] = static_cast(b); + + if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + ChangeDynamics(converted, accessor, a, 0, b, 255); + } + else + { + ChangeDynamics(converted, accessor, a, 0, b, 255); + } + } + else + { + return false; + } + + result["Orthanc"]["Compression"] = "Jpeg"; + result["sizeInBytes"] = converted.GetSize(); + + std::string jpeg; + WriteJpegToMemory(jpeg, image.GetContext(), converted, quality); + + result["Orthanc"]["PixelData"] = base64_encode(jpeg); + return true; + } } diff -r dbe7c97b6b4a -r a6ba21a083e5 Plugin/DecodedImageAdapter.h --- a/Plugin/DecodedImageAdapter.h Fri Nov 27 16:19:09 2015 +0100 +++ b/Plugin/DecodedImageAdapter.h Fri Nov 27 18:26:55 2015 +0100 @@ -24,6 +24,11 @@ #include #include +#include + +#include "../Orthanc/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h" +#include "../Orthanc/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h" + namespace OrthancPlugins { @@ -41,7 +46,20 @@ std::string& instanceId, const std::string& uri); + static bool GetCornerstoneMetadata(Json::Value& result, + const Json::Value& tags, + OrthancImageWrapper& image); + + static bool EncodeUsingDeflate(Json::Value& result, + OrthancImageWrapper& image, + uint8_t compressionLevel /* between 0 and 9 */); + + static bool EncodeUsingJpeg(Json::Value& result, + OrthancImageWrapper& image, + uint8_t quality /* between 0 and 100 */); + OrthancPluginContext* context_; + GdcmDecoderCache decoderCache_; public: DecodedImageAdapter(OrthancPluginContext* context) : context_(context) @@ -50,5 +68,10 @@ virtual bool Create(std::string& content, const std::string& uri); + + GdcmDecoderCache& GetDecoderCache() + { + return decoderCache_; + } }; } diff -r dbe7c97b6b4a -r a6ba21a083e5 Plugin/ParsedDicomImage.cpp --- a/Plugin/ParsedDicomImage.cpp Fri Nov 27 16:19:09 2015 +0100 +++ b/Plugin/ParsedDicomImage.cpp Fri Nov 27 18:26:55 2015 +0100 @@ -36,6 +36,8 @@ #include "../Orthanc/Resources/ThirdParty/base64/base64.h" + + namespace OrthancPlugins { class ParsedDicomImage::PImpl diff -r dbe7c97b6b4a -r a6ba21a083e5 Plugin/Plugin.cpp --- a/Plugin/Plugin.cpp Fri Nov 27 16:19:09 2015 +0100 +++ b/Plugin/Plugin.cpp Fri Nov 27 18:26:55 2015 +0100 @@ -327,14 +327,20 @@ { try { -#if 1 + std::auto_ptr image; + +#if 0 // Do not use the cache OrthancPlugins::GdcmImageDecoder decoder(dicom, size); - *target = decoder.Decode(context_, frameIndex); + image.reset(new OrthancPlugins::OrthancImageWrapper(context_, decoder, frameIndex)); #else - *target = cache_.Decode(context_, dicom, size, frameIndex); + using namespace OrthancPlugins; + ICacheFactory& factory = cache_->GetScheduler().GetFactory(CacheBundle_DecodedImage); + image.reset(dynamic_cast(factory).GetDecoderCache().Decode(context_, dicom, size, frameIndex)); #endif + *target = image->Release(); + return OrthancPluginErrorCode_Success; } catch (std::runtime_error& e)