Mercurial > hg > orthanc-webviewer
view Plugin/DecodedImageAdapter.cpp @ 317:f09050e06811 OrthancWebViewer-2.9
OrthancWebViewer-2.9
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 26 Mar 2024 11:36:15 +0100 |
parents | 591ca447ebf8 |
children | 553fa466835a |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2024 Osimis S.A., Belgium * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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 <http://www.gnu.org/licenses/>. **/ #include "DecodedImageAdapter.h" #include "ViewerToolbox.h" #include <Images/ImageBuffer.h> #include <Images/ImageProcessing.h> #include <Logging.h> #include <OrthancException.h> #include <Toolbox.h> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/regex.hpp> namespace OrthancPlugins { static bool GetStringTag(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; } } static float GetFloatTag(const Json::Value& tags, const std::string& tag, float defaultValue) { std::string tmp; if (GetStringTag(tmp, tags, tag)) { try { return boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(tmp)); } catch (boost::bad_lexical_cast&) { } } return defaultValue; } bool DecodedImageAdapter::ParseUri(CompressionType& type, uint8_t& compressionLevel, std::string& instanceId, unsigned int& frameIndex, const std::string& uri) { boost::regex pattern("^([a-z0-9]+)-([a-z0-9-]+)_([0-9]+)$"); boost::cmatch what; if (!regex_match(uri.c_str(), what, pattern)) { return false; } std::string compression(what[1]); instanceId = what[2]; frameIndex = boost::lexical_cast<unsigned int>(what[3]); if (compression == "deflate") { type = CompressionType_Deflate; } else if (boost::starts_with(compression, "jpeg")) { type = CompressionType_Jpeg; int level = boost::lexical_cast<int>(compression.substr(4)); if (level <= 0 || level > 100) { return false; } compressionLevel = static_cast<uint8_t>(level); } else { return false; } return true; } bool DecodedImageAdapter::Create(std::string& content, const std::string& uri) { LOG(INFO) << "Decoding DICOM instance: " << uri; CompressionType type; uint8_t level; std::string instanceId; unsigned int frameIndex; if (!ParseUri(type, level, instanceId, frameIndex, uri)) { return false; } bool ok = false; 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::unique_ptr<OrthancImage> image( new OrthancImage(OrthancPluginDecodeDicomImage( context_, dicom.c_str(), dicom.size(), frameIndex))); Json::Value json; if (GetCornerstoneMetadata(json, tags, *image)) { if (type == CompressionType_Deflate) { ok = EncodeUsingDeflate(json, *image); } else if (type == CompressionType_Jpeg) { ok = EncodeUsingJpeg(json, *image, level); } } if (ok) { std::string photometric; if (GetStringTag(photometric, tags, "0028,0004")) { json["Orthanc"]["PhotometricInterpretation"] = photometric; } Orthanc::Toolbox::WriteFastJson(content, json); return true; } else { LOG(WARNING) << "Unable to decode the following instance: " << uri; return false; } } bool DecodedImageAdapter::GetCornerstoneMetadata(Json::Value& result, const Json::Value& tags, const OrthancImage& image) { float windowCenter, windowWidth; Orthanc::ImageAccessor accessor; accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetPixelFormat()), image.GetWidth(), image.GetHeight(), image.GetPitch(), image.GetBuffer()); switch (accessor.GetFormat()) { case Orthanc::PixelFormat_Grayscale8: case Orthanc::PixelFormat_Grayscale16: case Orthanc::PixelFormat_SignedGrayscale16: { int64_t a, b; Orthanc::ImageProcessing::GetMinMaxIntegerValue(a, b, accessor); result["minPixelValue"] = (a < 0 ? static_cast<int32_t>(a) : 0); result["maxPixelValue"] = (b > 0 ? static_cast<int32_t>(b) : 1); result["color"] = false; windowCenter = static_cast<float>(a + b) / 2.0f; if (a == b) { windowWidth = 256.0f; // Arbitrary value } else { windowWidth = static_cast<float>(b - a) / 2.0f; } break; } case Orthanc::PixelFormat_RGB24: case Orthanc::PixelFormat_RGB48: result["minPixelValue"] = 0; result["maxPixelValue"] = 255; result["color"] = true; windowCenter = 127.5f; windowWidth = 256.0f; break; default: return false; } float slope = GetFloatTag(tags, "0028,1053", 1.0f); float intercept = GetFloatTag(tags, "0028,1052", 0.0f); result["slope"] = slope; result["intercept"] = intercept; result["rows"] = image.GetHeight(); result["columns"] = image.GetWidth(); result["height"] = image.GetHeight(); result["width"] = image.GetWidth(); bool ok = false; std::string pixelSpacing; if (GetStringTag(pixelSpacing, tags, "0028,0030")) { std::vector<std::string> tokens; Orthanc::Toolbox::TokenizeString(tokens, pixelSpacing, '\\'); if (tokens.size() >= 2) { try { result["columnPixelSpacing"] = boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(tokens[1])); result["rowPixelSpacing"] = boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(tokens[0])); ok = true; } catch (boost::bad_lexical_cast&) { } } } if (!ok) { result["columnPixelSpacing"] = 1.0f; result["rowPixelSpacing"] = 1.0f; } result["windowCenter"] = GetFloatTag(tags, "0028,1050", windowCenter * slope + intercept); result["windowWidth"] = GetFloatTag(tags, "0028,1051", windowWidth * slope); return true; } static void ConvertRGB48ToRGB24(Orthanc::ImageAccessor& target, const Orthanc::ImageAccessor& source) { if (source.GetWidth() != target.GetWidth() || source.GetHeight() != target.GetHeight()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); } const unsigned int width = source.GetWidth(); const unsigned int height = source.GetHeight(); for (unsigned int y = 0; y < height; y++) { const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y)); uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); for (unsigned int x = 0; x < width; x++) { q[0] = p[0] >> 8; q[1] = p[1] >> 8; q[2] = p[2] >> 8; p += 3; q += 3; } } } bool DecodedImageAdapter::EncodeUsingDeflate(Json::Value& result, const OrthancImage& image) { Orthanc::ImageAccessor accessor; accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetPixelFormat()), image.GetWidth(), image.GetHeight(), image.GetPitch(), image.GetBuffer()); std::unique_ptr<Orthanc::ImageBuffer> buffer; Orthanc::ImageAccessor converted; switch (accessor.GetFormat()) { case Orthanc::PixelFormat_RGB24: accessor.GetReadOnlyAccessor(converted); break; case Orthanc::PixelFormat_RGB48: buffer.reset(new Orthanc::ImageBuffer(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false)); buffer->GetWriteableAccessor(converted); ConvertRGB48ToRGB24(converted, accessor); break; case Orthanc::PixelFormat_Grayscale8: case Orthanc::PixelFormat_Grayscale16: buffer.reset(new Orthanc::ImageBuffer(Orthanc::PixelFormat_Grayscale16, accessor.GetWidth(), accessor.GetHeight(), true /* force minimal pitch */)); buffer->GetWriteableAccessor(converted); Orthanc::ImageProcessing::Convert(converted, accessor); break; case Orthanc::PixelFormat_SignedGrayscale16: accessor.GetReadOnlyAccessor(converted); break; default: // Unsupported pixel format return false; } result["Orthanc"]["IsSigned"] = (accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16); // 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, GetGlobalContext(), converted.GetConstBuffer(), converted.GetSize()); std::string s; Orthanc::Toolbox::EncodeBase64(s, z); result["Orthanc"]["PixelData"] = s; return true; } template <typename TargetType, typename SourceType> 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<float>(target2 - target1) / static_cast<float>(source2 - source1); float offset = static_cast<float>(target1) - scale * static_cast<float>(source1); const float minValue = static_cast<float>(std::numeric_limits<TargetType>::min()); const float maxValue = static_cast<float>(std::numeric_limits<TargetType>::max()); const unsigned int width = source.GetWidth(); const unsigned int height = source.GetHeight(); for (unsigned int y = 0; y < height; y++) { const SourceType* p = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y)); for (unsigned int x = 0; x < width; x++, p++, q++) { float v = (scale * static_cast<float>(*p)) + offset; if (v > maxValue) { *q = std::numeric_limits<TargetType>::max(); } else if (v < minValue) { *q = std::numeric_limits<TargetType>::min(); } else { //*q = static_cast<TargetType>(boost::math::iround(v)); // http://stackoverflow.com/a/485546/881731 *q = static_cast<TargetType>(floor(v + 0.5f)); } } } } bool DecodedImageAdapter::EncodeUsingJpeg(Json::Value& result, const OrthancImage& image, uint8_t quality /* between 0 and 100 */) { Orthanc::ImageAccessor accessor; accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetPixelFormat()), image.GetWidth(), image.GetHeight(), image.GetPitch(), image.GetBuffer()); std::unique_ptr<Orthanc::ImageBuffer> buffer; Orthanc::ImageAccessor converted; if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale8 || accessor.GetFormat() == Orthanc::PixelFormat_RGB24) { result["Orthanc"]["Stretched"] = false; accessor.GetReadOnlyAccessor(converted); } else if (accessor.GetFormat() == Orthanc::PixelFormat_RGB48) { result["Orthanc"]["Stretched"] = false; buffer.reset(new Orthanc::ImageBuffer(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false)); buffer->GetWriteableAccessor(converted); ConvertRGB48ToRGB24(converted, accessor); } else if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16 || accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16) { result["Orthanc"]["Stretched"] = true; buffer.reset(new Orthanc::ImageBuffer(Orthanc::PixelFormat_Grayscale8, accessor.GetWidth(), accessor.GetHeight(), true /* force minimal pitch */)); buffer->GetWriteableAccessor(converted); int64_t a, b; Orthanc::ImageProcessing::GetMinMaxIntegerValue(a, b, accessor); result["Orthanc"]["StretchLow"] = static_cast<int32_t>(a); result["Orthanc"]["StretchHigh"] = static_cast<int32_t>(b); if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16) { ChangeDynamics<uint8_t, uint16_t>(converted, accessor, a, 0, b, 255); } else { ChangeDynamics<uint8_t, int16_t>(converted, accessor, a, 0, b, 255); } } else { return false; } result["Orthanc"]["IsSigned"] = (accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16); result["Orthanc"]["Compression"] = "Jpeg"; result["sizeInBytes"] = converted.GetSize(); std::string jpeg; WriteJpegToMemory(jpeg, GetGlobalContext(), converted, quality); std::string s; Orthanc::Toolbox::EncodeBase64(s, jpeg); result["Orthanc"]["PixelData"] = s; return true; } }