# HG changeset patch # User Sebastien Jodogne # Date 1582635463 -3600 # Node ID a9ce35d67c3cff30deff7476d415a174a5283f4d # Parent e85bfba2d307b1d14ca2623d9de44395f8c9300f implementation of "/instances/.../rendered" for grayscale images diff -r e85bfba2d307 -r a9ce35d67c3c Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Tue Feb 25 11:07:56 2020 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Tue Feb 25 13:57:43 2020 +0100 @@ -984,6 +984,21 @@ } } + bool DicomMap::ParseFirstFloat(float& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseFirstFloat(result); + } + } + bool DicomMap::ParseDouble(double& result, const DicomTag& tag) const { diff -r e85bfba2d307 -r a9ce35d67c3c Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Tue Feb 25 11:07:56 2020 +0100 +++ b/Core/DicomFormat/DicomMap.h Tue Feb 25 13:57:43 2020 +0100 @@ -205,6 +205,9 @@ bool ParseFloat(float& result, const DicomTag& tag) const; + bool ParseFirstFloat(float& result, + const DicomTag& tag) const; + bool ParseDouble(double& result, const DicomTag& tag) const; diff -r e85bfba2d307 -r a9ce35d67c3c Core/DicomFormat/DicomValue.cpp --- a/Core/DicomFormat/DicomValue.cpp Tue Feb 25 11:07:56 2020 +0100 +++ b/Core/DicomFormat/DicomValue.cpp Tue Feb 25 13:57:43 2020 +0100 @@ -231,6 +231,11 @@ return ParseValue(result, *this); } + bool DicomValue::ParseFirstFloat(float& result) const + { + return ParseFirstValue(result, *this); + } + bool DicomValue::ParseFirstUnsignedInteger(unsigned int& result) const { return ParseFirstValue(result, *this); diff -r e85bfba2d307 -r a9ce35d67c3c Core/DicomFormat/DicomValue.h --- a/Core/DicomFormat/DicomValue.h Tue Feb 25 11:07:56 2020 +0100 +++ b/Core/DicomFormat/DicomValue.h Tue Feb 25 13:57:43 2020 +0100 @@ -112,6 +112,8 @@ bool ParseDouble(double& result) const; + bool ParseFirstFloat(float& result) const; + bool ParseFirstUnsignedInteger(unsigned int& result) const; void Serialize(Json::Value& target) const; diff -r e85bfba2d307 -r a9ce35d67c3c Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Tue Feb 25 11:07:56 2020 +0100 +++ b/Core/Images/ImageProcessing.cpp Tue Feb 25 13:57:43 2020 +0100 @@ -1430,6 +1430,46 @@ } + void ImageProcessing::ShiftScale(ImageAccessor& target, + const ImageAccessor& source, + float offset, + float scaling, + bool useRound) + { + // Rewrite "(x + offset) * scaling" as "a * x + b" + + const float a = scaling; + const float b = offset * scaling; + + switch (target.GetFormat()) + { + case PixelFormat_Grayscale8: + + switch (source.GetFormat()) + { + case PixelFormat_Float32: + if (useRound) + { + ShiftScaleInternal( + target, source, a, b, std::numeric_limits::min()); + } + else + { + ShiftScaleInternal( + target, source, a, b, std::numeric_limits::min()); + } + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue) { const unsigned int width = image.GetWidth(); diff -r e85bfba2d307 -r a9ce35d67c3c Core/Images/ImageProcessing.h --- a/Core/Images/ImageProcessing.h Tue Feb 25 11:07:56 2020 +0100 +++ b/Core/Images/ImageProcessing.h Tue Feb 25 13:57:43 2020 +0100 @@ -133,6 +133,12 @@ float scaling, bool useRound); + void ShiftScale(ImageAccessor& target, + const ImageAccessor& source, + float offset, + float scaling, + bool useRound); + void Invert(ImageAccessor& image); void Invert(ImageAccessor& image, int64_t maxValue); diff -r e85bfba2d307 -r a9ce35d67c3c OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Feb 25 11:07:56 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Feb 25 13:57:43 2020 +0100 @@ -672,23 +672,20 @@ class RenderedFrameHandler : public IDecodedFrameHandler { - public: - virtual void Handle(RestApiGetCall& call, - std::auto_ptr& decoded, - const DicomMap& dicom) ORTHANC_OVERRIDE + private: + static void GetDicomParameters(bool& invert, + float& rescaleSlope, + float& rescaleIntercept, + float& windowWidth, + float& windowCenter, + const DicomMap& dicom) { - static const char* ARG_WINDOW_CENTER = "window-center"; - static const char* ARG_WINDOW_WIDTH = "window-width"; - static const char* ARG_MAX_WIDTH = "max-width"; - static const char* ARG_MAX_HEIGHT = "max-height"; - static const char* ARG_SMOOTH = "smooth"; - DicomImageInformation info(dicom); - const bool invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); - - float rescaleSlope = 1.0f; - float rescaleIntercept = 0.0f; + invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); + + rescaleSlope = 1.0f; + rescaleIntercept = 0.0f; if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) @@ -697,15 +694,29 @@ dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); } - float windowWidth = static_cast(1 << info.GetBitsStored()); - float windowCenter = windowWidth / 2.0f; + windowWidth = static_cast(1 << info.GetBitsStored()); + windowCenter = windowWidth / 2.0f; if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) { - dicom.ParseFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); - dicom.ParseFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); + dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); + dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); } + } + + static void GetUserArguments(float& windowWidth /* inout */, + float& windowCenter /* inout */, + unsigned int& argWidth, + unsigned int& argHeight, + bool& smooth, + RestApiGetCall& call) + { + static const char* ARG_WINDOW_CENTER = "window-center"; + static const char* ARG_WINDOW_WIDTH = "window-width"; + static const char* ARG_WIDTH = "width"; + static const char* ARG_HEIGHT = "height"; + static const char* ARG_SMOOTH = "smooth"; if (call.HasArgument(ARG_WINDOW_WIDTH)) { @@ -733,54 +744,54 @@ } } - unsigned int maxWidth = 0; - unsigned int maxHeight = 0; + argWidth = 0; + argHeight = 0; - if (call.HasArgument(ARG_MAX_WIDTH)) + if (call.HasArgument(ARG_WIDTH)) { try { - int tmp = boost::lexical_cast(call.GetArgument(ARG_MAX_WIDTH, "")); + int tmp = boost::lexical_cast(call.GetArgument(ARG_WIDTH, "")); if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange, - "Argument cannot be negative: " + std::string(ARG_MAX_WIDTH)); + "Argument cannot be negative: " + std::string(ARG_WIDTH)); } else { - maxWidth = static_cast(tmp); + argWidth = static_cast(tmp); } } catch (boost::bad_lexical_cast&) { throw OrthancException(ErrorCode_ParameterOutOfRange, - "Bad value for argument: " + std::string(ARG_MAX_WIDTH)); + "Bad value for argument: " + std::string(ARG_WIDTH)); } } - if (call.HasArgument(ARG_MAX_HEIGHT)) + if (call.HasArgument(ARG_HEIGHT)) { try { - int tmp = boost::lexical_cast(call.GetArgument(ARG_MAX_HEIGHT, "")); + int tmp = boost::lexical_cast(call.GetArgument(ARG_HEIGHT, "")); if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange, - "Argument cannot be negative: " + std::string(ARG_MAX_HEIGHT)); + "Argument cannot be negative: " + std::string(ARG_HEIGHT)); } else { - maxHeight = static_cast(tmp); + argHeight = static_cast(tmp); } } catch (boost::bad_lexical_cast&) { throw OrthancException(ErrorCode_ParameterOutOfRange, - "Bad value for argument: " + std::string(ARG_MAX_HEIGHT)); + "Bad value for argument: " + std::string(ARG_HEIGHT)); } } - bool smooth = true; + smooth = false; if (call.HasArgument(ARG_SMOOTH)) { @@ -801,58 +812,118 @@ "Argument must be Boolean: " + std::string(ARG_SMOOTH)); } } - + } + + + public: + virtual void Handle(RestApiGetCall& call, + std::auto_ptr& decoded, + const DicomMap& dicom) ORTHANC_OVERRIDE + { + bool invert; + float rescaleSlope, rescaleIntercept, windowWidth, windowCenter; + GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom); - unsigned int width = decoded->GetWidth(); - unsigned int height = decoded->GetHeight(); + unsigned int argWidth, argHeight; + bool smooth; + GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call); + + unsigned int targetWidth = decoded->GetWidth(); + unsigned int targetHeight = decoded->GetHeight(); if (decoded->GetWidth() != 0 && decoded->GetHeight() != 0) { float ratio = 1; - if (maxWidth != 0) + if (argWidth != 0 && + argHeight != 0) { - ratio = static_cast(maxWidth) / static_cast(decoded->GetWidth()); + float ratioX = static_cast(argWidth) / static_cast(decoded->GetWidth()); + float ratioY = static_cast(argHeight) / static_cast(decoded->GetHeight()); + ratio = std::min(ratioX, ratioY); } - - if (maxHeight != 0) + else if (argWidth != 0) { - float ratioY = static_cast(maxHeight) / static_cast(decoded->GetHeight()); - if (ratioY < ratio) - { - ratio = ratioY; - } + ratio = static_cast(argWidth) / static_cast(decoded->GetWidth()); } - - width = boost::math::iround(ratio * static_cast(decoded->GetWidth())); - height = boost::math::iround(ratio * static_cast(decoded->GetHeight())); + else if (argHeight != 0) + { + ratio = static_cast(argHeight) / static_cast(decoded->GetHeight()); + } + + targetWidth = boost::math::iround(ratio * static_cast(decoded->GetWidth())); + targetHeight = boost::math::iround(ratio * static_cast(decoded->GetHeight())); } if (decoded->GetFormat() == PixelFormat_RGB24) { - if (width == decoded->GetWidth() && - height == decoded->GetHeight()) + if (targetWidth == decoded->GetWidth() && + targetHeight == decoded->GetHeight()) { DefaultHandler(call, decoded, ImageExtractionMode_Preview, false); } else { - std::auto_ptr rescaled(new Image(decoded->GetFormat(), width, height, false)); + std::auto_ptr resized( + new Image(decoded->GetFormat(), targetWidth, targetHeight, false)); + if (smooth && - (width < decoded->GetWidth() || - height < decoded->GetHeight())) + (targetWidth < decoded->GetWidth() || + targetHeight < decoded->GetHeight())) { ImageProcessing::SmoothGaussian5x5(*decoded); } - ImageProcessing::Resize(*rescaled, *decoded); - DefaultHandler(call, rescaled, ImageExtractionMode_Preview, false); + + ImageProcessing::Resize(*resized, *decoded); + DefaultHandler(call, resized, ImageExtractionMode_Preview, false); } } else { - // TODO : (1) convert to float32, (2) apply windowing, (3) possibly rescale - throw OrthancException(ErrorCode_NotImplemented); + // Grayscale image: (1) convert to Float32, (2) apply + // windowing to get a Grayscale8, (3) possibly resize + + Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); + ImageProcessing::Convert(converted, *decoded); + + // Avoid divisions by zero + if (windowWidth <= 1.0f) + { + windowWidth = 1; + } + + if (std::abs(rescaleSlope) <= 0.1f) + { + rescaleSlope = 0.1f; + } + + const float scaling = 255.0f * rescaleSlope / windowWidth; + const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope; + + std::auto_ptr rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false)); + ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false); + + if (targetWidth == decoded->GetWidth() && + targetHeight == decoded->GetHeight()) + { + DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert); + } + else + { + std::auto_ptr resized( + new Image(PixelFormat_Grayscale8, targetWidth, targetHeight, false)); + + if (smooth && + (targetWidth < decoded->GetWidth() || + targetHeight < decoded->GetHeight())) + { + ImageProcessing::SmoothGaussian5x5(*rescaled); + } + + ImageProcessing::Resize(*resized, *rescaled); + DefaultHandler(call, resized, ImageExtractionMode_UInt8, invert); + } } }