changeset 1924:f4050908c6bc

display of overlays
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 23 Mar 2022 12:24:35 +0100
parents f4cdcba8c32a
children a7a77488ddb1
files Applications/StoneWebViewer/NEWS Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp OrthancStone/Sources/Toolbox/DicomInstanceParameters.h
diffstat 4 files changed, 168 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/NEWS	Tue Mar 22 17:39:19 2022 +0100
+++ b/Applications/StoneWebViewer/NEWS	Wed Mar 23 12:24:35 2022 +0100
@@ -1,6 +1,7 @@
 Pending changes in the mainline
 ===============================
 
+* Display of overlays
 * SeriesList: 
   - display the SeriesNumber tag in front of image count.
   - order series by SeriesNumber
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Mar 22 17:39:19 2022 +0100
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Wed Mar 23 12:24:35 2022 +0100
@@ -1462,6 +1462,86 @@
 
 
 
+class OverlaysRegistry : public boost::noncopyable
+{
+private:
+  typedef std::map<std::string, OrthancStone::LookupTableTextureSceneLayer*>  Content;
+
+  Content  content_;
+
+public:
+  ~OverlaysRegistry()
+  {
+    for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+  static OverlaysRegistry& GetInstance()
+  {
+    static OverlaysRegistry singleton;
+    return singleton;
+  }
+
+  void Register(const std::string& sopInstanceUid,
+                const OrthancStone::DicomInstanceParameters& parameters,
+                int overlayX,
+                int overlayY,
+                const Orthanc::ImageAccessor& overlay)
+  {
+    // Don't register twice the same overlay
+    Content::iterator found = content_.find(sopInstanceUid);
+    if (found == content_.end())
+    {
+      content_[sopInstanceUid] = parameters.CreateOverlayTexture(overlayX, overlayY, overlay);
+    }
+  }
+
+  class Accessor : public boost::noncopyable
+  {
+  private:
+    const OrthancStone::LookupTableTextureSceneLayer* texture_;
+
+  public:
+    Accessor(const OverlaysRegistry& registry,
+             const std::string& sopInstanceUid)
+    {
+      Content::const_iterator found = registry.content_.find(sopInstanceUid);
+      if (found == registry.content_.end())
+      {
+        texture_ = NULL;
+      }
+      else
+      {
+        assert(found->second != NULL);
+        texture_ = found->second;
+      }
+    }
+
+    bool IsValid() const
+    {
+      return texture_ != NULL;
+    }
+
+    OrthancStone::LookupTableTextureSceneLayer* CreateTexture() const
+    {
+      if (texture_ == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return dynamic_cast<OrthancStone::LookupTableTextureSceneLayer*>(texture_->Clone());
+      }
+    }
+  };
+};
+
+
+
+
 class ViewerViewport : public OrthancStone::ObserverBase<ViewerViewport>
 {
 public:
@@ -1505,9 +1585,10 @@
 
 private:
   static const int LAYER_TEXTURE = 0;
-  static const int LAYER_REFERENCE_LINES = 1;
-  static const int LAYER_ANNOTATIONS_OSIRIX = 2;
-  static const int LAYER_ANNOTATIONS_STONE = 3;
+  static const int LAYER_OVERLAY = 1;
+  static const int LAYER_REFERENCE_LINES = 2;
+  static const int LAYER_ANNOTATIONS_OSIRIX = 3;
+  static const int LAYER_ANNOTATIONS_STONE = 4;
 
   
   class ICommand : public Orthanc::IDynamicObject
@@ -1823,6 +1904,8 @@
       Orthanc::DicomMap tags;
       dicom.ExtractDicomSummary(tags, ORTHANC_STONE_MAX_TAG_LENGTH);
 
+      OrthancStone::DicomInstanceParameters parameters(tags);
+
       std::unique_ptr<Orthanc::ImageAccessor> converted;
       
       if (frameProtection->GetFormat() == Orthanc::PixelFormat_RGB24)
@@ -1831,25 +1914,28 @@
       }
       else
       {
-        double a = 1;
-        double b = 0;
-
-        double doseScaling;
-        if (tags.ParseDouble(doseScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
-        {
-          a = doseScaling;
-        }
-      
-        double rescaleIntercept, rescaleSlope;
-        dicom.GetRescale(rescaleIntercept, rescaleSlope, frameNumber);
-        a *= rescaleSlope;
-        b = rescaleIntercept;
-
         converted.reset(new Orthanc::Image(Orthanc::PixelFormat_Float32, frameProtection->GetWidth(), frameProtection->GetHeight(), false));
         Orthanc::ImageProcessing::Convert(*converted, *frameProtection);
-        Orthanc::ImageProcessing::ShiftScale2(*converted, b, a, false);        
+        parameters.ApplyRescaleAndDoseScaling(*converted, false /* don't use double */);
       }
 
+      try
+      {
+        int x, y;
+        std::unique_ptr<Orthanc::ImageAccessor> overlay(dicom.DecodeAllOverlays(x, y));
+
+        if (overlay.get() != NULL &&
+            overlay->GetWidth() > 0 &&
+            overlay->GetHeight() > 0)
+        {
+          OverlaysRegistry::GetInstance().Register(sopInstanceUid, parameters, x, y, *overlay);
+        }
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Cannot decode overlays from instance " << sopInstanceUid;
+      }
+      
       assert(converted.get() != NULL);
       viewport.RenderCurrentSceneFromCommand(*converted, sopInstanceUid, frameNumber, DisplayedFrameQuality_High);
       viewport.framesCache_->Acquire(sopInstanceUid, frameNumber, converted.release(), QUALITY_FULL);
@@ -2107,6 +2193,18 @@
       layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY);
     }
 
+    std::unique_ptr<OrthancStone::LookupTableTextureSceneLayer> overlay;
+
+    {
+      OverlaysRegistry::Accessor accessor(OverlaysRegistry::GetInstance(), instance.GetSopInstanceUid());
+      if (accessor.IsValid())
+      {
+        overlay.reset(accessor.CreateTexture());
+        overlay->SetFlipX(flipX_);
+        overlay->SetFlipY(flipY_);
+      }
+    }
+
     std::unique_ptr<OrthancStone::MacroSceneLayer>  annotationsOsiriX;
 
     if (osiriXAnnotations_)
@@ -2139,6 +2237,15 @@
 
       scene.SetLayer(LAYER_TEXTURE, layer.release());
 
+      if (overlay.get() != NULL)
+      {
+        scene.SetLayer(LAYER_OVERLAY, overlay.release());
+      }
+      else
+      {
+        scene.DeleteLayer(LAYER_OVERLAY);
+      }
+
       if (annotationsOsiriX.get() != NULL)
       {
         scene.SetLayer(LAYER_ANNOTATIONS_OSIRIX, annotationsOsiriX.release());
@@ -2339,6 +2446,15 @@
         layer.SetFlipX(flipX_);
         layer.SetFlipY(flipY_);
       }
+
+      {
+        OrthancStone::TextureBaseSceneLayer& layer = 
+          dynamic_cast<OrthancStone::TextureBaseSceneLayer&>(
+            lock->GetController().GetScene().GetLayer(LAYER_OVERLAY));
+
+        layer.SetFlipX(flipX_);
+        layer.SetFlipY(flipY_);
+      }
         
       lock->Invalidate();
     }
--- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp	Tue Mar 22 17:39:19 2022 +0100
+++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp	Wed Mar 23 12:24:35 2022 +0100
@@ -355,45 +355,18 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
     }
 
-    double factor = data_.doseGridScaling_;
+    double scaling = data_.doseGridScaling_;
     double offset = 0.0;
 
     if (data_.hasRescale_)
     {
-      factor *= data_.rescaleSlope_;
+      scaling *= data_.rescaleSlope_;
       offset = data_.rescaleIntercept_;
     }
 
-    if (!LinearAlgebra::IsNear(factor, 1) ||
-        !LinearAlgebra::IsNear(offset, 0))
-    {
-      const unsigned int width = image.GetWidth();
-      const unsigned int height = image.GetHeight();
-        
-      for (unsigned int y = 0; y < height; y++)
-      {
-        float* p = reinterpret_cast<float*>(image.GetRow(y));
-
-        if (useDouble)
-        {
-          // Slower, accurate implementation using double
-          for (unsigned int x = 0; x < width; x++, p++)
-          {
-            double value = static_cast<double>(*p);
-            *p = static_cast<float>(value * factor + offset);
-          }
-        }
-        else
-        {
-          // Fast, approximate implementation using float
-          for (unsigned int x = 0; x < width; x++, p++)
-          {
-            *p = (*p) * static_cast<float>(factor) + static_cast<float>(offset);
-          }
-        }
-      }
-    }
+    Orthanc::ImageProcessing::ShiftScale2(image, offset, scaling, false);
   }
+  
 
   double DicomInstanceParameters::GetRescaleIntercept() const
   {
@@ -686,6 +659,29 @@
     texture->SetOrigin(static_cast<double>(originX - 1) * texture->GetPixelSpacingX(),
                        static_cast<double>(originY - 1) * texture->GetPixelSpacingY());
 
+    std::vector<uint8_t> lut(4 * 256);
+    for (size_t i = 0; i < 256; i++)
+    {
+      if (i < 127)
+      {
+        // Black pixels are converted to transparent pixels
+        lut[4 * i] = 0;
+        lut[4 * i + 1] = 0;
+        lut[4 * i + 2] = 0;
+        lut[4 * i + 3] = 0;  // alpha
+      }
+      else
+      {
+        // White pixels are converted to opaque white
+        lut[4 * i] = 255;
+        lut[4 * i + 1] = 255;
+        lut[4 * i + 2] = 255;
+        lut[4 * i + 3] = 255;  // alpha
+      }
+    }
+
+    texture->SetLookupTable(lut);
+    
     return texture.release();
   }
 
@@ -706,16 +702,16 @@
 
   double DicomInstanceParameters::ApplyRescale(double value) const
   {
-    double factor = data_.doseGridScaling_;
+    double scaling = data_.doseGridScaling_;
     double offset = 0.0;
 
     if (data_.hasRescale_)
     {
-      factor *= data_.rescaleSlope_;
+      scaling *= data_.rescaleSlope_;
       offset = data_.rescaleIntercept_;
     }
 
-    return (value * factor + offset);
+    return (value * scaling + offset);
   }
 
 
--- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h	Tue Mar 22 17:39:19 2022 +0100
+++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h	Wed Mar 23 12:24:35 2022 +0100
@@ -75,9 +75,6 @@
     std::unique_ptr<Orthanc::DicomMap>               tags_;
     std::unique_ptr<Orthanc::DicomImageInformation>  imageInformation_;  // Lazy evaluation
 
-    void ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image,
-                                    bool useDouble) const;
-
   public:
     explicit DicomInstanceParameters(const DicomInstanceParameters& other) :
       data_(other.data_),
@@ -231,6 +228,9 @@
       return data_.doseGridScaling_;
     }
 
+    void ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image,
+                                    bool useDouble) const;
+
     double ApplyRescale(double value) const;
 
     // Required for RT-DOSE