changeset 768:55411e7da2f7

LookupTableTextureSceneLayer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 23 May 2019 20:04:33 +0200
parents dce5b067d040
children 4ba8892870a2
files Framework/Scene2D/CairoCompositor.cpp Framework/Scene2D/ColorTextureSceneLayer.h Framework/Scene2D/ISceneLayer.h Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp Framework/Scene2D/Internals/CairoColorTextureRenderer.h Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h Framework/Scene2D/LookupTableTextureSceneLayer.cpp Framework/Scene2D/LookupTableTextureSceneLayer.h Framework/Toolbox/DicomInstanceParameters.cpp Framework/Toolbox/DicomInstanceParameters.h Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/Loader.cpp
diffstat 14 files changed, 528 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/CairoCompositor.cpp	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Scene2D/CairoCompositor.cpp	Thu May 23 20:04:33 2019 +0200
@@ -24,6 +24,7 @@
 #include "Internals/CairoColorTextureRenderer.h"
 #include "Internals/CairoFloatTextureRenderer.h"
 #include "Internals/CairoInfoPanelRenderer.h"
+#include "Internals/CairoLookupTableTextureRenderer.h"
 #include "Internals/CairoPolylineRenderer.h"
 #include "Internals/CairoTextRenderer.h"
 
@@ -60,6 +61,9 @@
       case ISceneLayer::Type_FloatTexture:
         return new Internals::CairoFloatTextureRenderer(*this, layer);
 
+      case ISceneLayer::Type_LookupTableTexture:
+        return new Internals::CairoLookupTableTextureRenderer(*this, layer);
+
       case ISceneLayer::Type_Text:
       {
         const TextSceneLayer& l = dynamic_cast<const TextSceneLayer&>(layer);
--- a/Framework/Scene2D/ColorTextureSceneLayer.h	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Scene2D/ColorTextureSceneLayer.h	Thu May 23 20:04:33 2019 +0200
@@ -28,6 +28,7 @@
   class ColorTextureSceneLayer : public TextureBaseSceneLayer
   {
   public:
+    // If using RGBA32, premultiplied alpha is assumed
     ColorTextureSceneLayer(const Orthanc::ImageAccessor& texture);
 
     virtual ISceneLayer* Clone() const;
--- a/Framework/Scene2D/ISceneLayer.h	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Scene2D/ISceneLayer.h	Thu May 23 20:04:33 2019 +0200
@@ -37,7 +37,8 @@
       Type_ColorTexture,
       Type_Polyline,
       Type_Text,
-      Type_FloatTexture
+      Type_FloatTexture,
+      Type_LookupTableTexture
     };
 
     virtual ~ISceneLayer()
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp	Thu May 23 20:04:33 2019 +0200
@@ -44,13 +44,17 @@
       isLinearInterpolation_ = l.IsLinearInterpolation();
     }
 
-    
-    void CairoColorTextureRenderer::Render(const AffineTransform2D& transform)
+
+    void CairoColorTextureRenderer::RenderColorTexture(ICairoContextProvider& target,
+                                                       const AffineTransform2D& transform,
+                                                       CairoSurface& texture,
+                                                       const AffineTransform2D& textureTransform,
+                                                       bool isLinearInterpolation)
     {
-      cairo_t* cr = target_.GetCairoContext();
+      cairo_t* cr = target.GetCairoContext();
 
       AffineTransform2D t =
-        AffineTransform2D::Combine(transform, textureTransform_);
+        AffineTransform2D::Combine(transform, textureTransform);
       Matrix h = t.GetHomogeneousMatrix();
       
       cairo_save(cr);
@@ -60,9 +64,9 @@
       cairo_transform(cr, &m);
 
       cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
-      cairo_set_source_surface(cr, texture_.GetObject(), 0, 0);
+      cairo_set_source_surface(cr, texture.GetObject(), 0, 0);
 
-      if (isLinearInterpolation_)
+      if (isLinearInterpolation)
       {
         cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
       }
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.h	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.h	Thu May 23 20:04:33 2019 +0200
@@ -43,7 +43,17 @@
 
       virtual void Update(const ISceneLayer& layer);
     
-      virtual void Render(const AffineTransform2D& transform);
+      virtual void Render(const AffineTransform2D& transform)
+      {
+        RenderColorTexture(target_, transform, texture_,
+                           textureTransform_, isLinearInterpolation_);
+      }
+
+      static void RenderColorTexture(ICairoContextProvider& target,
+                                     const AffineTransform2D& transform,
+                                     CairoSurface& texture,
+                                     const AffineTransform2D& textureTransform,
+                                     bool isLinearInterpolation);
     };
   }
 }
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Thu May 23 20:04:33 2019 +0200
@@ -21,6 +21,7 @@
 
 #include "CairoFloatTextureRenderer.h"
 
+#include "CairoColorTextureRenderer.h"
 #include "../FloatTextureSceneLayer.h"
 
 namespace OrthancStone
@@ -84,33 +85,8 @@
       
     void CairoFloatTextureRenderer::Render(const AffineTransform2D& transform)
     {
-      cairo_t* cr = target_.GetCairoContext();
-
-      AffineTransform2D t =
-        AffineTransform2D::Combine(transform, textureTransform_);
-      Matrix h = t.GetHomogeneousMatrix();
-      
-      cairo_save(cr);
-
-      cairo_matrix_t m;
-      cairo_matrix_init(&m, h(0, 0), h(1, 0), h(0, 1), h(1, 1), h(0, 2), h(1, 2));
-      cairo_transform(cr, &m);
-
-      cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
-      cairo_set_source_surface(cr, texture_.GetObject(), 0, 0);
-
-      if (isLinearInterpolation_)
-      {
-        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
-      }
-      else
-      {
-        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
-      }
-
-      cairo_paint(cr);
-
-      cairo_restore(cr);
+      CairoColorTextureRenderer::RenderColorTexture(target_, transform, texture_,
+                                                    textureTransform_, isLinearInterpolation_);
     }
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp	Thu May 23 20:04:33 2019 +0200
@@ -0,0 +1,108 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CairoLookupTableTextureRenderer.h"
+
+#include "CairoColorTextureRenderer.h"
+#include "../LookupTableTextureSceneLayer.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    void CairoLookupTableTextureRenderer::Update(const ISceneLayer& layer)
+    {
+      const LookupTableTextureSceneLayer& l = dynamic_cast<const LookupTableTextureSceneLayer&>(layer);
+
+      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();
+      texture_.SetSize(width, height, true /* alpha channel is enabled */);
+
+      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;
+          }
+
+          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;
+        }
+      }
+
+      cairo_surface_mark_dirty(texture_.GetObject());
+    }
+
+    void CairoLookupTableTextureRenderer::Render(const AffineTransform2D& transform)
+    {
+      CairoColorTextureRenderer::RenderColorTexture(target_, transform, texture_,
+                                                    textureTransform_, isLinearInterpolation_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h	Thu May 23 20:04:33 2019 +0200
@@ -0,0 +1,53 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Viewport/CairoSurface.h"
+#include "CompositorHelper.h"
+#include "ICairoContextProvider.h"
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    class CairoLookupTableTextureRenderer : public CompositorHelper::ILayerRenderer
+    {
+    private:
+      ICairoContextProvider&  target_;
+      CairoSurface            texture_;
+      AffineTransform2D       textureTransform_;
+      bool                    isLinearInterpolation_;
+    
+    public:
+      CairoLookupTableTextureRenderer(ICairoContextProvider& target,
+                                      const ISceneLayer& layer) :
+        target_(target)
+      {
+        Update(layer);
+      }
+
+      virtual void Update(const ISceneLayer& layer);
+    
+      virtual void Render(const AffineTransform2D& transform);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Thu May 23 20:04:33 2019 +0200
@@ -0,0 +1,183 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LookupTableTextureSceneLayer.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  static void StringToVector(std::vector<uint8_t>& target,
+                             const std::string& source)
+  {
+    target.resize(source.size());
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      target[i] = source[i];
+    }
+  }
+
+  
+  LookupTableTextureSceneLayer::LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture)
+  {
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> t(
+        new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                           texture.GetWidth(), 
+                           texture.GetHeight(), 
+                           false));
+
+      Orthanc::ImageProcessing::Convert(*t, texture);
+      SetTexture(t.release());
+    }
+
+    SetLookupTableGrayscale(1);
+    SetRange(0, 1);
+  }
+
+
+  void LookupTableTextureSceneLayer::SetLookupTableGrayscale(float alpha)
+  {
+    std::vector<uint8_t> rgb(3 * 256);
+
+    for (size_t i = 0; i < 256; i++)
+    {
+      rgb[3 * i] = i;
+      rgb[3 * i + 1] = i;
+      rgb[3 * i + 2] = i;
+    }
+
+    SetLookupTableRgb(rgb, alpha);
+  }  
+
+
+  void LookupTableTextureSceneLayer::SetLookupTableRgb(const std::vector<uint8_t>& lut,
+                                                       float alpha)
+  {
+    if (lut.size() != 3 * 256 ||
+        alpha < 0 ||
+        alpha > 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    lut_.resize(4 * 256);
+
+    for (size_t i = 0; i < 256; i++)
+    {
+      // Premultiplied alpha
+      
+      if (i == 0)
+      {
+        // Make zero transparent
+        lut_[4 * i] = 0;        // R
+        lut_[4 * i + 1] = 0;    // G
+        lut_[4 * i + 2] = 0;    // B
+        lut_[4 * i + 3] = 0;    // A
+      }
+      else
+      {
+        float r = static_cast<float>(lut[3 * i]) * alpha;
+        float g = static_cast<float>(lut[3 * i + 1]) * alpha;
+        float b = static_cast<float>(lut[3 * i + 2]) * alpha;
+        
+        lut_[4 * i] = static_cast<uint8_t>(std::floor(r));
+        lut_[4 * i + 1] = static_cast<uint8_t>(std::floor(g));
+        lut_[4 * i + 2] = static_cast<uint8_t>(std::floor(b));
+        lut_[4 * i + 3] = static_cast<uint8_t>(std::floor(alpha * 255.0f));
+      }
+    }
+
+    IncrementRevision();
+  }
+
+
+  void LookupTableTextureSceneLayer::SetLookupTableRgb(const std::string& lut,
+                                                       float alpha)
+  {
+    std::vector<uint8_t> tmp;
+    StringToVector(tmp, lut);
+    SetLookupTableRgb(tmp, alpha);
+  }
+
+  
+  void LookupTableTextureSceneLayer::SetLookupTable(const std::vector<uint8_t>& lut)
+  {
+    if (lut.size() != 4 * 256)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    lut_ = lut;
+
+    IncrementRevision();
+  }
+
+
+  void LookupTableTextureSceneLayer::SetLookupTable(const std::string& lut)
+  {
+    std::vector<uint8_t> tmp;
+    StringToVector(tmp, lut);
+    SetLookupTable(tmp);
+  }
+
+  
+  void LookupTableTextureSceneLayer::SetRange(float minValue,
+                                              float maxValue)
+  {
+    if (minValue > maxValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      minValue_ = minValue;
+      maxValue_ = maxValue;
+      IncrementRevision();
+    }
+  }
+    
+
+  void LookupTableTextureSceneLayer::FitRange()
+  {
+    Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture());
+    assert(minValue_ <= maxValue_);
+    
+    IncrementRevision();
+  }
+
+    
+  ISceneLayer* LookupTableTextureSceneLayer::Clone() const
+  {
+    std::auto_ptr<LookupTableTextureSceneLayer> cloned
+      (new LookupTableTextureSceneLayer(GetTexture()));
+
+    cloned->CopyParameters(*this);
+    cloned->minValue_ = minValue_;
+    cloned->maxValue_ = maxValue_;
+    cloned->lut_ = lut_;
+
+    return cloned.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h	Thu May 23 20:04:33 2019 +0200
@@ -0,0 +1,79 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "TextureBaseSceneLayer.h"
+
+namespace OrthancStone
+{
+  class LookupTableTextureSceneLayer : public TextureBaseSceneLayer
+  {
+  private:
+    ImageWindowing        windowing_;
+    float                 minValue_;
+    float                 maxValue_;
+    std::vector<uint8_t>  lut_;
+
+  public:
+    // The pixel format must be "Flot32"
+    LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture);
+
+    void SetLookupTableGrayscale(float alpha);
+    
+    void SetLookupTableRgb(const std::vector<uint8_t>& lut,
+                           float alpha);
+
+    void SetLookupTableRgb(const std::string& lut,
+                           float alpha);
+
+    void SetLookupTable(const std::vector<uint8_t>& lut);
+
+    void SetLookupTable(const std::string& lut);
+
+    void SetRange(float minValue,
+                  float maxValue);
+    
+    void FitRange();
+
+    float GetMinValue() const
+    {
+      return minValue_;
+    }
+
+    float GetMaxValue() const
+    {
+      return maxValue_;
+    }
+
+    const std::vector<uint8_t>& GetLookupTable() const
+    {
+      return lut_;
+    }
+
+    virtual ISceneLayer* Clone() const;
+
+    virtual Type GetType() const
+    {
+      return Type_LookupTableTexture;
+    }
+  };
+}
--- a/Framework/Toolbox/DicomInstanceParameters.cpp	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Toolbox/DicomInstanceParameters.cpp	Thu May 23 20:04:33 2019 +0200
@@ -320,7 +320,24 @@
   }
 
 
-  TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture(const Orthanc::ImageAccessor& pixelData) const
+  Orthanc::ImageAccessor* DicomInstanceParameters::ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const
+  {
+    std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                               pixelData.GetWidth(), 
+                                                               pixelData.GetHeight(),
+                                                               false));
+    Orthanc::ImageProcessing::Convert(*converted, pixelData);
+
+    // Correct rescale slope/intercept if need be
+    data_.ApplyRescale(*converted, (pixelData.GetFormat() == Orthanc::PixelFormat_Grayscale32));
+
+    return converted.release();
+  }
+    
+
+
+  TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture
+  (const Orthanc::ImageAccessor& pixelData) const
   {
     assert(sizeof(float) == 4);
 
@@ -338,26 +355,16 @@
     }
     else
     {
-      if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
-          sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
-          sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
+      // This is the case of a grayscale frame. Convert it to Float32.
+      std::auto_ptr<FloatTextureSceneLayer> texture;
 
-      std::auto_ptr<FloatTextureSceneLayer> texture;
-        
+      if (pixelData.GetFormat() == Orthanc::PixelFormat_Float32)
       {
-        // This is the case of a grayscale frame. Convert it to Float32.
-        std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
-                                                                   pixelData.GetWidth(), 
-                                                                   pixelData.GetHeight(),
-                                                                   false));
-        Orthanc::ImageProcessing::Convert(*converted, pixelData);
-
-        // Correct rescale slope/intercept if need be
-        data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
-
+        texture.reset(new FloatTextureSceneLayer(pixelData));
+      }
+      else
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> converted(ConvertToFloat(pixelData));
         texture.reset(new FloatTextureSceneLayer(*converted));
       }
 
@@ -370,4 +377,21 @@
       return texture.release();
     }
   }
+
+
+  LookupTableTextureSceneLayer* DicomInstanceParameters::CreateLookupTableTexture
+  (const Orthanc::ImageAccessor& pixelData) const
+  {
+    std::auto_ptr<FloatTextureSceneLayer> texture;
+
+    if (pixelData.GetFormat() == Orthanc::PixelFormat_Float32)
+    {
+      return new LookupTableTextureSceneLayer(pixelData);
+    }
+    else
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> converted(ConvertToFloat(pixelData));
+      return new LookupTableTextureSceneLayer(*converted);
+    }
+  }
 }
--- a/Framework/Toolbox/DicomInstanceParameters.h	Thu May 23 16:57:33 2019 +0200
+++ b/Framework/Toolbox/DicomInstanceParameters.h	Thu May 23 20:04:33 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../StoneEnumerations.h"
-#include "../Scene2D/TextureBaseSceneLayer.h"
+#include "../Scene2D/LookupTableTextureSceneLayer.h"
 #include "../Toolbox/CoordinateSystem3D.h"
 
 #include <Core/IDynamicObject.h>
@@ -72,6 +72,9 @@
     };
 
     
+    Orthanc::ImageAccessor* ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const;
+    
+
     Data  data_;
 
 
@@ -181,5 +184,7 @@
     }
 
     TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& pixelData) const;
+
+    LookupTableTextureSceneLayer* CreateLookupTableTexture(const Orthanc::ImageAccessor& pixelData) const;
   };
 }
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Thu May 23 16:57:33 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Thu May 23 20:04:33 2019 +0200
@@ -380,10 +380,12 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/FixedPointAligner.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableTextureSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp
--- a/Samples/Sdl/Loader.cpp	Thu May 23 16:57:33 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Thu May 23 20:04:33 2019 +0200
@@ -33,6 +33,7 @@
 #include "../../Framework/Scene2D/CairoCompositor.h"
 #include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/Scene2D/PolylineSceneLayer.h"
+#include "../../Framework/Scene2D/LookupTableTextureSceneLayer.h"
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Toolbox/GeometryToolbox.h"
 #include "../../Framework/Toolbox/SlicesSorter.h"
@@ -50,6 +51,9 @@
 #include <Core/Toolbox.h>
 
 
+#include <EmbeddedResources.h>
+
+
 namespace OrthancStone
 {
   class IVolumeSlicer : public boost::noncopyable
@@ -174,7 +178,25 @@
       {
         const DicomInstanceParameters& parameters = GetDicomParameters(projection_, sliceIndex_);
         ImageBuffer3D::SliceReader reader(image_, projection_, sliceIndex_);
-        texture.reset(parameters.CreateTexture(reader.GetAccessor()));
+
+        static unsigned int i = 1;
+        
+        if (i % 2)
+        {
+          texture.reset(parameters.CreateTexture(reader.GetAccessor()));
+        }
+        else
+        {
+          std::string lut;
+          Orthanc::EmbeddedResources::GetFileResource(lut, Orthanc::EmbeddedResources::COLORMAP_HOT);
+          
+          std::auto_ptr<LookupTableTextureSceneLayer> tmp(parameters.CreateLookupTableTexture(reader.GetAccessor()));
+          tmp->FitRange();
+          tmp->SetLookupTableRgb(lut, 1);
+          texture.reset(tmp.release());
+        }
+
+        i++;
       }
 
       const CoordinateSystem3D& system = geometry_.GetProjectionGeometry(projection_);