Mercurial > hg > orthanc
changeset 6086:ff1b0e0b4988
added ImageProcessing::RenderDefaultWindow()
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 05 Apr 2025 14:44:35 +0200 |
parents | 8b7354bd8654 |
children | 7b334c586295 |
files | OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp OrthancFramework/Sources/DicomFormat/DicomImageInformation.h OrthancFramework/Sources/Images/ImageProcessing.cpp OrthancFramework/Sources/Images/ImageProcessing.h OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp |
diffstat | 5 files changed, 171 insertions(+), 93 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp Sat Apr 05 13:24:29 2025 +0200 +++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp Sat Apr 05 14:44:35 2025 +0200 @@ -44,6 +44,29 @@ namespace Orthanc { + Window::Window(double center, + double width) : + center_(center) + { + width_ = std::abs(width); + } + + + void Window::GetBounds(double& low, + double& high) const + { + low = center_ - width_ / 2.0; + high = center_ + width_ / 2.0; + } + + + Window Window::FromBounds(double low, + double high) + { + return Window((low + high) / 2.0, std::abs(high - low)); + } + + DicomImageInformation::DicomImageInformation(const DicomMap& values) { std::string sopClassUid; @@ -526,24 +549,11 @@ } - double DicomImageInformation::GetWindowCenter(size_t index) const + const Window& DicomImageInformation::GetWindow(size_t index) const { if (index < windows_.size()) { - return windows_[index].GetCenter(); - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - double DicomImageInformation::GetWindowWidth(size_t index) const - { - if (index < windows_.size()) - { - return windows_[index].GetWidth(); + return windows_[index]; } else { @@ -558,30 +568,28 @@ } - void DicomImageInformation::GetDefaultWindowing(double& center, - double& width) const + Window DicomImageInformation::GetDefaultWindow() const { if (windows_.empty()) { - width = static_cast<double>(1 << GetBitsStored()); - center = width / 2.0; + const double width = static_cast<double>(1 << GetBitsStored()); + const double center = width / 2.0; + return Window(center, width); } else { - center = windows_[0].GetCenter(); - width = windows_[0].GetWidth(); + return windows_[0]; } } void DicomImageInformation::ComputeRenderingTransform(double& offset, double& scaling, - double windowCenter, - double windowWidth) const + const Window& window) const { // Check out "../../../OrthancServer/Resources/ImplementationNotes/windowing.py" - windowWidth = std::abs(windowWidth); + float windowWidth = std::abs(window.GetWidth()); // Avoid divisions by zero static const double MIN = 0.0001; @@ -593,12 +601,12 @@ if (GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1) { scaling = -255.0 * GetRescaleSlope() / windowWidth; - offset = 255.0 * (windowCenter - GetRescaleIntercept()) / windowWidth + 127.5; + offset = 255.0 * (window.GetCenter() - GetRescaleIntercept()) / windowWidth + 127.5; } else { scaling = 255.0 * GetRescaleSlope() / windowWidth; - offset = 255.0 * (GetRescaleIntercept() - windowCenter) / windowWidth + 127.5; + offset = 255.0 * (GetRescaleIntercept() - window.GetCenter()) / windowWidth + 127.5; } } @@ -606,8 +614,6 @@ void DicomImageInformation::ComputeRenderingTransform(double& offset, double& scaling) const { - double center, width; - GetDefaultWindowing(center, width); - ComputeRenderingTransform(offset, scaling, center, width); + ComputeRenderingTransform(offset, scaling, GetDefaultWindow()); } }
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h Sat Apr 05 13:24:29 2025 +0200 +++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.h Sat Apr 05 14:44:35 2025 +0200 @@ -30,34 +30,37 @@ namespace Orthanc { + class ORTHANC_PUBLIC Window + { + private: + double center_; + double width_; + + public: + Window(double center, + double width); + + double GetCenter() const + { + return center_; + } + + double GetWidth() const + { + return width_; + } + + void GetBounds(double& low, + double& high) const; + + static Window FromBounds(double low, + double high); + }; + + class ORTHANC_PUBLIC DicomImageInformation { private: - class Window - { - private: - double center_; - double width_; - - public: - Window(double center, - double width) : - center_(center), - width_(width) - { - } - - double GetCenter() const - { - return center_; - } - - double GetWidth() const - { - return width_; - } - }; - unsigned int width_; unsigned int height_; unsigned int samplesPerPixel_; @@ -149,14 +152,11 @@ return windows_.size(); } - double GetWindowCenter(size_t index) const; - - double GetWindowWidth(size_t index) const; + const Window& GetWindow(size_t index) const; double ApplyRescale(double value) const; - void GetDefaultWindowing(double& center, - double& width) const; + Window GetDefaultWindow() const; /** * Compute the linear transform "x * scaling + offset" that maps a @@ -166,14 +166,13 @@ **/ void ComputeRenderingTransform(double& offset, double& scaling, - double windowCenter, - double windowWidth) const; + const Window& window) const; void ComputeRenderingTransform(double& offset, double& scaling, size_t windowIndex) const { - ComputeRenderingTransform(offset, scaling, GetWindowCenter(windowIndex), GetWindowWidth(windowIndex)); + ComputeRenderingTransform(offset, scaling, GetWindow(windowIndex)); } void ComputeRenderingTransform(double& offset,
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp Sat Apr 05 13:24:29 2025 +0200 +++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp Sat Apr 05 14:44:35 2025 +0200 @@ -3103,4 +3103,74 @@ throw OrthancException(ErrorCode_NotImplemented); } } + + + void ImageProcessing::Render(ImageAccessor& target, + const DicomImageInformation& info, + const ImageAccessor& source, + const Window& window) + { + if (source.GetFormat() == PixelFormat_RGB24) + { + Copy(target, source); + } + else if (source.GetFormat() == PixelFormat_Grayscale8 || + source.GetFormat() == PixelFormat_Grayscale16 || + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + if (target.GetFormat() != PixelFormat_Grayscale8) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + double offset, scaling; + info.ComputeRenderingTransform(offset, scaling); + ShiftScale2(target, source, static_cast<float>(offset), static_cast<float>(scaling), false); + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::RenderDefaultWindow(ImageAccessor& target, + const DicomImageInformation& info, + const ImageAccessor& source) + { + if (source.GetFormat() == PixelFormat_RGB24) + { + Copy(target, source); + } + else if (source.GetFormat() == PixelFormat_Grayscale8 || + source.GetFormat() == PixelFormat_Grayscale16 || + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + if (target.GetFormat() != PixelFormat_Grayscale8) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + double offset, scaling; + if (info.HasWindows()) + { + info.ComputeRenderingTransform(offset, scaling); // Use the default windowing + } + else + { + // Use the full dynamic range of the image + int64_t minValue, maxValue; + GetMinMaxIntegerValue(minValue, maxValue, source); + double minRescaled = info.ApplyRescale(minValue); + double maxRescaled = info.ApplyRescale(maxValue); + info.ComputeRenderingTransform(offset, scaling, Window::FromBounds(minRescaled, maxRescaled)); + } + + ShiftScale2(target, source, static_cast<float>(offset), static_cast<float>(scaling), false); + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } }
--- a/OrthancFramework/Sources/Images/ImageProcessing.h Sat Apr 05 13:24:29 2025 +0200 +++ b/OrthancFramework/Sources/Images/ImageProcessing.h Sat Apr 05 14:44:35 2025 +0200 @@ -26,6 +26,7 @@ #include "../OrthancFramework.h" +#include "../DicomFormat/DicomImageInformation.h" #include "ImageAccessor.h" #include <vector> @@ -222,5 +223,14 @@ static void Maximum(ImageAccessor& image /* inout */, const ImageAccessor& other); + + static void Render(ImageAccessor& target, + const DicomImageInformation& info, + const ImageAccessor& source, + const Window& window); + + static void RenderDefaultWindow(ImageAccessor& target, + const DicomImageInformation& info, + const ImageAccessor& source); }; }
--- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp Sat Apr 05 13:24:29 2025 +0200 +++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp Sat Apr 05 14:44:35 2025 +0200 @@ -109,8 +109,7 @@ ASSERT_EQ(PhotometricInterpretation_Monochrome1, info.GetPhotometricInterpretation()); ASSERT_FALSE(info.HasWindows()); ASSERT_EQ(0, info.GetWindowsCount()); - ASSERT_THROW(info.GetWindowWidth(0), OrthancException); - ASSERT_THROW(info.GetWindowCenter(0), OrthancException); + ASSERT_THROW(info.GetWindow(0), OrthancException); ASSERT_DOUBLE_EQ(141.75, info.ApplyRescale(14.0)); } @@ -121,14 +120,13 @@ DicomImageInformation info(m); ASSERT_TRUE(info.HasWindows()); ASSERT_EQ(3u, info.GetWindowsCount()); - ASSERT_DOUBLE_EQ(10.0, info.GetWindowCenter(0)); - ASSERT_DOUBLE_EQ(50.0, info.GetWindowWidth(0)); - ASSERT_DOUBLE_EQ(100.0, info.GetWindowCenter(1)); - ASSERT_DOUBLE_EQ(60.0, info.GetWindowWidth(1)); - ASSERT_DOUBLE_EQ(1000.0, info.GetWindowCenter(2)); - ASSERT_DOUBLE_EQ(70.0, info.GetWindowWidth(2)); - ASSERT_THROW(info.GetWindowWidth(3), OrthancException); - ASSERT_THROW(info.GetWindowCenter(3), OrthancException); + ASSERT_DOUBLE_EQ(10.0, info.GetWindow(0).GetCenter()); + ASSERT_DOUBLE_EQ(50.0, info.GetWindow(0).GetWidth()); + ASSERT_DOUBLE_EQ(100.0, info.GetWindow(1).GetCenter()); + ASSERT_DOUBLE_EQ(60.0, info.GetWindow(1).GetWidth()); + ASSERT_DOUBLE_EQ(1000.0, info.GetWindow(2).GetCenter()); + ASSERT_DOUBLE_EQ(70.0, info.GetWindow(2).GetWidth()); + ASSERT_THROW(info.GetWindow(3), OrthancException); } } @@ -143,19 +141,17 @@ m.SetValue(DICOM_TAG_COLUMNS, "16", false); m.SetValue(DICOM_TAG_BITS_ALLOCATED, "8", false); - double wc, ww; - { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); - ASSERT_DOUBLE_EQ(128.0, wc); - ASSERT_DOUBLE_EQ(256.0, ww); + Window w = info.GetDefaultWindow(); + ASSERT_DOUBLE_EQ(128.0, w.GetCenter()); + ASSERT_DOUBLE_EQ(256.0, w.GetWidth()); ASSERT_EQ(PhotometricInterpretation_Unknown, info.GetPhotometricInterpretation()); ASSERT_DOUBLE_EQ(0.0, info.GetRescaleIntercept()); ASSERT_DOUBLE_EQ(1.0, info.GetRescaleSlope()); double offset, scaling, x; - info.ComputeRenderingTransform(offset, scaling, -100, 200); + info.ComputeRenderingTransform(offset, scaling, Window(-100, 200)); x = -200; ASSERT_NEAR(0, x * scaling + offset, 0.000001); x = -100; ASSERT_NEAR(127.5, x * scaling + offset, 0.000001); @@ -166,11 +162,10 @@ { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); ASSERT_EQ(PhotometricInterpretation_Monochrome1, info.GetPhotometricInterpretation()); double offset, scaling, x; - info.ComputeRenderingTransform(offset, scaling, -100, 200); + info.ComputeRenderingTransform(offset, scaling, Window(-100, 200)); x = -200; ASSERT_NEAR(255, x * scaling + offset, 0.000001); x = -100; ASSERT_NEAR(127.5, x * scaling + offset, 0.000001); @@ -183,11 +178,10 @@ { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); ASSERT_EQ(PhotometricInterpretation_Monochrome2, info.GetPhotometricInterpretation()); double offset, scaling, x; - info.ComputeRenderingTransform(offset, scaling, -100, 200); + info.ComputeRenderingTransform(offset, scaling, Window(-100, 200)); x = -5; ASSERT_NEAR(0, x * scaling + offset, 0.000001); x = 0; ASSERT_NEAR(127.5, x * scaling + offset, 0.000001); @@ -198,11 +192,10 @@ { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); ASSERT_EQ(PhotometricInterpretation_Monochrome1, info.GetPhotometricInterpretation()); double offset, scaling, x; - info.ComputeRenderingTransform(offset, scaling, -100, 200); + info.ComputeRenderingTransform(offset, scaling, Window(-100, 200)); x = -5; ASSERT_NEAR(255, x * scaling + offset, 0.000001); x = 0; ASSERT_NEAR(127.5, x * scaling + offset, 0.000001); @@ -214,9 +207,9 @@ { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); - ASSERT_DOUBLE_EQ(8.0, wc); - ASSERT_DOUBLE_EQ(16.0, ww); + Window w = info.GetDefaultWindow(); + ASSERT_DOUBLE_EQ(8.0, w.GetCenter()); + ASSERT_DOUBLE_EQ(16.0, w.GetWidth()); ASSERT_EQ(PhotometricInterpretation_RGB, info.GetPhotometricInterpretation()); } @@ -227,9 +220,9 @@ { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); - ASSERT_DOUBLE_EQ(12.0, wc); - ASSERT_DOUBLE_EQ(-22.0, ww); + Window w = info.GetDefaultWindow(); + ASSERT_DOUBLE_EQ(12.0, w.GetCenter()); + ASSERT_DOUBLE_EQ(-22.0, w.GetWidth()); ASSERT_DOUBLE_EQ(-22.0, info.GetRescaleIntercept()); ASSERT_DOUBLE_EQ(-23.0, info.GetRescaleSlope()); } @@ -243,9 +236,9 @@ { DicomImageInformation info(m); - info.GetDefaultWindowing(wc, ww); - ASSERT_DOUBLE_EQ(12.0, wc); - ASSERT_DOUBLE_EQ(-22.0, ww); + Window w = info.GetDefaultWindow(); + ASSERT_DOUBLE_EQ(12.0, w.GetCenter()); + ASSERT_DOUBLE_EQ(-22.0, w.GetWidth()); ASSERT_DOUBLE_EQ(0.0, info.GetRescaleIntercept()); ASSERT_DOUBLE_EQ(1.0, info.GetRescaleSlope()); }