changeset 376:c1838c538510

support windowing when rendering grayscale images using on-the-fly deep zoom
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 05 Apr 2025 13:28:04 +0200 (6 weeks ago)
parents 2b0432826f3b
children be713f91c3e6
files NEWS ViewerPlugin/OrthancPyramidFrameFetcher.cpp ViewerPlugin/OrthancPyramidFrameFetcher.h
diffstat 3 files changed, 73 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Mar 17 16:31:47 2025 +0100
+++ b/NEWS	Sat Apr 05 13:28:04 2025 +0200
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+* Support windowing when rendering grayscale images using on-the-fly deep zoom
+
 
 Version 3.1 (2025-03-17)
 ========================
@@ -29,7 +31,7 @@
 
 => Minimum SDK version: 1.7.0 <=
 
-* On-the-fly creation of pyramids from frames of DICOM instances
+* On-the-fly creation of pyramids from frames of DICOM instances ("deep zoom" button)
 
 
 Version 2.1 (2024-10-18)
--- a/ViewerPlugin/OrthancPyramidFrameFetcher.cpp	Mon Mar 17 16:31:47 2025 +0100
+++ b/ViewerPlugin/OrthancPyramidFrameFetcher.cpp	Sat Apr 05 13:28:04 2025 +0200
@@ -26,6 +26,8 @@
 
 #include "../Framework/Inputs/OnTheFlyPyramid.h"
 
+#include <DicomFormat/DicomImageInformation.h>
+#include <DicomFormat/DicomMap.h>
 #include <Images/Image.h>
 #include <Images/ImageProcessing.h>
 
@@ -34,32 +36,6 @@
 
 namespace OrthancWSI
 {
-  void OrthancPyramidFrameFetcher::RenderGrayscale(Orthanc::ImageAccessor& target,
-                                                   const Orthanc::ImageAccessor& source)
-  {
-    Orthanc::Image converted(Orthanc::PixelFormat_Float32, source.GetWidth(), source.GetHeight(), false);
-    Orthanc::ImageProcessing::Convert(converted, source);
-
-    float minValue, maxValue;
-    Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, converted);
-
-    assert(minValue <= maxValue);
-    if (std::abs(maxValue - minValue) < 0.0001)
-    {
-      Orthanc::ImageProcessing::Set(target, 0);
-    }
-    else
-    {
-      const float scaling = 255.0f / (maxValue - minValue);
-      const float offset = -minValue;
-
-      Orthanc::Image rescaled(Orthanc::PixelFormat_Grayscale8, source.GetWidth(), source.GetHeight(), false);
-      Orthanc::ImageProcessing::ShiftScale(rescaled, converted, static_cast<float>(offset), static_cast<float>(scaling), false);
-      Orthanc::ImageProcessing::Convert(target, rescaled);
-    }
-  }
-
-
   OrthancPyramidFrameFetcher::OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc,
                                                          bool smooth) :
     orthanc_(orthanc),
@@ -123,105 +99,108 @@
 
     OrthancPlugins::DicomInstance dicom(buffer.GetData(), buffer.GetSize());
 
-    uint8_t backgroundRed = defaultBackgroundRed_;
-    uint8_t backgroundGreen = defaultBackgroundGreen_;
-    uint8_t backgroundBlue = defaultBackgroundBlue_;
+    Json::Value tags;
+    dicom.GetJson(tags);
+
+    Orthanc::DicomMap m;
+    m.FromDicomAsJson(tags);
 
-    Json::Value tags;
-    dicom.GetSimplifiedJson(tags);
+    Orthanc::DicomImageInformation info(m);
 
-    static const char* const PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+    uint8_t backgroundRed, backgroundGreen, backgroundBlue;
 
-    if (tags.isMember(PHOTOMETRIC_INTERPRETATION) &&
-        tags[PHOTOMETRIC_INTERPRETATION].type() == Json::stringValue)
+    if (info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome1 ||
+        info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2)
     {
-      std::string p = tags[PHOTOMETRIC_INTERPRETATION].asString();
-      if (p == "MONOCHROME1")
-      {
-        backgroundRed = 255;
-        backgroundGreen = 255;
-        backgroundBlue = 255;
-      }
-      else if (p == "MONOCHROME2")
-      {
-        backgroundRed = 0;
-        backgroundGreen = 0;
-        backgroundBlue = 0;
-      }
+      backgroundRed = 0;
+      backgroundGreen = 0;
+      backgroundBlue = 0;
+    }
+    else
+    {
+      backgroundRed = defaultBackgroundRed_;
+      backgroundGreen = defaultBackgroundGreen_;
+      backgroundBlue = defaultBackgroundBlue_;
     }
 
     std::unique_ptr<OrthancPlugins::OrthancImage> frame(dicom.GetDecodedFrame(frameNumber));
 
-    Orthanc::PixelFormat format;
-    switch (frame->GetPixelFormat())
-    {
-    case OrthancPluginPixelFormat_RGB24:
-      format = Orthanc::PixelFormat_RGB24;
-      break;
-
-    case OrthancPluginPixelFormat_Grayscale8:
-      format = Orthanc::PixelFormat_Grayscale8;
-      break;
-
-    case OrthancPluginPixelFormat_Grayscale16:
-      format = Orthanc::PixelFormat_Grayscale16;
-      break;
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    Orthanc::ImageAccessor source;
-    source.AssignReadOnly(format, frame->GetWidth(), frame->GetHeight(), frame->GetPitch(), frame->GetBuffer());
-
     unsigned int paddedWidth, paddedHeight;
 
     if (paddingX_ >= 2)
     {
-      paddedWidth = OrthancWSI::CeilingDivision(source.GetWidth(), paddingX_) * paddingX_;
+      paddedWidth = OrthancWSI::CeilingDivision(frame->GetWidth(), paddingX_) * paddingX_;
     }
     else
     {
-      paddedWidth = source.GetWidth();
+      paddedWidth = frame->GetWidth();
     }
 
     if (paddingY_ >= 2)
     {
-      paddedHeight = OrthancWSI::CeilingDivision(source.GetHeight(), paddingY_) * paddingY_;
+      paddedHeight = OrthancWSI::CeilingDivision(frame->GetHeight(), paddingY_) * paddingY_;
     }
     else
     {
-      paddedHeight = source.GetHeight();
+      paddedHeight = frame->GetHeight();
     }
 
-    std::unique_ptr<Orthanc::ImageAccessor> rendered(new Orthanc::Image(Orthanc::PixelFormat_RGB24, paddedWidth, paddedHeight, false));
+
+    Orthanc::PixelFormat sourceFormat, targetFormat;
+    switch (frame->GetPixelFormat())
+    {
+      case OrthancPluginPixelFormat_RGB24:
+        sourceFormat = Orthanc::PixelFormat_RGB24;
+        targetFormat = Orthanc::PixelFormat_RGB24;
+        break;
+
+      case OrthancPluginPixelFormat_Grayscale8:
+        sourceFormat = Orthanc::PixelFormat_Grayscale8;
+        targetFormat = Orthanc::PixelFormat_Grayscale8;
+        break;
 
-    if (paddedWidth != source.GetWidth() ||
-        paddedHeight != source.GetHeight())
+      case OrthancPluginPixelFormat_Grayscale16:
+        sourceFormat = Orthanc::PixelFormat_Grayscale16;
+        targetFormat = Orthanc::PixelFormat_Grayscale8;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    std::unique_ptr<Orthanc::ImageAccessor> rendered(new Orthanc::Image(targetFormat, paddedWidth, paddedHeight, false));
+
+    if (paddedWidth != frame->GetWidth() ||
+        paddedHeight != frame->GetHeight())
     {
       Orthanc::ImageProcessing::Set(*rendered, backgroundRed, backgroundGreen, backgroundBlue, 255 /* alpha */);
     }
 
-    Orthanc::ImageAccessor region;
-    rendered->GetRegion(region, 0, 0, source.GetWidth(), source.GetHeight());
+    Orthanc::ImageAccessor target;
+    rendered->GetRegion(target, 0, 0, frame->GetWidth(), frame->GetHeight());
+
 
-    switch (format)
+    Orthanc::ImageAccessor source;
+    source.AssignReadOnly(sourceFormat, frame->GetWidth(), frame->GetHeight(), frame->GetPitch(), frame->GetBuffer());
+
+    switch (targetFormat)
     {
-    case Orthanc::PixelFormat_RGB24:
-      Orthanc::ImageProcessing::Copy(region, source);
-      break;
+      case Orthanc::PixelFormat_RGB24:
+        Orthanc::ImageProcessing::Copy(target, source);
+        break;
 
-    case Orthanc::PixelFormat_Grayscale8:
-      Orthanc::ImageProcessing::Convert(region, source);
-      break;
+      case Orthanc::PixelFormat_Grayscale8:
+      {
+        double offset, scaling;
+        info.ComputeRenderingTransform(offset, scaling);  // Use the default windowing
+        Orthanc::ImageProcessing::ShiftScale2(target, source, static_cast<float>(offset), static_cast<float>(scaling), false);
+        break;
+      }
 
-    case Orthanc::PixelFormat_Grayscale16:
-      RenderGrayscale(region, source);
-      break;
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
 
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
 
     std::unique_ptr<DecodedTiledPyramid> result(new OnTheFlyPyramid(rendered.release(), tileWidth_, tileHeight_, smooth_));
     result->SetBackgroundColor(backgroundRed, backgroundGreen, backgroundBlue);
--- a/ViewerPlugin/OrthancPyramidFrameFetcher.h	Mon Mar 17 16:31:47 2025 +0100
+++ b/ViewerPlugin/OrthancPyramidFrameFetcher.h	Sat Apr 05 13:28:04 2025 +0200
@@ -42,9 +42,6 @@
     uint8_t                                            defaultBackgroundGreen_;
     uint8_t                                            defaultBackgroundBlue_;
 
-    static void RenderGrayscale(Orthanc::ImageAccessor& target,
-                                const Orthanc::ImageAccessor& source);
-
   public:
     OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc,
                                bool smooth);