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());
   }