changeset 1200:54cbffabdc45 broker

integration mainline->broker
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 29 Nov 2019 11:03:41 +0100
parents 4cc997207d8a (current diff) 922d2e61aa5d (diff)
children f3bb9a6dd949
files Applications/Samples/SingleFrameEditorApplication.h Applications/Sdl/SdlEngine.cpp Framework/Radiography/RadiographyAlphaLayer.h Framework/Radiography/RadiographyDicomLayer.cpp Framework/Radiography/RadiographyDicomLayer.h Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyMaskLayer.h Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/Radiography/RadiographyTextLayer.h Framework/Radiography/RadiographyWidget.cpp Framework/Radiography/RadiographyWidget.h Framework/StoneEnumerations.h
diffstat 18 files changed, 259 insertions(+), 186 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Qt/QCairoWidget.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Applications/Qt/QCairoWidget.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -185,6 +185,16 @@
       break;
     }
   }
+  else if (keyChar == 127)
+  {
+    switch (event->key())
+    {
+      CASE_QT_KEY_TO_ORTHANC(Qt::Key_Delete, KeyboardKeys_Delete);
+      CASE_QT_KEY_TO_ORTHANC(Qt::Key_Backspace, KeyboardKeys_Backspace);
+    default:
+      break;
+    }
+  }
 
   {
     OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_);    
--- a/Applications/Samples/SingleFrameEditorApplication.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Fri Nov 29 11:03:41 2019 +0100
@@ -425,7 +425,6 @@
     private:
       boost::shared_ptr<RadiographyScene>   scene_;
       RadiographyEditorInteractor           interactor_;
-      Orthanc::FontRegistry                 fontRegistry_;
       RadiographyMaskLayer*                 maskLayer_;
 
     public:
@@ -480,8 +479,6 @@
         std::string instance = parameters["instance"].as<std::string>();
         //int frame = parameters["frame"].as<unsigned int>();
 
-        fontRegistry_.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
-        
         scene_.reset(new RadiographyScene);
         
         RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(*context->GetOrthancApiClient(), instance, 0, false, NULL);
@@ -507,10 +504,11 @@
           std::auto_ptr<Orthanc::ImageAccessor> renderedTextAlpha(TextRenderer::Render(Orthanc::EmbeddedResources::UBUNTU_FONT, 100,
                                                                                     "%öÇaA&#"));
           RadiographyLayer& layer = scene_->LoadAlphaBitmap(renderedTextAlpha.release(), NULL);
-          dynamic_cast<RadiographyAlphaLayer&>(layer).SetForegroundValue(200);
+          dynamic_cast<RadiographyAlphaLayer&>(layer).SetForegroundValue(200.0f * 256.0f);
         }
 
         {
+          RadiographyTextLayer::SetFont(Orthanc::EmbeddedResources::UBUNTU_FONT);
           RadiographyLayer& layer = scene_->LoadText("Hello\nworld", 20, 128, NULL);
           layer.SetResizeable(true);
         }
--- a/Applications/Sdl/SdlEngine.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Applications/Sdl/SdlEngine.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -255,6 +255,10 @@
           case SDLK_KP_MINUS:
             locker.GetCentralViewport().KeyPressed(KeyboardKeys_Generic, '-', modifiers);  break;
 
+          case SDLK_DELETE:
+            locker.GetCentralViewport().KeyPressed(KeyboardKeys_Delete, 0, modifiers);  break;
+          case SDLK_BACKSPACE:
+            locker.GetCentralViewport().KeyPressed(KeyboardKeys_Backspace, 0, modifiers);  break;
           case SDLK_RIGHT:
             locker.GetCentralViewport().KeyPressed(KeyboardKeys_Right, 0, modifiers);  break;
           case SDLK_LEFT:
--- a/Framework/Radiography/RadiographyAlphaLayer.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyAlphaLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -25,6 +25,7 @@
 
 #include <Core/Images/Image.h>
 #include <Core/OrthancException.h>
+#include "../Toolbox/ImageGeometry.h"
 
 namespace OrthancStone
 {
@@ -51,7 +52,10 @@
 
   void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer,
                                      const AffineTransform2D& viewTransform,
-                                     ImageInterpolation interpolation) const
+                                     ImageInterpolation interpolation,
+                                     float windowCenter,
+                                     float windowWidth,
+                                     bool applyWindowing) const
   {
     if (alpha_.get() == NULL)
     {
@@ -77,27 +81,37 @@
 
     t.Apply(tmp, cropped, interpolation, true /* clear */);
 
-    // Blit
-    const unsigned int width = buffer.GetWidth();
-    const unsigned int height = buffer.GetHeight();
+    unsigned int x1, y1, x2, y2;
+    OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
+                                               t.GetHomogeneousMatrix(),
+                                               cropped.GetWidth(),
+                                               cropped.GetHeight(),
+                                               buffer.GetWidth(),
+                                               buffer.GetHeight());
 
     float value = foreground_;
 
-    if (useWindowing_)
+    if (!applyWindowing) // if applying the windowing, it means we are ie rendering the image for a realtime visualization -> the foreground_ value is the value we want to see on the screen -> don't change it
     {
-      float center, width;
-      if (GetScene().GetWindowing(center, width))
+      // if not applying the windowing, it means ie that we are saving a dicom image to file and the windowing will be applied by a viewer later on -> we want the "foreground" value to be correct once the windowing will be applied
+      value = windowCenter - windowWidth/2 + (foreground_ / 65535.0f) * windowWidth;
+
+      if (value < 0.0f)
       {
-        value = center + width / 2.0f;  // set it to the maximum pixel value of the image
+        value = 0.0f;
+      }
+      if (value > 65535.0f)
+      {
+        value = 65535.0f;
       }
     }
 
-    for (unsigned int y = 0; y < height; y++)
+    for (unsigned int y = y1; y <= y2; y++)
     {
-      float *q = reinterpret_cast<float*>(buffer.GetRow(y));
-      const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y));
+      float *q = reinterpret_cast<float*>(buffer.GetRow(y)) + x1;
+      const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)) + x1;
 
-      for (unsigned int x = 0; x < width; x++, p++, q++)
+      for (unsigned int x = x1; x <= x2; x++, p++, q++)
       {
         float a = static_cast<float>(*p) / 255.0f;
 
@@ -109,26 +123,19 @@
   bool RadiographyAlphaLayer::GetRange(float& minValue,
                                        float& maxValue) const
   {
-    if (useWindowing_)
-    {
-      return false;
-    }
-    else
-    {
-      minValue = 0;
-      maxValue = 0;
+    minValue = 0;
+    maxValue = 0;
 
-      if (foreground_ < 0)
-      {
-        minValue = foreground_;
-      }
+    if (foreground_ < 0)
+    {
+      minValue = foreground_;
+    }
 
-      if (foreground_ > 0)
-      {
-        maxValue = foreground_;
-      }
+    if (foreground_ > 0)
+    {
+      maxValue = foreground_;
+    }
 
-      return true;
-    }
+    return true;
   }
 }
--- a/Framework/Radiography/RadiographyAlphaLayer.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyAlphaLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -33,14 +33,12 @@
   class RadiographyAlphaLayer : public RadiographyLayer
   {
   private:
-    std::auto_ptr<Orthanc::ImageAccessor>  alpha_;      // Grayscale8
-    bool                                   useWindowing_;
-    float                                  foreground_;
+    std::auto_ptr<Orthanc::ImageAccessor>  alpha_;       // Grayscale8 in the range [0, 255]  0 = transparent, 255 = opaque -> the foreground value will be displayed
+    float                                  foreground_;  // in the range [0.0, 65535.0]
 
   public:
     RadiographyAlphaLayer(const RadiographyScene& scene) :
       RadiographyLayer(scene),
-      useWindowing_(true),
       foreground_(0)
     {
     }
@@ -48,7 +46,6 @@
 
     void SetForegroundValue(float foreground)
     {
-      useWindowing_ = false;
       foreground_ = foreground;
     }
 
@@ -57,11 +54,6 @@
       return foreground_;
     }
 
-    bool IsUsingWindowing() const
-    {
-      return useWindowing_;
-    }
-
     void SetAlpha(Orthanc::ImageAccessor* image);
 
     virtual bool GetDefaultWindowing(float& center,
@@ -73,7 +65,10 @@
 
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const;
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const;
 
     virtual bool GetRange(float& minValue,
                           float& maxValue) const;
--- a/Framework/Radiography/RadiographyDicomLayer.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyDicomLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -28,6 +28,7 @@
 #include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
 #include <Plugins/Samples/Common/DicomDatasetReader.h>
+#include "../Toolbox/ImageGeometry.h"
 
 static OrthancPlugins::DicomTag  ConvertTag(const Orthanc::DicomTag& tag)
 {
@@ -139,7 +140,10 @@
 
   void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
                                      const AffineTransform2D& viewTransform,
-                                     ImageInterpolation interpolation) const
+                                     ImageInterpolation interpolation,
+                                     float windowCenter,
+                                     float windowWidth,
+                                     bool applyWindowing) const
   {
     if (converted_.get() != NULL)
     {
@@ -159,6 +163,47 @@
       converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
 
       t.Apply(buffer, cropped, interpolation, false);
+      unsigned int x1, y1, x2, y2;
+      OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
+                                                 t.GetHomogeneousMatrix(),
+                                                 cropped.GetWidth(),
+                                                 cropped.GetHeight(),
+                                                 buffer.GetWidth(),
+                                                 buffer.GetHeight());
+
+      if (applyWindowing)
+      {
+        // apply windowing but stay in the range [0.0, 65535.0]
+        float w0 = windowCenter - windowWidth / 2.0f;
+        float w1 = windowCenter + windowWidth / 2.0f;
+
+        if (windowWidth >= 0.001f)  // Avoid division by zero at (*)
+        {
+          float scaling = 1.0f / (w1 - w0) * 65535.0f;
+          for (unsigned int y = y1; y <= y2; y++)
+          {
+            float* p = reinterpret_cast<float*>(buffer.GetRow(y)) + x1;
+
+            for (unsigned int x = x1; x <= x2; x++, p++)
+            {
+              if (*p >= w1)
+              {
+                *p = 65535.0;
+              }
+              else if (*p <= w0)
+              {
+                *p = 0;
+              }
+              else
+              {
+                // https://en.wikipedia.org/wiki/Linear_interpolation
+                *p = scaling * (*p - w0);  // (*)
+              }
+            }
+          }
+        }
+      }
+
     }
   }
 
--- a/Framework/Radiography/RadiographyDicomLayer.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyDicomLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -91,7 +91,10 @@
 
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const;
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const;
 
     virtual bool GetDefaultWindowing(float& center,
                                      float& width) const;
--- a/Framework/Radiography/RadiographyLayer.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -350,7 +350,10 @@
 
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const = 0;
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const = 0;
 
     virtual bool GetRange(float& minValue,
                           float& maxValue) const = 0;
--- a/Framework/Radiography/RadiographyMaskLayer.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyMaskLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,10 +26,11 @@
 #include "Core/Images/Image.h"
 #include "Core/Images/ImageProcessing.h"
 #include <Core/OrthancException.h>
+#include "../Toolbox/ImageGeometry.h"
 
 namespace OrthancStone
 {
-  const unsigned char IN_MASK_VALUE = 0x00;
+  const unsigned char IN_MASK_VALUE = 0x77;
   const unsigned char OUT_MASK_VALUE = 0xFF;
 
   const AffineTransform2D& RadiographyMaskLayer::GetTransform() const
@@ -76,7 +77,10 @@
 
   void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer,
                                     const AffineTransform2D& viewTransform,
-                                    ImageInterpolation interpolation) const
+                                    ImageInterpolation interpolation,
+                                    float windowCenter,
+                                    float windowWidth,
+                                    bool applyWindowing) const
   {
     if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded)
       return;
@@ -108,20 +112,36 @@
 
       Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
 
-      t.Apply(tmp, cropped, interpolation, true /* clear */);
+      t.Apply(tmp, cropped, ImageInterpolation_Nearest, true /* clear */);
+
+      unsigned int x1, y1, x2, y2;
+      OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
+                                                 t.GetHomogeneousMatrix(),
+                                                 cropped.GetWidth(),
+                                                 cropped.GetHeight(),
+                                                 buffer.GetWidth(),
+                                                 buffer.GetHeight());
+
+      // we have observed vertical lines at the image border (probably due to bilinear filtering of the DICOM image when it is not aligned with the buffer pixels)
+      // -> draw the mask one line further on each side
+      if (x1 >= 1)
+      {
+        x1 = x1 - 1;
+      }
+      if (x2 < buffer.GetWidth() - 2)
+      {
+        x2 = x2 + 1;
+      }
 
       // Blit
-      const unsigned int width = buffer.GetWidth();
-      const unsigned int height = buffer.GetHeight();
-
-      for (unsigned int y = 0; y < height; y++)
+      for (unsigned int y = y1; y <= y2; y++)
       {
-        float *q = reinterpret_cast<float*>(buffer.GetRow(y));
-        const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y));
+        float *q = reinterpret_cast<float*>(buffer.GetRow(y)) + x1;
+        const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)) + x1;
 
-        for (unsigned int x = 0; x < width; x++, p++, q++)
+        for (unsigned int x = x1; x <= x2; x++, p++, q++)
         {
-          if (*p == OUT_MASK_VALUE)
+          if (*p != IN_MASK_VALUE)
             *q = foreground_;
           // else keep the underlying pixel value
         }
--- a/Framework/Radiography/RadiographyMaskLayer.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyMaskLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -76,7 +76,10 @@
 
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const;
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const;
 
     std::string GetInstanceId() const;
 
--- a/Framework/Radiography/RadiographyScene.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyScene.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -134,9 +134,7 @@
 
     std::auto_ptr<RadiographyLayer> raii(layer);
 
-    // LOG(INFO) << "Registering layer: " << countLayers_;
-
-    size_t index = countLayers_++;
+    size_t index = nextLayerIndex_++;
     raii->SetIndex(index);
     layers_[index] = raii.release();
 
@@ -162,8 +160,9 @@
     BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
   }
 
+  
   RadiographyScene::RadiographyScene() :
-    countLayers_(0),
+    nextLayerIndex_(0),
     hasWindowing_(false),
     windowingCenter_(0),  // Dummy initialization
     windowingWidth_(0)    // Dummy initialization
@@ -219,9 +218,8 @@
       delete found->second;
       
       layers_.erase(found);
-      countLayers_--;
       
-      LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers";
+      LOG(INFO) << "Removing layer, there are now : " << layers_.size() << " layers";
 
       BroadcastMessage(RadiographyScene::LayerRemovedMessage(*this, layerIndex));
     }
@@ -281,7 +279,7 @@
 
 
   RadiographyLayer& RadiographyScene::LoadText(const std::string& utf8,
-                                               size_t fontSize,
+                                               unsigned int fontSize,
                                                uint8_t foreground,
                                                RadiographyLayer::Geometry* geometry)
   {
@@ -292,7 +290,10 @@
       alpha->SetGeometry(*geometry);
     }
 
-    return RegisterLayer(alpha.release());
+    RadiographyLayer& registeredLayer = RegisterLayer(alpha.release());
+
+    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, registeredLayer));
+    return registeredLayer;
   }
 
 
@@ -507,16 +508,17 @@
 
   void RadiographyScene::Render(Orthanc::ImageAccessor& buffer,
                                 const AffineTransform2D& viewTransform,
-                                ImageInterpolation interpolation) const
+                                ImageInterpolation interpolation,
+                                bool applyWindowing) const
   {
     // Render layers in the background-to-foreground order
-    for (size_t index = 0; index < countLayers_; index++)
+    for (size_t index = 0; index < nextLayerIndex_; index++)
     {
       Layers::const_iterator it = layers_.find(index);
       if (it != layers_.end())
       {
         assert(it->second != NULL);
-        it->second->Render(buffer, viewTransform, interpolation);
+        it->second->Render(buffer, viewTransform, interpolation, windowingCenter_, windowingWidth_, applyWindowing);
       }
     }
   }
@@ -527,7 +529,7 @@
                                      double y) const
   {
     // Render layers in the foreground-to-background order
-    for (size_t i = countLayers_; i > 0; i--)
+    for (size_t i = nextLayerIndex_; i > 0; i--)
     {
       index = i - 1;
       Layers::const_iterator it = layers_.find(index);
@@ -597,7 +599,8 @@
                                                   double pixelSpacingY,
                                                   ImageInterpolation interpolation,
                                                   bool invert,
-                                                  int64_t maxValue /* for inversion */)
+                                                  int64_t maxValue /* for inversion */,
+                                                  bool applyWindowing)
   {
     if (pixelSpacingX <= 0 ||
         pixelSpacingY <= 0)
@@ -626,7 +629,7 @@
     // wipe background before rendering
     Orthanc::ImageProcessing::Set(layers, 0);
 
-    Render(layers, view, interpolation);
+    Render(layers, view, interpolation, applyWindowing);
 
     std::auto_ptr<Orthanc::Image> rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16,
                                                               layers.GetWidth(), layers.GetHeight(), false));
@@ -649,7 +652,7 @@
   {
     LOG(INFO) << "Exporting RadiographyScene to DICOM";
 
-    std::auto_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags
+    std::auto_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags
 
     createDicomRequestContent["Tags"] = dicomTags;
 
--- a/Framework/Radiography/RadiographyScene.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyScene.h	Fri Nov 29 11:03:41 2019 +0100
@@ -162,7 +162,7 @@
   protected:
     typedef std::map<size_t, RadiographyLayer*>  Layers;
 
-    size_t  countLayers_;
+    size_t  nextLayerIndex_;
     bool    hasWindowing_;
     float   windowingCenter_;
     float   windowingWidth_;
@@ -199,7 +199,7 @@
     RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const;
 
     RadiographyLayer& LoadText(const std::string& utf8,
-                               size_t fontSize,
+                               unsigned int fontSize,
                                uint8_t foreground,
                                RadiographyLayer::Geometry* geometry);
     
@@ -288,7 +288,8 @@
 
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const;
+                        ImageInterpolation interpolation,
+                        bool applyWindowing) const;
 
     bool LookupLayer(size_t& index /* out */,
                      double x,
@@ -340,15 +341,17 @@
 
     Orthanc::Image* ExportToImage(double pixelSpacingX,
                                   double pixelSpacingY,
-                                  ImageInterpolation interpolation)
+                                  ImageInterpolation interpolation,
+                                  bool applyWindowing)
     {
-      return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0);
+      return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0, applyWindowing);
     }
 
     Orthanc::Image* ExportToImage(double pixelSpacingX,
                                   double pixelSpacingY,
                                   ImageInterpolation interpolation,
                                   bool invert,
-                                  int64_t maxValue /* for inversion */);
+                                  int64_t maxValue /* for inversion */,
+                                  bool applyWindowing);  // i.e.: when
   };
 }
--- a/Framework/Radiography/RadiographySceneWriter.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographySceneWriter.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -97,7 +97,6 @@
     Orthanc::Toolbox::EncodeDataUriScheme(pngContentBase64, "image/png", pngContent);
     output["content"] = pngContentBase64;
     output["foreground"] = layer.GetForegroundValue();
-    output["isUsingWindowing"] = layer.IsUsingWindowing();
   }
 
   void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyLayer& layer)
--- a/Framework/Radiography/RadiographyTextLayer.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyTextLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -30,7 +30,7 @@
   Orthanc::EmbeddedResources::FileResourceId RadiographyTextLayer::fontResourceId_;
 
   void RadiographyTextLayer::LoadText(const std::string& utf8,
-                                      size_t fontSize,
+                                      unsigned int fontSize,
                                       uint8_t foreground)
   {
     if (!fontHasBeenConfigured_)
@@ -45,5 +45,6 @@
     SetAlpha(TextRenderer::Render(fontResourceId_,
                                   fontSize_,
                                   text_));
+    SetForegroundValue(foreground * 256.0f);
   }
 }
--- a/Framework/Radiography/RadiographyTextLayer.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyTextLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -31,7 +31,7 @@
   {
   private:
     std::string                 text_;
-    size_t                      fontSize_;
+    unsigned int                fontSize_;
     uint8_t                     foreground_;
 
     static bool                                       fontHasBeenConfigured_;
@@ -42,14 +42,14 @@
     {
     }
 
-    void LoadText(const std::string& utf8, size_t fontSize, uint8_t foreground);
+    void LoadText(const std::string& utf8, unsigned int fontSize, uint8_t foreground);
 
     const std::string& GetText() const
     {
       return text_;
     }
 
-    size_t GetFontSize() const
+    unsigned int GetFontSize() const
     {
       return fontSize_;
     }
--- a/Framework/Radiography/RadiographyWidget.cpp	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyWidget.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -70,83 +70,66 @@
                                          unsigned int height,
                                          ImageInterpolation interpolation)
   {
-    float windowCenter, windowWidth;
-    scene_->GetWindowingWithDefault(windowCenter, windowWidth);
-
-    float x0 = windowCenter - windowWidth / 2.0f;
-    float x1 = windowCenter + windowWidth / 2.0f;
+    if (floatBuffer_.get() == NULL ||
+        floatBuffer_->GetWidth() != width ||
+        floatBuffer_->GetHeight() != height)
+    {
+      floatBuffer_.reset(new Orthanc::Image(
+        Orthanc::PixelFormat_Float32, width, height, false));
+    }
 
-    if (windowWidth <= 0.001f)  // Avoid division by zero at (*)
-    {
-      return false;
-    }
-    else
+    if (cairoBuffer_.get() == NULL ||
+        cairoBuffer_->GetWidth() != width ||
+        cairoBuffer_->GetHeight() != height)
     {
-      if (floatBuffer_.get() == NULL ||
-          floatBuffer_->GetWidth() != width ||
-          floatBuffer_->GetHeight() != height)
-      {
-        floatBuffer_.reset(new Orthanc::Image(
-          Orthanc::PixelFormat_Float32, width, height, false));
-      }
+      cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */));
+    }
+
+    RenderBackground(*floatBuffer_, 0.0, 65535.0);
+
+    scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation, true);
 
-      if (cairoBuffer_.get() == NULL ||
-          cairoBuffer_->GetWidth() != width ||
-          cairoBuffer_->GetHeight() != height)
-      {
-        cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */));
-      }
-
-      RenderBackground(*floatBuffer_, x0, x1);
+    // Conversion from Float32 to BGRA32 (cairo). Very similar to
+    // GrayscaleFrameRenderer => TODO MERGE?
+    Orthanc::ImageAccessor target;
+    cairoBuffer_->GetWriteableAccessor(target);
 
-      scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
-
-      // Conversion from Float32 to BGRA32 (cairo). Very similar to
-      // GrayscaleFrameRenderer => TODO MERGE?
-
-      Orthanc::ImageAccessor target;
-      cairoBuffer_->GetWriteableAccessor(target);
-
-      float scaling = 255.0f / (x1 - x0);
+    bool invert = IsInvertedInternal();
 
-      bool invert = IsInvertedInternal();
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const float* p = reinterpret_cast<const float*>(floatBuffer_->GetConstRow(y));
+      uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
 
-      for (unsigned int y = 0; y < height; y++)
+      for (unsigned int x = 0; x < width; x++, p++, q += 4)
       {
-        const float* p = reinterpret_cast<const float*>(floatBuffer_->GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-
-        for (unsigned int x = 0; x < width; x++, p++, q += 4)
+        uint8_t v = 0;
+        if (*p >= 65535.0)
+        {
+          v = 255;
+        }
+        else if (*p <= 0.0)
         {
-          uint8_t v = 0;
-          if (*p >= x1)
-          {
-            v = 255;
-          }
-          else if (*p <= x0)
-          {
-            v = 0;
-          }
-          else
-          {
-            // https://en.wikipedia.org/wiki/Linear_interpolation
-            v = static_cast<uint8_t>(scaling * (*p - x0));  // (*)
-          }
+          v = 0;
+        }
+        else
+        {
+          v = static_cast<uint8_t>(*p / 256.0);
+        }
 
-          if (invert)
-          {
-            v = 255 - v;
-          }
+        if (invert)
+        {
+          v = 255 - v;
+        }
 
-          q[0] = v;
-          q[1] = v;
-          q[2] = v;
-          q[3] = 255;
-        }
+        q[0] = v;
+        q[1] = v;
+        q[2] = v;
+        q[3] = 255;
       }
+    }
 
-      return true;
-    }
+    return true;
   }
 
 
@@ -199,34 +182,6 @@
     selectedLayer_ = layer;
   }
 
-  void RadiographyWidget::ClearSelectedLayer()
-  {
-    hasSelection_ = false;
-  }
-
-  bool RadiographyWidget::SelectMaskLayer(size_t index)
-  {
-    std::vector<size_t> layerIndexes;
-    size_t count = 0;
-    scene_->GetLayersIndexes(layerIndexes);
-
-    for (size_t i = 0; i < layerIndexes.size(); ++i)
-    {
-      const RadiographyMaskLayer* maskLayer = dynamic_cast<const RadiographyMaskLayer*>(&(scene_->GetLayer(layerIndexes[i])));
-      if (maskLayer != NULL)
-      {
-        if (count == index)
-        {
-          Select(layerIndexes[i]);
-          return true;
-        }
-        count++;
-      }
-    }
-
-    return false;
-  }
-
   bool RadiographyWidget::LookupSelectedLayer(size_t& layer)
   {
     if (hasSelection_)
@@ -259,8 +214,9 @@
     size_t removedLayerIndex = message.GetLayerIndex();
     if (hasSelection_ && selectedLayer_ == removedLayerIndex)
     {
-      ClearSelectedLayer();
+      Unselect();
     }
+    NotifyContentChanged();
   }
   
   void RadiographyWidget::SetInvert(bool invert)
--- a/Framework/Radiography/RadiographyWidget.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/Radiography/RadiographyWidget.h	Fri Nov 29 11:03:41 2019 +0100
@@ -71,16 +71,14 @@
 
     void SetScene(boost::shared_ptr<RadiographyScene> scene);
 
+    void Select(size_t layer);
+
     void Unselect()
     {
       hasSelection_ = false;
     }
 
-    void Select(size_t layer);
-
-    void ClearSelectedLayer();
-
-    bool SelectMaskLayer(size_t index = 0);
+    template<typename LayerType> bool SelectLayerByType(size_t index = 0);
 
     bool LookupSelectedLayer(size_t& layer);
 
@@ -106,4 +104,27 @@
       return interpolation_;
     }
   };
+
+  template<typename LayerType> bool RadiographyWidget::SelectLayerByType(size_t index)
+  {
+    std::vector<size_t> layerIndexes;
+    size_t count = 0;
+    scene_->GetLayersIndexes(layerIndexes);
+
+    for (size_t i = 0; i < layerIndexes.size(); ++i)
+    {
+      const LayerType* typedLayer = dynamic_cast<const LayerType*>(&(scene_->GetLayer(layerIndexes[i])));
+      if (typedLayer != NULL)
+      {
+        if (count == index)
+        {
+          Select(layerIndexes[i]);
+          return true;
+        }
+        count++;
+      }
+    }
+
+    return false;
+  }
 }
--- a/Framework/StoneEnumerations.h	Thu Nov 28 17:13:33 2019 +0100
+++ b/Framework/StoneEnumerations.h	Fri Nov 29 11:03:41 2019 +0100
@@ -95,10 +95,12 @@
     KeyboardKeys_Generic = 0,
 
     // let's use the same ids as in javascript to avoid some conversion in WASM: https://css-tricks.com/snippets/javascript/javascript-keycodes/
+    KeyboardKeys_Backspace = 8,
     KeyboardKeys_Left = 37,
     KeyboardKeys_Up = 38,
     KeyboardKeys_Right = 39,
-    KeyboardKeys_Down = 40
+    KeyboardKeys_Down = 40,
+    KeyboardKeys_Delete = 46
   };
 
   enum SopClassUid