changeset 1179:177e7d431cd1 broker

log scale in textures, remove redundant code for LUTs
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 Nov 2019 15:24:20 +0100
parents 3c7cdbf32e2a
children 9c8f557ea799
files Framework/Scene2D/FloatTextureSceneLayer.cpp Framework/Scene2D/GrayscaleStyleConfigurator.cpp Framework/Scene2D/GrayscaleStyleConfigurator.h Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Framework/Scene2D/LookupTableStyleConfigurator.cpp Framework/Scene2D/LookupTableStyleConfigurator.h Framework/Scene2D/LookupTableTextureSceneLayer.cpp Framework/Scene2D/LookupTableTextureSceneLayer.h
diffstat 11 files changed, 217 insertions(+), 143 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/FloatTextureSceneLayer.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -131,6 +131,7 @@
     cloned->customCenter_ = customCenter_;
     cloned->customWidth_ = customWidth_;
     cloned->inverted_ = inverted_;
+    cloned->applyLog_ = applyLog_;
 
     return cloned.release();
   }
--- a/Framework/Scene2D/GrayscaleStyleConfigurator.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -27,6 +27,17 @@
 
 namespace OrthancStone
 {
+  GrayscaleStyleConfigurator::GrayscaleStyleConfigurator() :
+    revision_(0),
+    linearInterpolation_(false),
+    hasWindowing_(false),
+    customWindowWidth_(0),
+    customWindowCenter_(0),
+    inverted_(false),
+    applyLog_(false)
+  {
+  }
+
   void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing)
   {
     hasWindowing_ = true;
--- a/Framework/Scene2D/GrayscaleStyleConfigurator.h	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h	Wed Nov 20 15:24:20 2019 +0100
@@ -42,16 +42,7 @@
     bool            applyLog_;
     
   public:
-    GrayscaleStyleConfigurator() :
-      revision_(0),
-      linearInterpolation_(false),
-      hasWindowing_(false),
-      customWindowWidth_(0),
-      customWindowCenter_(0),
-      inverted_(false),
-      applyLog_(false)
-    {
-    }
+    GrayscaleStyleConfigurator();
 
     void SetWindowing(ImageWindowing windowing);
 
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -53,6 +53,8 @@
              target.GetFormat() == Orthanc::PixelFormat_BGRA32 &&
              sizeof(float) == 4);
 
+      static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f);
+      
       for (unsigned int y = 0; y < height; y++)
       {
         const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
@@ -70,6 +72,14 @@
             v = 255;
           }
 
+          if (l.IsApplyLog())
+          {
+            // https://theailearner.com/2019/01/01/log-transformation/
+            v = LOG_NORMALIZATION * log(1.0f + static_cast<float>(v));
+          }
+
+          assert(v >= 0.0f && v <= 255.0f);
+
           uint8_t vv = static_cast<uint8_t>(v);
 
           if (l.IsInverted())
--- a/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -37,18 +37,6 @@
       textureTransform_ = l.GetTransform();
       isLinearInterpolation_ = l.IsLinearInterpolation();
 
-      const float a = l.GetMinValue();
-      float slope;
-
-      if (l.GetMinValue() >= l.GetMaxValue())
-      {
-        slope = 0;
-      }
-      else
-      {
-        slope = 256.0f / (l.GetMaxValue() - l.GetMinValue());
-      }
-
       const Orthanc::ImageAccessor& source = l.GetTexture();
       const unsigned int width = source.GetWidth();
       const unsigned int height = source.GetHeight();
@@ -56,60 +44,8 @@
 
       Orthanc::ImageAccessor target;
       texture_.GetWriteableAccessor(target);
-
-      const std::vector<uint8_t>& lut = l.GetLookupTable();
-      if (lut.size() != 4 * 256)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      assert(source.GetFormat() == Orthanc::PixelFormat_Float32 &&
-             target.GetFormat() == Orthanc::PixelFormat_BGRA32 &&
-             sizeof(float) == 4);
-
-      for (unsigned int y = 0; y < height; y++)
-      {
-        const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-
-        for (unsigned int x = 0; x < width; x++)
-        {
-          float v = (*p - a) * slope;
-          if (v <= 0)
-          {
-            v = 0;
-          }
-          else if (v >= 255)
-          {
-            v = 255;
-          }
-
-          if (1) //l.IsApplyLog())
-          {
-            // https://theailearner.com/2019/01/01/log-transformation/
-            v = 255.0f / log(1.0f + 255.0f * 1.5f) * log(1.0f + static_cast<float>(v));
-            if (v <= 0)
-            {
-              v = 0;
-            }
-            else if (v >= 255)
-            {
-              v = 255;
-            }
-          }
-
-          uint8_t vv = static_cast<uint8_t>(v);
-
-          q[0] = lut[4 * vv + 2];  // B
-          q[1] = lut[4 * vv + 1];  // G
-          q[2] = lut[4 * vv + 0];  // R
-          q[3] = lut[4 * vv + 3];  // A
-
-          p++;
-          q += 4;
-        }
-      }
-
+      l.Render(target);
+      
       cairo_surface_mark_dirty(texture_.GetObject());
     }
 
--- a/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -21,6 +21,8 @@
 
 #include "OpenGLFloatTextureRenderer.h"
 
+#include <Core/OrthancException.h>
+
 namespace OrthancStone
 {
   namespace Internals
@@ -32,6 +34,11 @@
       {
         if (loadTexture)
         {
+          if (layer.IsApplyLog())
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+          
           context_.MakeCurrent();
           texture_.reset(new OpenGLFloatTextureProgram::Data(
             context_, layer.GetTexture(), layer.IsLinearInterpolation()));
--- a/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -36,74 +36,14 @@
         const unsigned int width = source.GetWidth();
         const unsigned int height = source.GetHeight();
 
-        if ((texture_.get() == NULL) ||
-          (texture_->GetWidth() != width) ||
-          (texture_->GetHeight() != height))
+        if (texture_.get() == NULL ||
+            texture_->GetWidth() != width ||
+            texture_->GetHeight() != height)
         {
-
-          texture_.reset(new Orthanc::Image(
-            Orthanc::PixelFormat_RGBA32,
-            width,
-            height,
-            false));
+          texture_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, false));
         }
 
-        {
-
-          const float a = layer.GetMinValue();
-          float slope = 0;
-
-          if (layer.GetMinValue() >= layer.GetMaxValue())
-          {
-            slope = 0;
-          }
-          else
-          {
-            slope = 256.0f / (layer.GetMaxValue() - layer.GetMinValue());
-          }
-
-          Orthanc::ImageAccessor target;
-          texture_->GetWriteableAccessor(target);
-
-          const std::vector<uint8_t>& lut = layer.GetLookupTable();
-          if (lut.size() != 4 * 256)
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-
-          assert(source.GetFormat() == Orthanc::PixelFormat_Float32 &&
-            target.GetFormat() == Orthanc::PixelFormat_RGBA32 &&
-            sizeof(float) == 4);
-
-          for (unsigned int y = 0; y < height; y++)
-          {
-            const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
-            uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-
-            for (unsigned int x = 0; x < width; x++)
-            {
-              float v = (*p - a) * slope;
-              if (v <= 0)
-              {
-                v = 0;
-              }
-              else if (v >= 255)
-              {
-                v = 255;
-              }
-
-              uint8_t vv = static_cast<uint8_t>(v);
-
-              q[0] = lut[4 * vv + 0];  // R
-              q[1] = lut[4 * vv + 1];  // G
-              q[2] = lut[4 * vv + 2];  // B
-              q[3] = lut[4 * vv + 3];  // A
-
-              p++;
-              q += 4;
-            }
-          }
-        }
+        layer.Render(*texture_);
 
         context_.MakeCurrent();
         glTexture_.reset(new OpenGL::OpenGLTexture(context_));
--- a/Framework/Scene2D/LookupTableStyleConfigurator.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -39,7 +39,8 @@
   LookupTableStyleConfigurator::LookupTableStyleConfigurator() :
     revision_(0),
     hasLut_(false),
-    hasRange_(false)
+    hasRange_(false),
+    applyLog_(false)
   {
   }
 
@@ -82,6 +83,12 @@
     }
   }
 
+  void LookupTableStyleConfigurator::SetApplyLog(bool apply)
+  {
+    applyLog_ = apply;
+    revision_++;
+  }
+
   TextureBaseSceneLayer* LookupTableStyleConfigurator::CreateTextureFromImage(const Orthanc::ImageAccessor& image) const
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
@@ -104,5 +111,7 @@
     {
       l.FitRange();
     }
+
+    l.SetApplyLog(applyLog_);
   }
 }
--- a/Framework/Scene2D/LookupTableStyleConfigurator.h	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/LookupTableStyleConfigurator.h	Wed Nov 20 15:24:20 2019 +0100
@@ -39,6 +39,7 @@
     bool                  hasRange_;
     float                 minValue_;
     float                 maxValue_;
+    bool                  applyLog_;
 
   public:
     LookupTableStyleConfigurator();
@@ -55,6 +56,13 @@
 
     void SetRange(float minValue, float maxValue);
 
+    void SetApplyLog(bool apply);
+
+    bool IsApplyLog() const
+    {
+      return applyLog_;
+    }
+    
     virtual uint64_t GetRevision() const
     {
       return revision_;
@@ -63,7 +71,7 @@
     virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const;
 
     virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
-      const DicomInstanceParameters& parameters) const
+                                                          const DicomInstanceParameters& parameters) const
     {
       return parameters.CreateLookupTableTexture(frame);
     }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Wed Nov 20 15:24:20 2019 +0100
@@ -132,6 +132,12 @@
     }
   }
 
+  void LookupTableTextureSceneLayer::SetApplyLog(bool apply)
+  {
+    applyLog_ = apply;
+    IncrementRevision();
+  }
+
   void LookupTableTextureSceneLayer::FitRange()
   {
     Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture());
@@ -158,4 +164,147 @@
 
     return cloned.release();
   }
+
+
+  // Templatized function to speed up computations, by avoiding
+  // testing conditions on each pixel
+  template <bool IsApplyLog,
+            Orthanc::PixelFormat TargetFormat>
+  static void RenderInternal(Orthanc::ImageAccessor& target,
+                             const Orthanc::ImageAccessor& source,
+                             float minValue,
+                             float slope,
+                             const std::vector<uint8_t>& lut)
+  {
+    static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f);
+
+    const unsigned int width = source.GetWidth();
+    const unsigned int height = source.GetHeight();
+    
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
+      uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++)
+      {
+        float v = (*p - minValue) * slope;
+        if (v <= 0)
+        {
+          v = 0;
+        }
+        else if (v >= 255)
+        {
+          v = 255;
+        }
+
+        if (IsApplyLog)
+        {
+          // https://theailearner.com/2019/01/01/log-transformation/
+          v = LOG_NORMALIZATION * log(1.0f + static_cast<float>(v));
+        }
+
+        assert(v >= 0.0f && v <= 255.0f);
+
+        uint8_t vv = static_cast<uint8_t>(v);
+
+        switch (TargetFormat)
+        {
+          case Orthanc::PixelFormat_BGRA32:
+            // For Cairo surfaces
+            q[0] = lut[4 * vv + 2];  // B
+            q[1] = lut[4 * vv + 1];  // G
+            q[2] = lut[4 * vv + 0];  // R
+            q[3] = lut[4 * vv + 3];  // A
+            break;
+
+          case Orthanc::PixelFormat_RGBA32:
+            // For OpenGL
+            q[0] = lut[4 * vv + 0];  // R
+            q[1] = lut[4 * vv + 1];  // G
+            q[2] = lut[4 * vv + 2];  // B
+            q[3] = lut[4 * vv + 3];  // A
+            break;
+
+          default:
+            assert(0);
+        }
+            
+        p++;
+        q += 4;
+      }
+    }
+  }
+  
+
+  void LookupTableTextureSceneLayer::Render(Orthanc::ImageAccessor& target) const
+  {
+    assert(sizeof(float) == 4);
+
+    if (!HasTexture())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    const Orthanc::ImageAccessor& source = GetTexture();
+    
+    if (source.GetFormat() != Orthanc::PixelFormat_Float32 ||
+        (target.GetFormat() != Orthanc::PixelFormat_RGBA32 &&
+         target.GetFormat() != Orthanc::PixelFormat_BGRA32))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (source.GetWidth() != target.GetWidth() ||
+        source.GetHeight() != target.GetHeight())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+
+    const float minValue = GetMinValue();
+    float slope;
+
+    if (GetMinValue() >= GetMaxValue())
+    {
+      slope = 0;
+    }
+    else
+    {
+      slope = 256.0f / (GetMaxValue() - GetMinValue());
+    }
+
+    const std::vector<uint8_t>& lut = GetLookupTable();
+    if (lut.size() != 4 * 256)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    switch (target.GetFormat())
+    {
+      case Orthanc::PixelFormat_RGBA32:
+        if (applyLog_)
+        {
+          RenderInternal<true, Orthanc::PixelFormat_RGBA32>(target, source, minValue, slope, lut);
+        }
+        else
+        {
+          RenderInternal<false, Orthanc::PixelFormat_RGBA32>(target, source, minValue, slope, lut);
+        }
+        break;
+
+      case Orthanc::PixelFormat_BGRA32:
+        if (applyLog_)
+        {
+          RenderInternal<true, Orthanc::PixelFormat_BGRA32>(target, source, minValue, slope, lut);
+        }
+        else
+        {
+          RenderInternal<false, Orthanc::PixelFormat_BGRA32>(target, source, minValue, slope, lut);
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
 }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.h	Wed Nov 20 14:12:11 2019 +0100
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h	Wed Nov 20 15:24:20 2019 +0100
@@ -32,6 +32,7 @@
     float                 minValue_;
     float                 maxValue_;
     std::vector<uint8_t>  lut_;
+    bool                  applyLog_;
 
     void SetLookupTableRgb(const std::vector<uint8_t>& lut);
 
@@ -66,11 +67,22 @@
       return lut_;
     }
 
+    void SetApplyLog(bool apply);
+
+    bool IsApplyLog() const
+    {
+      return applyLog_;
+    }
+
     virtual ISceneLayer* Clone() const;
 
     virtual Type GetType() const
     {
       return Type_LookupTableTexture;
     }
+
+    // Render the texture to a color image of format BGRA32 (Cairo
+    // surfaces) or RGBA32 (OpenGL)
+    void Render(Orthanc::ImageAccessor& target) const;
   };
 }