changeset 1398:c5403d52078c

moved Radiography into Deprecated
author Alain Mazy <alain@mazy.be>
date Wed, 29 Apr 2020 20:43:09 +0200
parents 1c2d065ba372
children ff8d2e46ac63
files Framework/Deprecated/Radiography/RadiographyAlphaLayer.cpp Framework/Deprecated/Radiography/RadiographyAlphaLayer.h Framework/Deprecated/Radiography/RadiographyDicomLayer.cpp Framework/Deprecated/Radiography/RadiographyDicomLayer.h Framework/Deprecated/Radiography/RadiographyLayer.cpp Framework/Deprecated/Radiography/RadiographyLayer.h Framework/Deprecated/Radiography/RadiographyLayerCropTracker.cpp Framework/Deprecated/Radiography/RadiographyLayerCropTracker.h Framework/Deprecated/Radiography/RadiographyLayerMaskTracker.cpp Framework/Deprecated/Radiography/RadiographyLayerMaskTracker.h Framework/Deprecated/Radiography/RadiographyLayerMoveTracker.cpp Framework/Deprecated/Radiography/RadiographyLayerMoveTracker.h Framework/Deprecated/Radiography/RadiographyLayerResizeTracker.cpp Framework/Deprecated/Radiography/RadiographyLayerResizeTracker.h Framework/Deprecated/Radiography/RadiographyLayerRotateTracker.cpp Framework/Deprecated/Radiography/RadiographyLayerRotateTracker.h Framework/Deprecated/Radiography/RadiographyMaskLayer.cpp Framework/Deprecated/Radiography/RadiographyMaskLayer.h Framework/Deprecated/Radiography/RadiographyScene.cpp Framework/Deprecated/Radiography/RadiographyScene.h Framework/Deprecated/Radiography/RadiographySceneCommand.cpp Framework/Deprecated/Radiography/RadiographySceneCommand.h Framework/Deprecated/Radiography/RadiographySceneReader.cpp Framework/Deprecated/Radiography/RadiographySceneReader.h Framework/Deprecated/Radiography/RadiographySceneWriter.cpp Framework/Deprecated/Radiography/RadiographySceneWriter.h Framework/Deprecated/Radiography/RadiographyTextLayer.cpp Framework/Deprecated/Radiography/RadiographyTextLayer.h Framework/Deprecated/Radiography/RadiographyWidget.cpp Framework/Deprecated/Radiography/RadiographyWidget.h Framework/Deprecated/Radiography/RadiographyWindowingTracker.cpp Framework/Deprecated/Radiography/RadiographyWindowingTracker.h Framework/Radiography/RadiographyAlphaLayer.cpp Framework/Radiography/RadiographyAlphaLayer.h Framework/Radiography/RadiographyDicomLayer.cpp Framework/Radiography/RadiographyDicomLayer.h Framework/Radiography/RadiographyLayer.cpp Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyLayerCropTracker.cpp Framework/Radiography/RadiographyLayerCropTracker.h Framework/Radiography/RadiographyLayerMaskTracker.cpp Framework/Radiography/RadiographyLayerMaskTracker.h Framework/Radiography/RadiographyLayerMoveTracker.cpp Framework/Radiography/RadiographyLayerMoveTracker.h Framework/Radiography/RadiographyLayerResizeTracker.cpp Framework/Radiography/RadiographyLayerResizeTracker.h Framework/Radiography/RadiographyLayerRotateTracker.cpp Framework/Radiography/RadiographyLayerRotateTracker.h Framework/Radiography/RadiographyMaskLayer.cpp Framework/Radiography/RadiographyMaskLayer.h Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/Radiography/RadiographySceneCommand.cpp Framework/Radiography/RadiographySceneCommand.h Framework/Radiography/RadiographySceneReader.cpp Framework/Radiography/RadiographySceneReader.h Framework/Radiography/RadiographySceneWriter.cpp Framework/Radiography/RadiographySceneWriter.h Framework/Radiography/RadiographyTextLayer.cpp Framework/Radiography/RadiographyTextLayer.h Framework/Radiography/RadiographyWidget.cpp Framework/Radiography/RadiographyWidget.h Framework/Radiography/RadiographyWindowingTracker.cpp Framework/Radiography/RadiographyWindowingTracker.h
diffstat 64 files changed, 5648 insertions(+), 5648 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyAlphaLayer.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,152 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyAlphaLayer.h"
+
+#include "RadiographyScene.h"
+
+#include <Core/Compatibility.h>
+#include <Core/Images/Image.h>
+#include <Core/OrthancException.h>
+
+#include "../Toolbox/ImageGeometry.h"
+
+namespace OrthancStone
+{
+
+  void RadiographyAlphaLayer::SetAlpha(Orthanc::ImageAccessor* image)
+  {
+    std::unique_ptr<Orthanc::ImageAccessor> raii(image);
+
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    SetSize(image->GetWidth(), image->GetHeight());
+
+#if __cplusplus < 201103L
+    alpha_.reset(raii.release());
+#else
+    alpha_ = std::move(raii);
+#endif
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer,
+                                     const AffineTransform2D& viewTransform,
+                                     ImageInterpolation interpolation,
+                                     float windowCenter,
+                                     float windowWidth,
+                                     bool applyWindowing) const
+  {
+    if (alpha_.get() == NULL)
+    {
+      return;
+    }
+
+    if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    unsigned int cropX, cropY, cropWidth, cropHeight;
+    GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+    const AffineTransform2D t = AffineTransform2D::Combine(
+          viewTransform, GetTransform(),
+          AffineTransform2D::CreateOffset(cropX, cropY));
+
+    Orthanc::ImageAccessor cropped;
+    alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
+
+    Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
+
+    unsigned int x1, y1, x2, y2;
+
+    if (!OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
+                                                    t.GetHomogeneousMatrix(),
+                                                    cropped.GetWidth(),
+                                                    cropped.GetHeight(),
+                                                    buffer.GetWidth(),
+                                                    buffer.GetHeight()))
+    {
+      return;  // layer is outside the buffer
+    }
+
+    t.Apply(tmp, cropped, interpolation, true /* clear */);
+
+    float value = foreground_;
+
+    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
+    {
+      // 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 = 0.0f;
+      }
+      if (value > 65535.0f)
+      {
+        value = 65535.0f;
+      }
+    }
+
+    for (unsigned int y = y1; y <= y2; 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 = x1; x <= x2; x++, p++, q++)
+      {
+        float a = static_cast<float>(*p) / 255.0f;
+
+        *q = (a * value + (1.0f - a) * (*q));
+      }
+    }
+  }
+
+  bool RadiographyAlphaLayer::GetRange(float& minValue,
+                                       float& maxValue) const
+  {
+    minValue = 0;
+    maxValue = 0;
+
+    if (foreground_ < 0)
+    {
+      minValue = foreground_;
+    }
+
+    if (foreground_ > 0)
+    {
+      maxValue = foreground_;
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyAlphaLayer.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,85 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
+
+#include <Core/Compatibility.h>
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  // creates a transparent layer whose alpha channel is provided as a UINT8 image to SetAlpha.
+  // The color of the "mask" is either defined by a ForegroundValue or by the center value of the
+  // windowing from the scene.
+  class RadiographyAlphaLayer : public RadiographyLayer
+  {
+  private:
+    std::unique_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),
+      foreground_(0)
+    {
+    }
+
+
+    void SetForegroundValue(float foreground)
+    {
+      foreground_ = foreground;
+    }
+
+    float GetForegroundValue() const
+    {
+      return foreground_;
+    }
+
+    void SetAlpha(Orthanc::ImageAccessor* image);
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const
+    {
+      return false;
+    }
+
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const;
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const;
+
+    const Orthanc::ImageAccessor& GetAlpha() const
+    {
+      return *(alpha_.get());
+    }
+
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyDicomLayer.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,264 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyDicomLayer.h"
+
+#include "RadiographyScene.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
+
+#include <Core/OrthancException.h>
+#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)
+{
+  return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
+}
+
+namespace OrthancStone
+{
+
+  void RadiographyDicomLayer::ApplyConverter()
+  {
+    if (source_.get() != NULL &&
+        converter_.get() != NULL)
+    {
+      converted_.reset(converter_->ConvertFrame(*source_));
+    }
+  }
+
+
+  RadiographyDicomLayer::RadiographyDicomLayer(const RadiographyScene& scene) :
+    RadiographyLayer(scene)
+  {
+
+  }
+
+  void RadiographyDicomLayer::SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
+  {
+    converter_.reset(new Deprecated::DicomFrameConverter);
+    converter_->ReadParameters(dataset);
+    ApplyConverter();
+
+    std::string tmp;
+    Vector pixelSpacing;
+
+    if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
+        LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
+        pixelSpacing.size() == 2)
+    {
+      SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
+    }
+
+    OrthancPlugins::DicomDatasetReader reader(dataset);
+
+    unsigned int width, height;
+    if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
+        !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      SetSize(width, height);
+    }
+
+    if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION)))
+    {
+      if (tmp == "MONOCHROME1")
+      {
+        SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome1);
+      }
+      else if (tmp == "MONOCHROME2")
+      {
+        SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome2);
+      }
+    }
+  }
+
+  void RadiographyDicomLayer::SetSourceImage(Orthanc::ImageAccessor* image)   // Takes ownership
+  {
+    std::unique_ptr<Orthanc::ImageAccessor> raii(image);
+
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    SetSize(image->GetWidth(), image->GetHeight());
+
+#if __cplusplus < 201103L
+      source_.reset(raii.release());
+#else
+      source_ = std::move(raii);
+#endif
+
+    ApplyConverter();
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyDicomLayer::SetSourceImage(Orthanc::ImageAccessor* image, double newPixelSpacingX, double newPixelSpacingY, bool emitLayerEditedEvent)   // Takes ownership
+  {
+    std::unique_ptr<Orthanc::ImageAccessor> raii(image);
+
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    SetSize(image->GetWidth(), image->GetHeight(), false);
+
+#if __cplusplus < 201103L
+    source_.reset(raii.release());
+#else
+    source_ = std::move(raii);
+#endif
+
+    ApplyConverter();
+
+    SetPixelSpacing(newPixelSpacingX, newPixelSpacingY, false);
+
+    if (emitLayerEditedEvent)
+    {
+      BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+    }
+  }
+
+
+  void RadiographyDicomLayer::SetDicomFrameConverter(Deprecated::DicomFrameConverter* converter)
+  {
+    converter_.reset(converter);
+  }
+
+  void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
+                                     const AffineTransform2D& viewTransform,
+                                     ImageInterpolation interpolation,
+                                     float windowCenter,
+                                     float windowWidth,
+                                     bool applyWindowing) const
+  {
+    if (converted_.get() != NULL)
+    {
+      if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      unsigned int cropX, cropY, cropWidth, cropHeight;
+      GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+      AffineTransform2D t = AffineTransform2D::Combine(
+            viewTransform, GetTransform(),
+            AffineTransform2D::CreateOffset(cropX, cropY));
+
+      Orthanc::ImageAccessor cropped;
+      converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
+
+      unsigned int x1, y1, x2, y2;
+      if (!OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
+                                                      t.GetHomogeneousMatrix(),
+                                                      cropped.GetWidth(),
+                                                      cropped.GetHeight(),
+                                                      buffer.GetWidth(),
+                                                      buffer.GetHeight()))
+      {
+        return;  // layer is outside the buffer
+      }
+
+      t.Apply(buffer, cropped, interpolation, false);
+
+      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);  // (*)
+              }
+            }
+          }
+        }
+      }
+
+    }
+  }
+
+
+  bool RadiographyDicomLayer::GetDefaultWindowing(float& center,
+                                                  float& width) const
+  {
+    if (converter_.get() != NULL &&
+        converter_->HasDefaultWindow())
+    {
+      center = static_cast<float>(converter_->GetDefaultWindowCenter());
+      width = static_cast<float>(converter_->GetDefaultWindowWidth());
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool RadiographyDicomLayer::GetRange(float& minValue,
+                                       float& maxValue) const
+  {
+    if (converted_.get() != NULL)
+    {
+      if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyDicomLayer.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,105 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Deprecated/Toolbox/DicomFrameConverter.h"
+#include "RadiographyLayer.h"
+
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  class RadiographyDicomLayer : public RadiographyLayer
+  {
+  private:
+    std::unique_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
+    std::unique_ptr<Deprecated::DicomFrameConverter>     converter_;
+    std::unique_ptr<Orthanc::ImageAccessor>  converted_;  // Float32
+    std::string                            instanceId_;
+    unsigned int                           frame_;
+
+    void ApplyConverter();
+
+  public:
+    RadiographyDicomLayer(const RadiographyScene& scene);
+
+    void SetInstance(const std::string& instanceId, unsigned int frame)
+    {
+      instanceId_ = instanceId;
+      frame_ = frame;
+    }
+
+    std::string GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      return frame_;
+    }
+
+    virtual size_t GetApproximateMemoryUsage() const
+    {
+      size_t size = 0;
+      if (source_.get() != NULL)
+      {
+        size += source_->GetPitch() * source_->GetHeight();
+      }
+      if (converted_.get() != NULL)
+      {
+        size += converted_->GetPitch() * converted_->GetHeight();
+      }
+
+      return size;
+    }
+
+
+    void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset);
+
+    void SetSourceImage(Orthanc::ImageAccessor* image);   // Takes ownership
+
+    void SetSourceImage(Orthanc::ImageAccessor* image, double newPixelSpacingX, double newPixelSpacingY, bool emitLayerEditedEvent = true);   // Takes ownership
+
+    const Orthanc::ImageAccessor* GetSourceImage() const {return source_.get();}  // currently need this access to serialize scene in plain old data to send to a WASM worker
+
+    const Deprecated::DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker
+    
+     // Takes ownership
+    void SetDicomFrameConverter(Deprecated::DicomFrameConverter* converter);
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const;
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const;
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayer.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,402 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  static double Square(double x)
+  {
+    return x * x;
+  }
+
+
+  RadiographyLayer::Geometry::Geometry() :
+    hasCrop_(false),
+    flipVertical_(false),
+    flipHorizontal_(false),
+    panX_(0),
+    panY_(0),
+    angle_(0),
+    resizeable_(false),
+    pixelSpacingX_(1),
+    pixelSpacingY_(1)
+  {
+
+  }
+
+  void RadiographyLayer::Geometry::GetCrop(unsigned int &x, unsigned int &y, unsigned int &width, unsigned int &height) const
+  {
+    if (!hasCrop_)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);  // you should probably use RadiographyLayer::GetCrop() or at least call HasCrop() before
+
+    x = cropX_;
+    y = cropY_;
+    width = cropWidth_;
+    height = cropHeight_;
+  }
+
+  void RadiographyLayer::UpdateTransform()
+  {
+    // important to update transform_ before getting the center to use the right scaling !!!
+    transform_ = AffineTransform2D::CreateScaling(geometry_.GetScalingX(), geometry_.GetScalingY());
+
+    double centerX, centerY;
+    GetCenter(centerX, centerY);
+
+    transform_ = AffineTransform2D::Combine(
+          AffineTransform2D::CreateOffset(geometry_.GetPanX(), geometry_.GetPanY()),
+          AffineTransform2D::CreateRotation(geometry_.GetAngle(), centerX, centerY),
+          transform_);
+
+    transformInverse_ = AffineTransform2D::Invert(transform_);
+  }
+
+
+  void RadiographyLayer::AddToExtent(Extent2D& extent,
+                                     double x,
+                                     double y) const
+  {
+    GetTransform().Apply(x, y);
+    extent.AddPoint(x, y);
+  }
+
+  bool RadiographyLayer::Contains(double x,
+                                  double y) const
+  {
+    GetTransformInverse().Apply(x, y);
+
+    unsigned int cropX, cropY, cropWidth, cropHeight;
+    GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+    return (x >= cropX && x <= cropX + cropWidth &&
+            y >= cropY && y <= cropY + cropHeight);
+  }
+
+
+  void RadiographyLayer::DrawBorders(CairoContext& context,
+                                     double zoom)
+  {
+    if (GetControlPointCount() < 3 )
+      return;
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_line_width(cr, 2.0 / zoom);
+
+    ControlPoint cp;
+    GetControlPoint(cp, 0);
+    cairo_move_to(cr, cp.x, cp.y);
+
+    for (size_t i = 0; i < GetControlPointCount(); i++)
+    {
+      GetControlPoint(cp, i);
+      cairo_line_to(cr, cp.x, cp.y);
+    }
+
+    cairo_close_path(cr);
+    cairo_stroke(cr);
+  }
+
+
+  RadiographyLayer::RadiographyLayer(const RadiographyScene& scene) :
+    index_(0),
+    hasSize_(false),
+    width_(0),
+    height_(0),
+    prefferedPhotometricDisplayMode_(RadiographyPhotometricDisplayMode_Default),
+    scene_(scene)
+  {
+    UpdateTransform();
+  }
+
+  void RadiographyLayer::ResetCrop()
+  {
+    geometry_.ResetCrop();
+    UpdateTransform();
+  }
+
+  void RadiographyLayer::SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode)
+  {
+    prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode;
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyLayer::SetCrop(unsigned int x,
+                                 unsigned int y,
+                                 unsigned int width,
+                                 unsigned int height)
+  {
+    if (!hasSize_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (x + width > width_ ||
+        y + height > height_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    geometry_.SetCrop(x, y, width, height);
+    UpdateTransform();
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyLayer::SetGeometry(const Geometry& geometry)
+  {
+    geometry_ = geometry;
+
+    if (hasSize_)
+    {
+      UpdateTransform();
+    }
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+
+  void RadiographyLayer::GetCrop(unsigned int& x,
+                                 unsigned int& y,
+                                 unsigned int& width,
+                                 unsigned int& height) const
+  {
+    if (GetGeometry().HasCrop())
+    {
+      GetGeometry().GetCrop(x, y, width, height);
+    }
+    else
+    {
+      x = 0;
+      y = 0;
+      width = width_;
+      height = height_;
+    }
+  }
+
+  
+  void RadiographyLayer::SetAngle(double angle)
+  {
+    geometry_.SetAngle(angle);
+    UpdateTransform();
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyLayer::SetFlipVertical(bool flip)
+  {
+    geometry_.SetFlipVertical(flip);
+    UpdateTransform();
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyLayer::SetFlipHorizontal(bool flip)
+  {
+    geometry_.SetFlipHorizontal(flip);
+    UpdateTransform();
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyLayer::SetSize(unsigned int width,
+                                 unsigned int height,
+                                 bool emitLayerEditedEvent)
+  {
+    hasSize_ = true;
+    width_ = width;
+    height_ = height;
+
+    UpdateTransform();
+
+    if (emitLayerEditedEvent)
+    {
+      BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+    }
+  }
+
+  Extent2D RadiographyLayer::GetSceneExtent(bool /*minimal*/) const
+  {
+    Extent2D extent;
+
+    unsigned int x, y, width, height;
+    GetCrop(x, y, width, height);
+
+    double dx = static_cast<double>(x);
+    double dy = static_cast<double>(y);
+    double dwidth = static_cast<double>(width);
+    double dheight = static_cast<double>(height);
+
+    // AddToExtent transforms the coordinates from image to scene
+    AddToExtent(extent, dx, dy);
+    AddToExtent(extent, dx + dwidth, dy);
+    AddToExtent(extent, dx, dy + dheight);
+    AddToExtent(extent, dx + dwidth, dy + dheight);
+
+    return extent;
+  }
+
+
+  bool RadiographyLayer::GetPixel(unsigned int& imageX,
+                                  unsigned int& imageY,
+                                  double sceneX,
+                                  double sceneY) const
+  {
+    if (width_ == 0 ||
+        height_ == 0)
+    {
+      return false;
+    }
+    else
+    {
+      GetTransformInverse().Apply(sceneX, sceneY);
+
+      int x = static_cast<int>(std::floor(sceneX));
+      int y = static_cast<int>(std::floor(sceneY));
+
+      if (x < 0)
+      {
+        imageX = 0;
+      }
+      else if (x >= static_cast<int>(width_))
+      {
+        imageX = width_;
+      }
+      else
+      {
+        imageX = static_cast<unsigned int>(x);
+      }
+
+      if (y < 0)
+      {
+        imageY = 0;
+      }
+      else if (y >= static_cast<int>(height_))
+      {
+        imageY = height_;
+      }
+      else
+      {
+        imageY = static_cast<unsigned int>(y);
+      }
+
+      return true;
+    }
+  }
+
+
+  void RadiographyLayer::SetPan(double x,
+                                double y)
+  {
+    geometry_.SetPan(x, y);
+    UpdateTransform();
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+
+  void RadiographyLayer::SetPixelSpacing(double x,
+                                         double y,
+                                         bool emitLayerEditedEvent)
+  {
+    geometry_.SetPixelSpacing(x, y);
+    UpdateTransform();
+    if (emitLayerEditedEvent)
+    {
+      BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+    }
+  }
+
+
+  void RadiographyLayer::GetCenter(double& centerX,
+                                   double& centerY) const
+  {
+    centerX = static_cast<double>(width_) / 2.0;
+    centerY = static_cast<double>(height_) / 2.0;
+    GetTransform().Apply(centerX, centerY);
+  }
+
+
+
+  size_t RadiographyLayer::GetControlPointCount() const {return 4;}
+
+  void RadiographyLayer::GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */,
+                                         size_t index) const
+  {
+    unsigned int cropX, cropY, cropWidth, cropHeight;
+    GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+    ControlPoint cp;
+    switch (index)
+    {
+      case RadiographyControlPointType_TopLeftCorner:
+        cp = ControlPoint(cropX, cropY, RadiographyControlPointType_TopLeftCorner);
+        break;
+
+      case RadiographyControlPointType_TopRightCorner:
+        cp = ControlPoint(cropX + cropWidth, cropY, RadiographyControlPointType_TopRightCorner);
+        break;
+
+      case RadiographyControlPointType_BottomLeftCorner:
+        cp = ControlPoint(cropX, cropY + cropHeight, RadiographyControlPointType_BottomLeftCorner);
+        break;
+
+      case RadiographyControlPointType_BottomRightCorner:
+        cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, RadiographyControlPointType_BottomRightCorner);
+        break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    // transforms image coordinates into scene coordinates
+    GetTransform().Apply(cp.x, cp.y);
+    cpScene = cp;
+  }
+
+  bool RadiographyLayer::LookupControlPoint(ControlPoint& cpScene /* out */,
+                                            double x,
+                                            double y,
+                                            double zoom,
+                                            double viewportDistance) const
+  {
+    double threshold = Square(viewportDistance / zoom);
+
+    for (size_t i = 0; i < GetControlPointCount(); i++)
+    {
+      ControlPoint cp;
+      GetControlPoint(cp, i);
+
+      double d = Square(cp.x - x) + Square(cp.y - y);
+
+      if (d <= threshold)
+      {
+        cpScene = cp;
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayer.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,395 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 <algorithm>
+
+#include "../Toolbox/AffineTransform2D.h"
+#include "../Toolbox/Extent2D.h"
+#include "../Wrappers/CairoContext.h"
+#include "../Messages/IMessage.h"
+#include "../Messages/IObservable.h"
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  enum RadiographyControlPointType
+  {
+    RadiographyControlPointType_TopLeftCorner = 0,
+    RadiographyControlPointType_TopRightCorner = 1,
+    RadiographyControlPointType_BottomRightCorner = 2,
+    RadiographyControlPointType_BottomLeftCorner = 3
+  };
+
+  enum RadiographyPhotometricDisplayMode
+  {
+    RadiographyPhotometricDisplayMode_Default,
+
+    RadiographyPhotometricDisplayMode_Monochrome1,
+    RadiographyPhotometricDisplayMode_Monochrome2
+  };
+
+  
+  struct ControlPoint
+  {
+    double x;
+    double y;
+    size_t index;
+
+    ControlPoint(double x, double y, size_t index)
+      : x(x),
+        y(y),
+        index(index)
+    {}
+
+    ControlPoint()
+      : x(0),
+        y(0),
+        index(std::numeric_limits<size_t>::max())
+    {}
+  };
+
+  class RadiographyLayer : public IObservable
+  {
+    friend class RadiographyScene;
+
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, LayerEditedMessage, RadiographyLayer);
+
+    class Geometry
+    {
+      bool               hasCrop_;
+      unsigned int       cropX_;
+      unsigned int       cropY_;
+      unsigned int       cropWidth_;
+      unsigned int       cropHeight_;
+      bool               flipVertical_;
+      bool               flipHorizontal_;
+      double             panX_;
+      double             panY_;
+      double             angle_;
+      bool               resizeable_;
+      double             pixelSpacingX_;
+      double             pixelSpacingY_;
+
+    public:
+      Geometry();
+
+      void ResetCrop()
+      {
+        hasCrop_ = false;
+      }
+
+      void SetCrop(unsigned int x,
+                   unsigned int y,
+                   unsigned int width,
+                   unsigned int height)
+      {
+        hasCrop_ = true;
+        cropX_ = x;
+        cropY_ = y;
+        cropWidth_ = width;
+        cropHeight_ = height;
+      }
+
+      bool HasCrop() const
+      {
+        return hasCrop_;
+      }
+
+      void GetCrop(unsigned int& x,
+                   unsigned int& y,
+                   unsigned int& width,
+                   unsigned int& height) const;
+
+      void SetAngle(double angle)
+      {
+        angle_ = angle;
+      }
+
+      double GetAngle() const
+      {
+        return angle_;
+      }
+
+      void SetPan(double x,
+                  double y)
+      {
+        panX_ = x;
+        panY_ = y;
+      }
+
+      double GetPanX() const
+      {
+        return panX_;
+      }
+
+      double GetPanY() const
+      {
+        return panY_;
+      }
+
+      bool IsResizeable() const
+      {
+        return resizeable_;
+      }
+
+      void SetResizeable(bool resizeable)
+      {
+        resizeable_ = resizeable;
+      }
+
+      void SetPixelSpacing(double x,
+                           double y)
+      {
+        pixelSpacingX_ = x;
+        pixelSpacingY_ = y;
+      }
+
+      double GetPixelSpacingX() const
+      {
+        return pixelSpacingX_;
+      }
+
+      double GetPixelSpacingY() const
+      {
+        return pixelSpacingY_;
+      }
+
+      void SetFlipVertical(bool flip) //  mirrors image around an horizontal axis (note: flip is applied before the rotation !)
+      {
+        flipVertical_ = flip;
+      }
+
+      void SetFlipHorizontal(bool flip) //  mirrors image around a vertical axis (note: flip is applied before the rotation !)
+      {
+        flipHorizontal_ = flip;
+      }
+
+      bool GetFlipVertical() const
+      {
+        return flipVertical_;
+      }
+
+      bool GetFlipHorizontal() const
+      {
+        return flipHorizontal_;
+      }
+
+      double GetScalingX() const
+      {
+        return (flipHorizontal_ ? - pixelSpacingX_: pixelSpacingX_);
+      }
+
+      double GetScalingY() const
+      {
+        return (flipVertical_ ? - pixelSpacingY_: pixelSpacingY_);
+      }
+    };
+
+  private:
+    size_t             index_;
+    bool               hasSize_;
+    unsigned int       width_;
+    unsigned int       height_;
+    AffineTransform2D  transform_;
+    AffineTransform2D  transformInverse_;
+    Geometry           geometry_;
+    RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode_;
+    const RadiographyScene&   scene_;
+
+  protected:
+    void SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode);
+
+  private:
+    void UpdateTransform();
+
+    void AddToExtent(Extent2D& extent,
+                     double x,
+                     double y) const;
+
+    void SetIndex(size_t index)
+    {
+      index_ = index;
+    }
+
+    bool Contains(double x,
+                  double y) const;
+
+    void DrawBorders(CairoContext& context,
+                     double zoom);
+
+  public:
+    RadiographyLayer(const RadiographyScene& scene);
+
+    virtual ~RadiographyLayer()
+    {
+    }
+
+    virtual const AffineTransform2D& GetTransform() const
+    {
+      return transform_;
+    }
+
+    virtual const AffineTransform2D& GetTransformInverse() const
+    {
+      return transformInverse_;
+    }
+
+    size_t GetIndex() const
+    {
+      return index_;
+    }
+
+    const RadiographyScene& GetScene() const
+    {
+      return scene_;
+    }
+
+    const Geometry& GetGeometry() const
+    {
+      return geometry_;
+    }
+
+    void SetGeometry(const Geometry& geometry);
+
+    void ResetCrop();
+
+    void SetCrop(unsigned int x,       // those are pixel coordinates/size
+                 unsigned int y,
+                 unsigned int width,
+                 unsigned int height);
+
+    void SetCrop(const Extent2D& sceneExtent)
+    {
+      Extent2D imageCrop;
+
+      {
+        double x = sceneExtent.GetX1();
+        double y = sceneExtent.GetY1();
+        GetTransformInverse().Apply(x, y);
+        imageCrop.AddPoint(x, y);
+      }
+
+      {
+        double x = sceneExtent.GetX2();
+        double y = sceneExtent.GetY2();
+        GetTransformInverse().Apply(x, y);
+        imageCrop.AddPoint(x, y);
+      }
+
+      SetCrop(static_cast<unsigned int>(std::max(0.0, std::floor(imageCrop.GetX1()))),
+              static_cast<unsigned int>(std::max(0.0, std::floor(imageCrop.GetY1()))),
+              std::min(width_, static_cast<unsigned int>(std::ceil(imageCrop.GetWidth()))),
+              std::min(height_, static_cast<unsigned int>(std::ceil(imageCrop.GetHeight())))
+              );
+    }
+
+
+    void GetCrop(unsigned int& x,
+                 unsigned int& y,
+                 unsigned int& width,
+                 unsigned int& height) const;
+
+    void SetAngle(double angle);
+
+    void SetPan(double x,
+                double y);
+
+    void SetFlipVertical(bool flip); //  mirrors image around an horizontal axis (note: flip is applied before the rotation !)
+
+    void SetFlipHorizontal(bool flip); //  mirrors image around a vertical axis (note: flip is applied before the rotation !)
+
+    void SetResizeable(bool resizeable)
+    {
+      geometry_.SetResizeable(resizeable);
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 bool emitLayerEditedEvent = true);
+
+    bool HasSize() const
+    {
+      return hasSize_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    virtual Extent2D GetSceneExtent(bool minimal) const;
+
+    virtual bool GetPixel(unsigned int& imageX,
+                          unsigned int& imageY,
+                          double sceneX,
+                          double sceneY) const;
+
+    void SetPixelSpacing(double x,
+                         double y,
+                         bool emitLayerEditedEvent = true);
+
+    void GetCenter(double& centerX,
+                   double& centerY) const;
+
+    virtual void GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */,
+                                 size_t index) const;
+
+    virtual size_t GetControlPointCount() const;
+
+    bool LookupControlPoint(ControlPoint& cpScene /* out */,
+                            double x,
+                            double y,
+                            double zoom,
+                            double viewportDistance) const;
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const = 0;
+
+    RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const
+    {
+      return prefferedPhotometricDisplayMode_;
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const = 0;
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const = 0;
+
+    virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs)
+    {
+      return 0;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerCropTracker.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,145 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayerCropTracker.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class RadiographyLayerCropTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    unsigned int  sourceCropX_;
+    unsigned int  sourceCropY_;
+    unsigned int  sourceCropWidth_;
+    unsigned int  sourceCropHeight_;
+    unsigned int  targetCropX_;
+    unsigned int  targetCropY_;
+    unsigned int  targetCropWidth_;
+    unsigned int  targetCropHeight_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerCropTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceCropX_(tracker.cropX_),
+      sourceCropY_(tracker.cropY_),
+      sourceCropWidth_(tracker.cropWidth_),
+      sourceCropHeight_(tracker.cropHeight_)
+    {
+      tracker.accessor_.GetLayer().GetCrop(targetCropX_, targetCropY_,
+                                           targetCropWidth_, targetCropHeight_);
+    }
+  };
+
+
+  RadiographyLayerCropTracker::RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           const Deprecated::ViewportGeometry& view,
+                                                           size_t layer,
+                                                           const ControlPoint& startControlPoint) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    startControlPoint_(startControlPoint)
+  {
+    if (accessor_.IsValid())
+    {
+      accessor_.GetLayer().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);
+    }
+  }
+
+
+  void RadiographyLayerCropTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerCropTracker::MouseUp()
+  {
+    if (accessor_.IsValid())
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerCropTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY,
+                                              const std::vector<Deprecated::Touch>& displayTouches,
+                                              const std::vector<Deprecated::Touch>& sceneTouches)
+  {
+    if (accessor_.IsValid())
+    {
+      unsigned int x, y;
+        
+      RadiographyLayer& layer = accessor_.GetLayer();
+      if (layer.GetPixel(x, y, sceneX, sceneY))
+      {
+        unsigned int targetX, targetWidth;
+
+        if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner ||
+            startControlPoint_.index == RadiographyControlPointType_BottomLeftCorner)
+        {
+          targetX = std::min(x, cropX_ + cropWidth_);
+          targetWidth = cropX_ + cropWidth_ - targetX;
+        }
+        else
+        {
+          targetX = cropX_;
+          targetWidth = std::max(x, cropX_) - cropX_;
+        }
+
+        unsigned int targetY, targetHeight;
+
+        if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner ||
+            startControlPoint_.index == RadiographyControlPointType_TopRightCorner)
+        {
+          targetY = std::min(y, cropY_ + cropHeight_);
+          targetHeight = cropY_ + cropHeight_ - targetY;
+        }
+        else
+        {
+          targetY = cropY_;
+          targetHeight = std::max(y, cropY_) - cropY_;
+        }
+
+        layer.SetCrop(targetX, targetY, targetWidth, targetHeight);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerCropTracker.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "../Deprecated/Toolbox/ViewportGeometry.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerCropTracker : public Deprecated::IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    ControlPoint                     startControlPoint_;
+    unsigned int                     cropX_;
+    unsigned int                     cropY_;
+    unsigned int                     cropWidth_;
+    unsigned int                     cropHeight_;
+
+  public:
+    RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                const Deprecated::ViewportGeometry& view,
+                                size_t layer,
+                                const ControlPoint& startControlPoint);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Deprecated::Touch>& displayTouches,
+                           const std::vector<Deprecated::Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerMaskTracker.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,140 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayerMaskTracker.h"
+#include "RadiographyMaskLayer.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class RadiographyLayerMaskTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    ControlPoint sourceSceneCp_;
+    ControlPoint targetSceneCp_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
+      if (maskLayer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      unsigned int ix, iy; // image coordinates
+      if (maskLayer->GetPixel(ix, iy, sourceSceneCp_.x, sourceSceneCp_.y))
+      {
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), sourceSceneCp_.index);
+      }
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
+      if (maskLayer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      unsigned int ix, iy; // image coordinates
+      if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y))
+      {
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index);
+      }
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerMaskTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceSceneCp_(tracker.startSceneCp_),
+      targetSceneCp_(tracker.endSceneCp_)
+    {
+      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&(tracker.accessor_.GetLayer()));
+      if (maskLayer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      unsigned int ix, iy; // image coordinates
+      if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y))
+      {
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index);
+      }
+    }
+  };
+
+
+  RadiographyLayerMaskTracker::RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           const Deprecated::ViewportGeometry& view,
+                                                           size_t layer,
+                                                           const ControlPoint& startSceneControlPoint) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    startSceneCp_(startSceneControlPoint),
+    endSceneCp_(startSceneControlPoint)
+  {
+  }
+
+
+  void RadiographyLayerMaskTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerMaskTracker::MouseUp()
+  {
+    if (accessor_.IsValid() && startSceneCp_.x != endSceneCp_.x && startSceneCp_.y != endSceneCp_.y)
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerMaskTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY,
+                                              const std::vector<Deprecated::Touch>& displayTouches,
+                                              const std::vector<Deprecated::Touch>& sceneTouches)
+  {
+    if (accessor_.IsValid())
+    {
+      unsigned int ix, iy; // image coordinates
+
+      RadiographyLayer& layer = accessor_.GetLayer();
+      if (layer.GetPixel(ix, iy, sceneX, sceneY))
+      {
+        endSceneCp_ = ControlPoint(sceneX, sceneY, startSceneCp_.index);
+
+        RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
+        if (maskLayer == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), startSceneCp_.index);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerMaskTracker.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "../Deprecated/Toolbox/ViewportGeometry.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerMaskTracker : public Deprecated::IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    ControlPoint                     startSceneCp_;
+    ControlPoint                     endSceneCp_;
+
+  public:
+    RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                const Deprecated::ViewportGeometry& view,
+                                size_t layer,
+                                const ControlPoint& startSceneControlPoint);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Deprecated::Touch>& displayTouches,
+                           const std::vector<Deprecated::Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerMoveTracker.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,126 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayerMoveTracker.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class RadiographyLayerMoveTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    double  sourceX_;
+    double  sourceY_;
+    double  targetX_;
+    double  targetY_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetPan(sourceX_, sourceY_);
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetPan(targetX_, targetY_);
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerMoveTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceX_(tracker.panX_),
+      sourceY_(tracker.panY_),
+      targetX_(tracker.accessor_.GetLayer().GetGeometry().GetPanX()),
+      targetY_(tracker.accessor_.GetLayer().GetGeometry().GetPanY())
+    {
+    }
+  };
+
+
+  RadiographyLayerMoveTracker::RadiographyLayerMoveTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           size_t layer,
+                                                           double x,
+                                                           double y,
+                                                           bool oneAxis) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    clickX_(x),
+    clickY_(y),
+    oneAxis_(oneAxis)
+  {
+    if (accessor_.IsValid())
+    {
+      panX_ = accessor_.GetLayer().GetGeometry().GetPanX();
+      panY_ = accessor_.GetLayer().GetGeometry().GetPanY();
+    }
+  }
+
+
+  void RadiographyLayerMoveTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+  
+  void RadiographyLayerMoveTracker::MouseUp()
+  {
+    if (accessor_.IsValid())
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerMoveTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY,
+                                              const std::vector<Deprecated::Touch>& displayTouches,
+                                              const std::vector<Deprecated::Touch>& sceneTouches)
+  {
+    if (accessor_.IsValid())
+    {
+      double dx = sceneX - clickX_;
+      double dy = sceneY - clickY_;
+
+      if (oneAxis_)
+      {
+        if (fabs(dx) > fabs(dy))
+        {
+          accessor_.GetLayer().SetPan(dx + panX_, panY_);
+        }
+        else
+        {
+          accessor_.GetLayer().SetPan(panX_, dy + panY_);
+        }
+      }
+      else
+      {
+        accessor_.GetLayer().SetPan(dx + panX_, dy + panY_);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerMoveTracker.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerMoveTracker : public Deprecated::IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+    
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    double                           clickX_;
+    double                           clickY_;
+    double                           panX_;
+    double                           panY_;
+    bool                             oneAxis_;
+
+  public:
+    RadiographyLayerMoveTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                size_t layer,
+                                double x,
+                                double y,
+                                bool oneAxis);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Deprecated::Touch>& displayTouches,
+                           const std::vector<Deprecated::Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerResizeTracker.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,188 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayerResizeTracker.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+
+namespace OrthancStone
+{
+  static double ComputeDistance(double x1,
+                                double y1,
+                                double x2,
+                                double y2)
+  {
+    double dx = x1 - x2;
+    double dy = y1 - y2;
+    return sqrt(dx * dx + dy * dy);
+  }
+      
+
+  class RadiographyLayerResizeTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    double   sourceSpacingX_;
+    double   sourceSpacingY_;
+    double   sourcePanX_;
+    double   sourcePanY_;
+    double   targetSpacingX_;
+    double   targetSpacingY_;
+    double   targetPanX_;
+    double   targetPanY_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
+      layer.SetPan(sourcePanX_, sourcePanY_);
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
+      layer.SetPan(targetPanX_, targetPanY_);
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerResizeTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceSpacingX_(tracker.originalSpacingX_),
+      sourceSpacingY_(tracker.originalSpacingY_),
+      sourcePanX_(tracker.originalPanX_),
+      sourcePanY_(tracker.originalPanY_),
+      targetSpacingX_(tracker.accessor_.GetLayer().GetGeometry().GetPixelSpacingX()),
+      targetSpacingY_(tracker.accessor_.GetLayer().GetGeometry().GetPixelSpacingY()),
+      targetPanX_(tracker.accessor_.GetLayer().GetGeometry().GetPanX()),
+      targetPanY_(tracker.accessor_.GetLayer().GetGeometry().GetPanY())
+    {
+    }
+  };
+
+
+  RadiographyLayerResizeTracker::RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
+                                                               RadiographyScene& scene,
+                                                               size_t layer,
+                                                               const ControlPoint& startControlPoint,
+                                                               bool roundScaling) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    roundScaling_(roundScaling)
+  {
+    if (accessor_.IsValid() &&
+        accessor_.GetLayer().GetGeometry().IsResizeable())
+    {
+      originalSpacingX_ = accessor_.GetLayer().GetGeometry().GetPixelSpacingX();
+      originalSpacingY_ = accessor_.GetLayer().GetGeometry().GetPixelSpacingY();
+      originalPanX_ = accessor_.GetLayer().GetGeometry().GetPanX();
+      originalPanY_ = accessor_.GetLayer().GetGeometry().GetPanY();
+
+      size_t oppositeControlPointType;
+      switch (startControlPoint.index)
+      {
+        case RadiographyControlPointType_TopLeftCorner:
+          oppositeControlPointType = RadiographyControlPointType_BottomRightCorner;
+          break;
+
+        case RadiographyControlPointType_TopRightCorner:
+          oppositeControlPointType = RadiographyControlPointType_BottomLeftCorner;
+          break;
+
+        case RadiographyControlPointType_BottomLeftCorner:
+          oppositeControlPointType = RadiographyControlPointType_TopRightCorner;
+          break;
+
+        case RadiographyControlPointType_BottomRightCorner:
+          oppositeControlPointType = RadiographyControlPointType_TopLeftCorner;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      accessor_.GetLayer().GetControlPoint(startOppositeControlPoint_, oppositeControlPointType);
+
+      double d = ComputeDistance(startControlPoint.x, startControlPoint.y, startOppositeControlPoint_.x, startOppositeControlPoint_.y);
+      if (d >= std::numeric_limits<float>::epsilon())
+      {
+        baseScaling_ = 1.0 / d;
+      }
+      else
+      {
+        // Avoid division by zero in extreme cases
+        accessor_.Invalidate();
+      }
+    }
+  }
+
+
+  void RadiographyLayerResizeTracker::Render(CairoContext& context,
+                                             double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerResizeTracker::MouseUp()
+  {
+    if (accessor_.IsValid() &&
+        accessor_.GetLayer().GetGeometry().IsResizeable())
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerResizeTracker::MouseMove(int displayX,
+                                                int displayY,
+                                                double sceneX,
+                                                double sceneY,
+                                                const std::vector<Deprecated::Touch>& displayTouches,
+                                                const std::vector<Deprecated::Touch>& sceneTouches)
+  {
+    static const double ROUND_SCALING = 0.1;
+        
+    if (accessor_.IsValid() &&
+        accessor_.GetLayer().GetGeometry().IsResizeable())
+    {
+      double scaling = ComputeDistance(startOppositeControlPoint_.x, startOppositeControlPoint_.y, sceneX, sceneY) * baseScaling_;
+
+      if (roundScaling_)
+      {
+        scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING);
+      }
+          
+      RadiographyLayer& layer = accessor_.GetLayer();
+      layer.SetPixelSpacing(scaling * originalSpacingX_,
+                            scaling * originalSpacingY_);
+
+      // Keep the opposite corner at a fixed location
+      ControlPoint currentOppositeCorner;
+      layer.GetControlPoint(currentOppositeCorner, startOppositeControlPoint_.index);
+      layer.SetPan(layer.GetGeometry().GetPanX() + startOppositeControlPoint_.x - currentOppositeCorner.x,
+                   layer.GetGeometry().GetPanY() + startOppositeControlPoint_.y - currentOppositeCorner.y);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerResizeTracker.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayerResizeTracker : public Deprecated::IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    bool                             roundScaling_;
+    double                           originalSpacingX_;
+    double                           originalSpacingY_;
+    double                           originalPanX_;
+    double                           originalPanY_;
+    ControlPoint                     startOppositeControlPoint_;
+    double                           baseScaling_;
+
+  public:
+    RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
+                                  RadiographyScene& scene,
+                                  size_t layer,
+                                  const ControlPoint& startControlPoint,
+                                  bool roundScaling);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Deprecated::Touch>& displayTouches,
+                           const std::vector<Deprecated::Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerRotateTracker.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,156 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayerRotateTracker.h"
+
+#include "RadiographySceneCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/math/constants/constants.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+namespace OrthancStone
+{
+  class RadiographyLayerRotateTracker::UndoRedoCommand : public RadiographySceneCommand
+  {
+  private:
+    double  sourceAngle_;
+    double  targetAngle_;
+
+    static int ToDegrees(double angle)
+    {
+      return boost::math::iround(angle * 180.0 / boost::math::constants::pi<double>());
+    }
+      
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const
+    {
+      LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
+      layer.SetAngle(sourceAngle_);
+    }
+
+    virtual void RedoInternal(RadiographyLayer& layer) const
+    {
+      LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
+      layer.SetAngle(targetAngle_);
+    }
+
+  public:
+    UndoRedoCommand(const RadiographyLayerRotateTracker& tracker) :
+      RadiographySceneCommand(tracker.accessor_),
+      sourceAngle_(tracker.originalAngle_),
+      targetAngle_(tracker.accessor_.GetLayer().GetGeometry().GetAngle())
+    {
+    }
+  };
+
+
+  bool RadiographyLayerRotateTracker::ComputeAngle(double& angle /* out */,
+                                                   double sceneX,
+                                                   double sceneY) const
+  {
+    Vector u;
+    LinearAlgebra::AssignVector(u, sceneX - centerX_, sceneY - centerY_);
+
+    double nu = boost::numeric::ublas::norm_2(u);
+
+    if (!LinearAlgebra::IsCloseToZero(nu))
+    {
+      u /= nu;
+      angle = atan2(u[1], u[0]);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  RadiographyLayerRotateTracker::RadiographyLayerRotateTracker(UndoRedoStack& undoRedoStack,
+                                                               RadiographyScene& scene,
+                                                               const Deprecated::ViewportGeometry& view,
+                                                               size_t layer,
+                                                               double x,
+                                                               double y,
+                                                               bool roundAngles) :
+    undoRedoStack_(undoRedoStack),
+    accessor_(scene, layer),
+    roundAngles_(roundAngles)
+  {
+    if (accessor_.IsValid())
+    {
+      accessor_.GetLayer().GetCenter(centerX_, centerY_);
+      originalAngle_ = accessor_.GetLayer().GetGeometry().GetAngle();
+
+      double sceneX, sceneY;
+      view.MapDisplayToScene(sceneX, sceneY, x, y);
+
+      if (!ComputeAngle(clickAngle_, x, y))
+      {
+        accessor_.Invalidate();
+      }
+    }
+  }
+
+
+  void RadiographyLayerRotateTracker::Render(CairoContext& context,
+                                             double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void RadiographyLayerRotateTracker::MouseUp()
+  {
+    if (accessor_.IsValid())
+    {
+      undoRedoStack_.Add(new UndoRedoCommand(*this));
+    }
+  }
+
+  
+  void RadiographyLayerRotateTracker::MouseMove(int displayX,
+                                                int displayY,
+                                                double sceneX,
+                                                double sceneY,
+                                                const std::vector<Deprecated::Touch>& displayTouches,
+                                                const std::vector<Deprecated::Touch>& sceneTouches)
+  {
+    static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>(); 
+        
+    double angle;
+        
+    if (accessor_.IsValid() &&
+        ComputeAngle(angle, sceneX, sceneY))
+    {
+      angle = angle - clickAngle_ + originalAngle_;
+
+      if (roundAngles_)
+      {
+        angle = boost::math::round<double>((angle / ROUND_ANGLE) * ROUND_ANGLE);
+      }
+          
+      accessor_.GetLayer().SetAngle(angle);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyLayerRotateTracker.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,75 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "../Deprecated/Toolbox/ViewportGeometry.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+
+namespace OrthancStone
+{
+  class RadiographyLayerRotateTracker : public Deprecated::IWorldSceneMouseTracker
+  {
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&                   undoRedoStack_;
+    RadiographyScene::LayerAccessor  accessor_;
+    double                           centerX_;
+    double                           centerY_;
+    double                           originalAngle_;
+    double                           clickAngle_;
+    bool                             roundAngles_;
+
+    bool ComputeAngle(double& angle /* out */,
+                      double sceneX,
+                      double sceneY) const;
+
+  public:
+    RadiographyLayerRotateTracker(UndoRedoStack& undoRedoStack,
+                                  RadiographyScene& scene,
+                                  const Deprecated::ViewportGeometry& view,
+                                  size_t layer,
+                                  double x,
+                                  double y,
+                                  bool roundAngles);
+
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Deprecated::Touch>& displayTouches,
+                           const std::vector<Deprecated::Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyMaskLayer.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,199 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyMaskLayer.h"
+#include "RadiographyDicomLayer.h"
+
+#include "RadiographyScene.h"
+#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 = 0x77;
+  const unsigned char OUT_MASK_VALUE = 0xFF;
+
+  const AffineTransform2D& RadiographyMaskLayer::GetTransform() const
+  {
+    return dicomLayer_.GetTransform();
+  }
+
+  const AffineTransform2D& RadiographyMaskLayer::GetTransformInverse() const
+  {
+    return dicomLayer_.GetTransformInverse();
+  }
+
+  bool RadiographyMaskLayer::GetPixel(unsigned int& imageX,
+                        unsigned int& imageY,
+                        double sceneX,
+                        double sceneY) const
+  {
+    return dicomLayer_.GetPixel(imageX, imageY, sceneX, sceneY);
+  }
+
+  std::string RadiographyMaskLayer::GetInstanceId() const
+  {
+    return dicomLayer_.GetInstanceId();
+  }
+
+  void RadiographyMaskLayer::SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index)
+  {
+    if (index < corners_.size())
+      corners_[index] = corner;
+    else
+      corners_.push_back(corner);
+    invalidated_ = true;
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  void RadiographyMaskLayer::SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners)
+  {
+    corners_ = corners;
+    invalidated_ = true;
+
+    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
+  }
+
+  Extent2D RadiographyMaskLayer::GetSceneExtent(bool minimal) const
+  {
+    if (!minimal)
+    {
+      return RadiographyLayer::GetSceneExtent(minimal);
+    }
+    else
+    { // get the extent of the in-mask area
+      Extent2D sceneExtent;
+
+      for (std::vector<Orthanc::ImageProcessing::ImagePoint>::const_iterator corner = corners_.begin(); corner != corners_.end(); ++corner)
+      {
+        double x = static_cast<double>(corner->GetX());
+        double y = static_cast<double>(corner->GetY());
+
+        dicomLayer_.GetTransform().Apply(x, y);
+        sceneExtent.AddPoint(x, y);
+      }
+      return sceneExtent;
+    }
+  }
+
+
+
+  void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer,
+                                    const AffineTransform2D& viewTransform,
+                                    ImageInterpolation interpolation,
+                                    float windowCenter,
+                                    float windowWidth,
+                                    bool applyWindowing) const
+  {
+    if (dicomLayer_.GetWidth() == 0 || dicomLayer_.GetSourceImage() == NULL) // nothing to do if the DICOM layer is not displayed (or not loaded)
+      return;
+
+    if (invalidated_)
+    {
+      mask_.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, dicomLayer_.GetWidth(), dicomLayer_.GetHeight(), false));
+
+      DrawMask();
+
+      invalidated_ = false;
+    }
+
+    {// rendering
+      if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+
+      unsigned int cropX, cropY, cropWidth, cropHeight;
+      dicomLayer_.GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+      const AffineTransform2D t = AffineTransform2D::Combine(
+            viewTransform, dicomLayer_.GetTransform(),
+            AffineTransform2D::CreateOffset(cropX, cropY));
+
+      Orthanc::ImageAccessor cropped;
+      mask_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
+
+      Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
+
+
+      unsigned int x1, y1, x2, y2;
+      if (!OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
+                                                      t.GetHomogeneousMatrix(),
+                                                      cropped.GetWidth(),
+                                                      cropped.GetHeight(),
+                                                      buffer.GetWidth(),
+                                                      buffer.GetHeight()))
+      {
+        return;  // layer is outside the buffer
+      }
+
+      t.Apply(tmp, cropped, ImageInterpolation_Nearest, true /* clear */);
+
+      // 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
+      for (unsigned int y = y1; y <= y2; 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 = x1; x <= x2; x++, p++, q++)
+        {
+          if (*p != IN_MASK_VALUE)
+            *q = foreground_;
+          // else keep the underlying pixel value
+        }
+      }
+
+    }
+  }
+
+  void RadiographyMaskLayer::DrawMask() const
+  {
+    // first fill the complete image
+    Orthanc::ImageProcessing::Set(*mask_, OUT_MASK_VALUE);
+
+    // clip corners
+    std::vector<Orthanc::ImageProcessing::ImagePoint> clippedCorners;
+    for (size_t i = 0; i < corners_.size(); i++)
+    {
+      clippedCorners.push_back(corners_[i]);
+      clippedCorners[i].ClipTo(0, mask_->GetWidth() - 1, 0, mask_->GetHeight() - 1);
+    }
+
+    // fill mask
+    Orthanc::ImageProcessing::FillPolygon(*mask_, clippedCorners, IN_MASK_VALUE);
+
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyMaskLayer.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,146 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
+
+#include <Core/Compatibility.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+  class RadiographyDicomLayer;
+
+  class RadiographyMaskLayer : public RadiographyLayer
+  {
+  private:
+    std::vector<Orthanc::ImageProcessing::ImagePoint>            corners_;
+    const RadiographyDicomLayer&      dicomLayer_;
+    mutable bool                      invalidated_;
+    float                             foreground_;
+
+    mutable std::unique_ptr<Orthanc::ImageAccessor>  mask_;
+  public:
+    RadiographyMaskLayer(const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer,
+                         float foreground) :
+      RadiographyLayer(scene),
+      dicomLayer_(dicomLayer),
+      invalidated_(true),
+      foreground_(foreground)
+    {
+    }
+
+    virtual size_t GetApproximateMemoryUsage() const
+    {
+      size_t size = 0;
+      if (mask_.get() != NULL)
+      {
+        size += mask_->GetPitch() * mask_->GetHeight();
+      }
+
+      return size;
+    }
+
+
+    void SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners);
+    void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index);
+
+    const std::vector<Orthanc::ImageProcessing::ImagePoint>& GetCorners() const
+    {
+      return corners_;
+    }
+
+    float GetForeground() const
+    {
+      return foreground_;
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation,
+                        float windowCenter,
+                        float windowWidth,
+                        bool applyWindowing) const;
+
+    std::string GetInstanceId() const;
+
+    virtual size_t GetControlPointCount() const
+    {
+      return corners_.size();
+    }
+
+    virtual void GetControlPoint(ControlPoint& cpScene,
+                                         size_t index) const
+    {
+      ControlPoint cp(corners_[index].GetX(), corners_[index].GetY(), index);
+
+      // transforms image coordinates into scene coordinates
+      GetTransform().Apply(cp.x, cp.y);
+      cpScene = cp;
+    }
+
+    virtual Extent2D GetSceneExtent(bool minimal) const;
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const
+    {
+      return false;
+    }
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const
+    {
+      minValue = 0;
+      maxValue = 0;
+
+      if (foreground_ < 0)
+      {
+        minValue = foreground_;
+      }
+
+      if (foreground_ > 0)
+      {
+        maxValue = foreground_;
+      }
+
+      return true;
+
+    }
+
+    virtual bool GetPixel(unsigned int& imageX,
+                          unsigned int& imageY,
+                          double sceneX,
+                          double sceneY) const;
+
+  protected:
+    virtual const AffineTransform2D& GetTransform() const;
+
+    virtual const AffineTransform2D& GetTransformInverse() const;
+
+
+  private:
+    void DrawMask() const;
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyScene.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,968 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyScene.h"
+
+#include "RadiographyAlphaLayer.h"
+#include "RadiographyDicomLayer.h"
+#include "RadiographyTextLayer.h"
+#include "RadiographyMaskLayer.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
+#include "../Scene2D/CairoCompositor.h"
+#include "../Scene2D/FloatTextureSceneLayer.h"
+#include "../Scene2D/TextSceneLayer.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/PamReader.h>
+#include <Core/Images/PamWriter.h>
+#include <Core/Images/PngWriter.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+
+namespace OrthancStone
+{
+  RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
+                                                 size_t index) :
+    scene_(scene),
+    index_(index)
+  {
+    Layers::iterator layer = scene.layers_.find(index);
+    if (layer == scene.layers_.end())
+    {
+      layer_ = NULL;
+    }
+    else
+    {
+      assert(layer->second != NULL);
+      layer_ = layer->second;
+    }
+  }
+
+
+  RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
+                                                 double x,
+                                                 double y) :
+    scene_(scene),
+    index_(0)  // Dummy initialization
+  {
+    if (scene.LookupLayer(index_, x, y))
+    {
+      Layers::iterator layer = scene.layers_.find(index_);
+
+      if (layer == scene.layers_.end())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      else
+      {
+        assert(layer->second != NULL);
+        layer_ = layer->second;
+      }
+    }
+    else
+    {
+      layer_ = NULL;
+    }
+  }
+
+
+  RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const
+  {
+    if (IsValid())
+    {
+      return scene_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  size_t RadiographyScene::LayerAccessor::GetIndex() const
+  {
+    if (IsValid())
+    {
+      return index_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  
+  RadiographyLayer& RadiographyScene::LayerAccessor::GetLayer() const
+  {
+    if (IsValid())
+    {
+      return *layer_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  void RadiographyScene::_RegisterLayer(RadiographyLayer* layer)
+  {
+    std::unique_ptr<RadiographyLayer> raii(layer);
+
+    // LOG(INFO) << "Registering layer: " << countLayers_;
+
+    size_t index = nextLayerIndex_++;
+    raii->SetIndex(index);
+    layers_[index] = raii.release();
+  }
+
+  RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer)
+  {
+    if (layer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    _RegisterLayer(layer);
+
+    BroadcastMessage(GeometryChangedMessage(*this, *layer));
+    BroadcastMessage(ContentChangedMessage(*this, *layer));
+    Register<RadiographyLayer::LayerEditedMessage>(*layer, &RadiographyScene::OnLayerEdited);
+
+    return *layer;
+  }
+
+  size_t RadiographyScene::GetApproximateMemoryUsage() const
+  {
+    size_t size = 0;
+    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
+    {
+      size += it->second->GetApproximateMemoryUsage();
+    }
+    return size;
+  }
+
+  void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message)
+  {
+    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
+  }
+
+  
+  RadiographyScene::RadiographyScene() :
+    nextLayerIndex_(0),
+    hasWindowing_(false),
+    windowingCenter_(0),  // Dummy initialization
+    windowingWidth_(0)    // Dummy initialization
+  {
+  }
+
+
+  RadiographyScene::~RadiographyScene()
+  {
+    for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+  RadiographyPhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const
+  {
+    // return the mode of the first layer who "cares" about its display mode (normaly, the one and only layer that is a DicomLayer)
+    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
+    {
+      if (it->second->GetPreferredPhotomotricDisplayMode() != RadiographyPhotometricDisplayMode_Default)
+      {
+        return it->second->GetPreferredPhotomotricDisplayMode();
+      }
+    }
+
+    return RadiographyPhotometricDisplayMode_Default;
+  }
+
+
+  void RadiographyScene::GetLayersIndexes(std::vector<size_t>& output) const
+  {
+    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
+    {
+      output.push_back(it->first);
+    }
+  }
+
+  void RadiographyScene::RemoveLayer(size_t layerIndex)
+  {
+    LOG(INFO) << "Removing layer: " << layerIndex;
+
+    Layers::iterator found = layers_.find(layerIndex);
+
+    if (found == layers_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      delete found->second;
+      
+      layers_.erase(found);
+      
+      LOG(INFO) << "Removing layer, there are now : " << layers_.size() << " layers";
+
+      _OnLayerRemoved();
+
+      BroadcastMessage(RadiographyScene::LayerRemovedMessage(*this, layerIndex));
+    }
+  }
+
+  const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const
+  {
+    Layers::const_iterator found = layers_.find(layerIndex);
+    
+    if (found == layers_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      return *found->second;
+    }
+  }
+
+  RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex)
+  {
+    Layers::const_iterator found = layers_.find(layerIndex);
+
+    if (found == layers_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      return *found->second;
+    }
+  }
+
+  bool RadiographyScene::GetWindowing(float& center,
+                                      float& width) const
+  {
+    if (hasWindowing_)
+    {
+      center = windowingCenter_;
+      width = windowingWidth_;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void RadiographyScene::GetWindowingWithDefault(float& center,
+                                                 float& width) const
+  {
+    if (!GetWindowing(center, width))
+    {
+      center = 128;
+      width = 256;
+    }
+  }
+
+
+  void RadiographyScene::SetWindowing(float center,
+                                      float width)
+  {
+    hasWindowing_ = true;
+    windowingCenter_ = center;
+    windowingWidth_ = width;
+
+    BroadcastMessage(RadiographyScene::WindowingChangedMessage(*this));
+  }
+
+
+  RadiographyLayer& RadiographyScene::UpdateText(size_t layerIndex,
+                                                 const std::string& utf8,
+                                                 const std::string& font,
+                                                 unsigned int fontSize,
+                                                 uint8_t foreground)
+  {
+    RadiographyTextLayer& textLayer = dynamic_cast<RadiographyTextLayer&>(GetLayer(layerIndex));
+    textLayer.SetText(utf8, font, fontSize, foreground);
+
+    BroadcastMessage(RadiographyScene::ContentChangedMessage(*this, textLayer));
+    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, textLayer));
+    return textLayer;
+  }
+
+
+  RadiographyLayer& RadiographyScene::LoadText(const std::string& utf8,
+                                               const std::string& font,
+                                               unsigned int fontSize,
+                                               uint8_t foreground,
+                                               RadiographyLayer::Geometry* centerGeometry,
+                                               bool isCenterGeometry)
+  {
+    std::unique_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(*this));
+    alpha->SetText(utf8, font, fontSize, foreground);
+    if (centerGeometry != NULL)
+    {
+      if (isCenterGeometry)
+      {
+        // modify geometry to reference the top left corner
+        double tlx = centerGeometry->GetPanX();
+        double tly = centerGeometry->GetPanY();
+        Extent2D textExtent = alpha->GetSceneExtent(false);
+        tlx = tlx - (textExtent.GetWidth() / 2) * centerGeometry->GetPixelSpacingX();
+        tly = tly - (textExtent.GetHeight() / 2) * centerGeometry->GetPixelSpacingY();
+        centerGeometry->SetPan(tlx, tly);
+      }
+      alpha->SetGeometry(*centerGeometry);
+    }
+
+    RadiographyLayer& registeredLayer = RegisterLayer(alpha.release());
+
+    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, registeredLayer));
+    return registeredLayer;
+  }
+
+
+  RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width,
+                                                    unsigned int height,
+                                                    RadiographyLayer::Geometry* geometry)
+  {
+    std::unique_ptr<Orthanc::Image>  block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
+
+    for (unsigned int padding = 0;
+         (width > 2 * padding) && (height > 2 * padding);
+         padding++)
+    {
+      uint8_t color;
+      if (255 > 10 * padding)
+      {
+        color = 255 - 10 * padding;
+      }
+      else
+      {
+        color = 0;
+      }
+
+      Orthanc::ImageAccessor region;
+      block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding);
+      Orthanc::ImageProcessing::Set(region, color);
+    }
+
+    return LoadAlphaBitmap(block.release(), geometry);
+  }
+
+  RadiographyLayer& RadiographyScene::LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
+                                               const RadiographyDicomLayer& dicomLayer,
+                                               float foreground,
+                                               RadiographyLayer::Geometry* geometry)
+  {
+    std::unique_ptr<RadiographyMaskLayer>  mask(new RadiographyMaskLayer(*this, dicomLayer, foreground));
+    mask->SetCorners(corners);
+    if (geometry != NULL)
+    {
+      mask->SetGeometry(*geometry);
+    }
+
+    return RegisterLayer(mask.release());
+  }
+
+
+  RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry)
+  {
+    std::unique_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(*this));
+    alpha->SetAlpha(bitmap);
+    if (geometry != NULL)
+    {
+      alpha->SetGeometry(*geometry);
+    }
+
+    return RegisterLayer(alpha.release());
+  }
+
+  RadiographyLayer& RadiographyScene::LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
+                                                     const std::string& instance,
+                                                     unsigned int frame,
+                                                     Deprecated::DicomFrameConverter* converter,  // takes ownership
+                                                     RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
+                                                     RadiographyLayer::Geometry* geometry)
+  {
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(*this)));
+
+    layer.SetInstance(instance, frame);
+
+    if (geometry != NULL)
+    {
+      layer.SetGeometry(*geometry);
+    }
+
+    layer.SetDicomFrameConverter(converter);
+    layer.SetSourceImage(dicomImage);
+    layer.SetPreferredPhotomotricDisplayMode(preferredPhotometricDisplayMode);
+
+    return layer;
+  }
+
+  RadiographyLayer& RadiographyScene::LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
+                                                     const std::string& instance,
+                                                     unsigned int frame,
+                                                     bool httpCompression,
+                                                     RadiographyLayer::Geometry* geometry)
+  {
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer( *this)));
+    layer.SetInstance(instance, frame);
+
+    if (geometry != NULL)
+    {
+      layer.SetGeometry(*geometry);
+    }
+
+    {
+      Deprecated::IWebService::HttpHeaders headers;
+      std::string uri = "/instances/" + instance + "/tags";
+
+      orthanc.GetBinaryAsync(
+            uri, headers,
+            new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
+            (GetSharedObserver(), &RadiographyScene::OnTagsReceived), NULL,
+            new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
+    }
+
+    {
+      Deprecated::IWebService::HttpHeaders headers;
+      headers["Accept"] = "image/x-portable-arbitrarymap";
+
+      if (httpCompression)
+      {
+        headers["Accept-Encoding"] = "gzip";
+      }
+
+      std::string uri = ("/instances/" + instance + "/frames/" +
+                         boost::lexical_cast<std::string>(frame) + "/image-uint16");
+
+      orthanc.GetBinaryAsync(
+            uri, headers,
+            new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
+            (GetSharedObserver(), &RadiographyScene::OnFrameReceived), NULL,
+            new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
+    }
+
+    return layer;
+  }
+
+
+  RadiographyLayer& RadiographyScene::LoadDicomWebFrame(Deprecated::IWebService& web)
+  {
+    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(*this));
+
+
+    return layer;
+  }
+
+
+
+  void RadiographyScene::OnTagsReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message)
+  {
+    size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>
+        (message.GetPayload()).GetValue();
+
+    VLOG(1) << "JSON received: " << message.GetUri().c_str()
+            << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
+
+    Layers::iterator layer = layers_.find(index);
+    if (layer != layers_.end())
+    {
+      assert(layer->second != NULL);
+
+      OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize());
+      dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetDicomTags(dicom);
+
+      float c, w;
+      if (!hasWindowing_ &&
+          layer->second->GetDefaultWindowing(c, w))
+      {
+        hasWindowing_ = true;
+        windowingCenter_ = c;
+        windowingWidth_ = w;
+      }
+
+      BroadcastMessage(GeometryChangedMessage(*this, *(layer->second)));
+    }
+  }
+
+
+  void RadiographyScene::OnFrameReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message)
+  {
+    size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue();
+
+    VLOG(1) << "DICOM frame received: " << message.GetUri().c_str()
+            << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
+
+    Layers::iterator layer = layers_.find(index);
+    if (layer != layers_.end())
+    {
+      assert(layer->second != NULL);
+
+      std::string content;
+      if (message.GetAnswerSize() > 0)
+      {
+        content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize());
+      }
+
+      std::unique_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
+      reader->ReadFromMemory(content);
+      dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release());
+
+      BroadcastMessage(ContentChangedMessage(*this, *(layer->second)));
+    }
+  }
+
+
+  Extent2D RadiographyScene::GetSceneExtent(bool minimal) const
+  {
+    Extent2D extent;
+
+    for (Layers::const_iterator it = layers_.begin();
+         it != layers_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      extent.Union(it->second->GetSceneExtent(minimal));
+    }
+
+    return extent;
+  }
+
+
+  void RadiographyScene::Render(Orthanc::ImageAccessor& buffer,
+                                const AffineTransform2D& viewTransform,
+                                ImageInterpolation interpolation,
+                                bool applyWindowing) const
+  {
+    // Render layers in the background-to-foreground order
+    for (size_t index = 0; index < nextLayerIndex_; index++)
+    {
+      try
+      {
+        Layers::const_iterator it = layers_.find(index);
+        if (it != layers_.end())
+        {
+          assert(it->second != NULL);
+          it->second->Render(buffer, viewTransform, interpolation, windowingCenter_, windowingWidth_, applyWindowing);
+        }
+      }
+      catch (Orthanc::OrthancException& ex)
+      {
+        LOG(ERROR) << "RadiographyScene::Render: " << index << ", OrthancException: " << ex.GetDetails();
+        throw ex; // rethrow because we want it to crash to see there's a problem !
+      }
+      catch (...)
+      {
+        LOG(ERROR) << "RadiographyScene::Render: " << index << ", unkown exception: ";
+        throw; // rethrow because we want it to crash to see there's a problem !
+      }
+    }
+  }
+
+
+  bool RadiographyScene::LookupLayer(size_t& index /* out */,
+                                     double x,
+                                     double y) const
+  {
+    // Render layers in the foreground-to-background order
+    for (size_t i = nextLayerIndex_; i > 0; i--)
+    {
+      index = i - 1;
+      Layers::const_iterator it = layers_.find(index);
+      if (it != layers_.end())
+      {
+        assert(it->second != NULL);
+        if (it->second->Contains(x, y))
+        {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+
+  void RadiographyScene::DrawBorder(CairoContext& context,
+                                    unsigned int layer,
+                                    double zoom)
+  {
+    Layers::const_iterator found = layers_.find(layer);
+
+    if (found != layers_.end())
+    {
+      context.SetSourceColor(255, 0, 0);
+      found->second->DrawBorders(context, zoom);
+    }
+  }
+
+
+  void RadiographyScene::GetRange(float& minValue,
+                                  float& maxValue) const
+  {
+    bool first = true;
+
+    for (Layers::const_iterator it = layers_.begin();
+         it != layers_.end(); it++)
+    {
+      assert(it->second != NULL);
+
+      float a, b;
+      if (it->second->GetRange(a, b))
+      {
+        if (first)
+        {
+          minValue = a;
+          maxValue = b;
+          first = false;
+        }
+        else
+        {
+          minValue = std::min(a, minValue);
+          maxValue = std::max(b, maxValue);
+        }
+      }
+    }
+
+    if (first)
+    {
+      minValue = 0;
+      maxValue = 0;
+    }
+  }
+
+  void RadiographyScene::ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
+                                                       const Orthanc::ImageAccessor& renderedScene,
+                                                       size_t layerIndex,
+                                                       bool isCropped,
+                                                       ImageInterpolation interpolation)
+  {
+    Extent2D sceneExtent = GetSceneExtent(isCropped);
+
+    double pixelSpacingX = sceneExtent.GetWidth() / renderedScene.GetWidth();
+    double pixelSpacingY = sceneExtent.GetHeight() / renderedScene.GetHeight();
+
+    AffineTransform2D view = AffineTransform2D::Combine(
+          AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
+          AffineTransform2D::CreateOffset(-sceneExtent.GetX1(), -sceneExtent.GetY1()));
+
+    AffineTransform2D layerToSceneTransform = AffineTransform2D::Combine(
+          view,
+          GetLayer(layerIndex).GetTransform());
+
+    AffineTransform2D sceneToLayerTransform = AffineTransform2D::Invert(layerToSceneTransform);
+    sceneToLayerTransform.Apply(layer, renderedScene, interpolation, false);
+  }
+
+  Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX,
+                                                  double pixelSpacingY,
+                                                  ImageInterpolation interpolation,
+                                                  bool invert,
+                                                  int64_t maxValue /* for inversion */,
+                                                  bool autoCrop,
+                                                  bool applyWindowing)
+  {
+    if (pixelSpacingX <= 0 ||
+        pixelSpacingY <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Extent2D extent = GetSceneExtent(autoCrop);
+
+    int w = boost::math::iround(extent.GetWidth() / pixelSpacingX);
+    int h = boost::math::iround(extent.GetHeight() / pixelSpacingY);
+
+    if (w < 0 || h < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    Orthanc::Image layers(Orthanc::PixelFormat_Float32,
+                          static_cast<unsigned int>(w),
+                          static_cast<unsigned int>(h), false);
+
+    AffineTransform2D view = AffineTransform2D::Combine(
+          AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
+          AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1()));
+
+    // wipe background before rendering
+    if (GetPreferredPhotomotricDisplayMode() == RadiographyPhotometricDisplayMode_Monochrome1)
+    {
+      Orthanc::ImageProcessing::Set(layers, 65535);
+    }
+    else
+    {
+      Orthanc::ImageProcessing::Set(layers, 0);
+    }
+
+    Render(layers, view, interpolation, applyWindowing);
+
+    std::unique_ptr<Orthanc::Image> rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16,
+                                                              layers.GetWidth(), layers.GetHeight(), false));
+
+    Orthanc::ImageProcessing::Convert(*rendered, layers);
+    if (invert)
+      Orthanc::ImageProcessing::Invert(*rendered, maxValue);
+
+    return rendered.release();
+  }
+
+
+  Orthanc::Image* RadiographyScene::ExportToCreateDicomRequestAndImage(Json::Value& createDicomRequestContent,
+                                                                       const Json::Value& dicomTags,
+                                                                       const std::string& parentOrthancId,
+                                                                       double pixelSpacingX,
+                                                                       double pixelSpacingY,
+                                                                       bool invert,
+                                                                       bool autoCrop,
+                                                                       ImageInterpolation interpolation)
+  {
+    LOG(INFO) << "Exporting RadiographyScene to DICOM";
+
+    std::unique_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, autoCrop, 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;
+
+    RadiographyPhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode();
+    if ((invert && photometricMode != RadiographyPhotometricDisplayMode_Monochrome2) ||
+        (!invert && photometricMode == RadiographyPhotometricDisplayMode_Monochrome1))
+    {
+      createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1";
+    }
+    else
+    {
+      createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME2";
+    }
+
+    // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to
+    // avoid floating-point numbers to grow over 16 characters,
+    // which would be invalid according to DICOM standard
+    // ("dciodvfy" would complain).
+    char buf[32];
+    sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
+
+    createDicomRequestContent["Tags"]["PixelSpacing"] = buf;
+
+    float center, width;
+    if (GetWindowing(center, width))
+    {
+      createDicomRequestContent["Tags"]["WindowCenter"] =
+          boost::lexical_cast<std::string>(boost::math::iround(center));
+
+      createDicomRequestContent["Tags"]["WindowWidth"] =
+          boost::lexical_cast<std::string>(boost::math::iround(width));
+    }
+
+    if (!parentOrthancId.empty())
+    {
+      createDicomRequestContent["Parent"] = parentOrthancId;
+    }
+
+    return rendered.release();
+  }
+
+
+  void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
+                                                    const Json::Value& dicomTags,
+                                                    const std::string& parentOrthancId,
+                                                    double pixelSpacingX,
+                                                    double pixelSpacingY,
+                                                    bool invert,
+                                                    bool autoCrop,
+                                                    ImageInterpolation interpolation,
+                                                    bool usePam)
+  {
+    LOG(INFO) << "Exporting RadiographyScene to DICOM";
+    VLOG(1) << "Exporting RadiographyScene to: export to image";
+
+    std::unique_ptr<Orthanc::Image> rendered(ExportToCreateDicomRequestAndImage(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation));
+
+    // convert the image into base64 for inclusing in the createDicomRequest
+    std::string base64;
+
+    {
+      std::string content;
+
+      if (usePam)
+      {
+        VLOG(1) << "Exporting RadiographyScene: convert to PAM";
+        Orthanc::PamWriter writer;
+        writer.WriteToMemory(content, *rendered);
+      }
+      else
+      {
+        Orthanc::PngWriter writer;
+        writer.WriteToMemory(content, *rendered);
+      }
+
+      VLOG(1) << "Exporting RadiographyScene: encoding to base64";
+      Orthanc::Toolbox::EncodeBase64(base64, content);
+    }
+
+    // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme
+    createDicomRequestContent["Content"] = ("data:" +
+                                            std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) +
+                                            ";base64," + base64);
+
+    VLOG(1) << "Exporting RadiographyScene: create-dicom request is ready";
+  }
+
+
+  void RadiographyScene::ExportDicom(Deprecated::OrthancApiClient& orthanc,
+                                     const Json::Value& dicomTags,
+                                     const std::string& parentOrthancId,
+                                     double pixelSpacingX,
+                                     double pixelSpacingY,
+                                     bool invert,
+                                     bool autoCrop,
+                                     ImageInterpolation interpolation,
+                                     bool usePam)
+  {
+    Json::Value createDicomRequestContent;
+
+    ExportToCreateDicomRequest(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation, usePam);
+
+    orthanc.PostJsonAsyncExpectJson(
+          "/tools/create-dicom", createDicomRequestContent,
+          new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
+          (GetSharedObserver(), &RadiographyScene::OnDicomExported),
+          NULL, NULL);
+
+  }
+
+
+  // Export using PAM is faster than using PNG, but requires Orthanc
+  // core >= 1.4.3
+  void RadiographyScene::ExportDicom(Deprecated::OrthancApiClient& orthanc,
+                                     const Orthanc::DicomMap& dicom,
+                                     const std::string& parentOrthancId,
+                                     double pixelSpacingX,
+                                     double pixelSpacingY,
+                                     bool invert,
+                                     bool autoCrop,
+                                     ImageInterpolation interpolation,
+                                     bool usePam)
+  {
+    std::set<Orthanc::DicomTag> tags;
+    dicom.GetTags(tags);
+
+    Json::Value jsonTags = Json::objectValue;
+
+    for (std::set<Orthanc::DicomTag>::const_iterator
+         tag = tags.begin(); tag != tags.end(); ++tag)
+    {
+      const Orthanc::DicomValue& value = dicom.GetValue(*tag);
+      if (!value.IsNull() &&
+          !value.IsBinary())
+      {
+        jsonTags[tag->Format()] = value.GetContent();
+      }
+    }
+
+    ExportDicom(orthanc, jsonTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation, usePam);
+  }
+
+  void RadiographyScene::OnDicomExported(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    LOG(INFO) << "DICOM export was successful: "
+              << message.GetJson().toStyledString();
+  }
+
+
+  void RadiographyScene::OnDicomWebReceived(const Deprecated::IWebService::HttpRequestSuccessMessage& message)
+  {
+    LOG(INFO) << "DICOMweb WADO-RS received: " << message.GetAnswerSize() << " bytes";
+
+    const Deprecated::IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders();
+    for (Deprecated::IWebService::HttpHeaders::const_iterator
+         it = h.begin(); it != h.end(); ++it)
+    {
+      printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str());
+    }
+  }
+
+  void RadiographyScene::ExportToScene2D(Scene2D& output) const
+  {
+    int depth = 0;
+    for (Layers::const_iterator it = layers_.begin();
+         it != layers_.end(); ++it)
+    {
+      assert(it->second != NULL);
+
+      std::unique_ptr<ISceneLayer> layer;
+      if (dynamic_cast<RadiographyDicomLayer*>(it->second))
+      {
+        RadiographyDicomLayer* oldLayer = dynamic_cast<RadiographyDicomLayer*>(it->second);
+
+        std::unique_ptr<FloatTextureSceneLayer> newLayer(new FloatTextureSceneLayer(*(oldLayer->GetSourceImage())));
+
+        newLayer->SetOrigin(oldLayer->GetGeometry().GetPanX(),
+                            oldLayer->GetGeometry().GetPanY()
+                            );
+        newLayer->SetAngle(oldLayer->GetGeometry().GetAngle());
+
+        layer.reset(newLayer.release());
+
+        // TODO: windowing dynamic_cast
+      }
+      else if (dynamic_cast<RadiographyTextLayer*>(it->second))
+      {
+        RadiographyTextLayer* oldLayer = dynamic_cast<RadiographyTextLayer*>(it->second);
+
+        std::unique_ptr<TextSceneLayer> newLayer(new TextSceneLayer());
+
+        newLayer->SetText(oldLayer->GetText());
+        newLayer->SetColor(oldLayer->GetForegroundGreyLevel(),
+                           oldLayer->GetForegroundGreyLevel(),
+                           oldLayer->GetForegroundGreyLevel()
+                           );
+        newLayer->SetPosition(oldLayer->GetGeometry().GetPanX(),
+                              oldLayer->GetGeometry().GetPanY()
+                              );
+        newLayer->SetFontIndex(1);
+        newLayer->SetAnchor(BitmapAnchor_TopLeft);
+        //newLayer->SetAngle(oldLayer->GetGeometry().GetAngle());
+
+        layer.reset(newLayer.release());
+      }
+
+      output.SetLayer(depth++, layer.release());
+
+    }
+
+  }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyScene.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,370 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
+#include "../Messages/ObserverBase.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
+#include "../Deprecated/Toolbox/OrthancApiClient.h"
+#include "../StoneEnumerations.h"
+#include "Core/Images/Image.h"
+#include "Core/Images/ImageProcessing.h"
+
+#include "../Scene2D/Scene2D.h"
+
+namespace OrthancStone
+{
+  class RadiographyDicomLayer;
+
+  class RadiographyScene :
+    public ObserverBase<RadiographyScene>,
+    public IObservable
+  {
+    friend class RadiographySceneGeometryReader;
+  public:
+    class GeometryChangedMessage : public OriginMessage<RadiographyScene>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      RadiographyLayer&        layer_;
+
+    public:
+      GeometryChangedMessage(const RadiographyScene& origin,
+                             RadiographyLayer& layer) :
+        OriginMessage(origin),
+        layer_(layer)
+      {
+      }
+
+      RadiographyLayer& GetLayer() const
+      {
+        return layer_;
+      }
+    };
+
+    class ContentChangedMessage : public OriginMessage<RadiographyScene>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      RadiographyLayer&        layer_;
+
+    public:
+      ContentChangedMessage(const RadiographyScene& origin,
+                            RadiographyLayer& layer) :
+        OriginMessage(origin),
+        layer_(layer)
+      {
+      }
+
+      RadiographyLayer& GetLayer() const
+      {
+        return layer_;
+      }
+    };
+
+    class LayerEditedMessage : public OriginMessage<RadiographyScene>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      const RadiographyLayer&        layer_;
+
+    public:
+      LayerEditedMessage(const RadiographyScene& origin,
+                         const RadiographyLayer& layer) :
+        OriginMessage(origin),
+        layer_(layer)
+      {
+      }
+
+      const RadiographyLayer& GetLayer() const
+      {
+        return layer_;
+      }
+    };
+
+    class LayerRemovedMessage : public OriginMessage<RadiographyScene>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      size_t&        layerIndex_;
+
+    public:
+      LayerRemovedMessage(const RadiographyScene& origin,
+                          size_t& layerIndex) :
+        OriginMessage(origin),
+        layerIndex_(layerIndex)
+      {
+      }
+
+      size_t& GetLayerIndex() const
+      {
+        return layerIndex_;
+      }
+    };
+
+
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, WindowingChangedMessage, RadiographyScene);
+
+    
+    class LayerAccessor : public boost::noncopyable
+    {
+    private:
+      RadiographyScene&  scene_;
+      size_t             index_;
+      RadiographyLayer*  layer_;
+
+    public:
+      LayerAccessor(RadiographyScene& scene,
+                    size_t index);
+
+      LayerAccessor(RadiographyScene& scene,
+                    double x,
+                    double y);
+
+      void Invalidate()
+      {
+        layer_ = NULL;
+      }
+
+      bool IsValid() const
+      {
+        return layer_ != NULL;
+      }
+
+      RadiographyScene& GetScene() const;
+
+      size_t GetIndex() const;
+
+      RadiographyLayer& GetLayer() const;
+    };
+
+
+  protected:
+    typedef std::map<size_t, RadiographyLayer*>  Layers;
+
+    size_t  nextLayerIndex_;
+    bool    hasWindowing_;
+    float   windowingCenter_;
+    float   windowingWidth_;
+    Layers  layers_;
+
+  public:
+    RadiographyLayer& RegisterLayer(RadiographyLayer* layer);
+
+  protected:
+    virtual void _RegisterLayer(RadiographyLayer* layer);
+    virtual void _OnLayerRemoved() {}
+
+    void SetLayerIndex(RadiographyLayer* layer, size_t index)
+    {
+      layer->SetIndex(index);
+    }
+
+    virtual void OnTagsReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message);
+
+    virtual void OnFrameReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message);
+    
+    void OnDicomExported(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnDicomWebReceived(const Deprecated::IWebService::HttpRequestSuccessMessage& message);
+
+    virtual void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message);
+
+  public:
+    RadiographyScene();
+    
+    virtual ~RadiographyScene();
+
+    virtual size_t GetApproximateMemoryUsage() const;
+
+    bool GetWindowing(float& center,
+                      float& width) const;
+
+    void GetWindowingWithDefault(float& center,
+                                 float& width) const;
+
+    virtual void SetWindowing(float center,
+                              float width);
+
+    RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const;
+
+    RadiographyLayer& LoadText(const std::string& utf8,
+                               const std::string& font,
+                               unsigned int fontSize,
+                               uint8_t foreground,
+                               RadiographyLayer::Geometry* geometry,
+                               bool isCenterGeometry);
+
+    RadiographyLayer& UpdateText(size_t layerIndex,
+                                 const std::string& font,
+                                 const std::string& utf8,
+                                 unsigned int fontSize,
+                                 uint8_t foreground);
+
+    RadiographyLayer& LoadTestBlock(unsigned int width,
+                                    unsigned int height,
+                                    RadiographyLayer::Geometry* geometry);
+
+    RadiographyLayer& LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
+                               const RadiographyDicomLayer& dicomLayer,
+                               float foreground,
+                               RadiographyLayer::Geometry* geometry);
+
+    RadiographyLayer& LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap,  // takes ownership
+                                      RadiographyLayer::Geometry* geometry);
+
+    virtual RadiographyLayer& LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
+                                             const std::string& instance,
+                                             unsigned int frame,
+                                             Deprecated::DicomFrameConverter* converter,  // takes ownership
+                                             RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
+                                             RadiographyLayer::Geometry* geometry);
+
+    virtual RadiographyLayer& LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
+                                             const std::string& instance,
+                                             unsigned int frame,
+                                             bool httpCompression,
+                                             RadiographyLayer::Geometry* geometry); // pass NULL if you want default geometry
+
+    RadiographyLayer& LoadDicomWebFrame(Deprecated::IWebService& web);
+
+    void RemoveLayer(size_t layerIndex);
+
+    RadiographyLayer& GetLayer(size_t layerIndex);
+
+    const RadiographyLayer& GetLayer(size_t layerIndex) const;
+
+    template <typename TypeLayer>
+    TypeLayer* GetTypedLayer(size_t indexOfType = 0)
+    {
+      std::vector<size_t> layerIndexes;
+      GetLayersIndexes(layerIndexes);
+
+      size_t count = 0;
+
+      for (size_t i = 0; i < layerIndexes.size(); ++i)
+      {
+        TypeLayer* typedLayer = dynamic_cast<TypeLayer*>(layers_[layerIndexes[i]]);
+        if (typedLayer != NULL)
+        {
+          if (count == indexOfType)
+          {
+            return typedLayer;
+          }
+          count++;
+        }
+      }
+
+      return NULL;
+    }
+
+    void GetLayersIndexes(std::vector<size_t>& output) const;
+
+    virtual Extent2D GetSceneExtent(bool minimal) const;
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation,
+                        bool applyWindowing) const;
+
+    bool LookupLayer(size_t& index /* out */,
+                     double x,
+                     double y) const;
+    
+    void DrawBorder(CairoContext& context,
+                    unsigned int layer,
+                    double zoom);
+
+    void GetRange(float& minValue,
+                  float& maxValue) const;
+
+    void ExportToScene2D(Scene2D& output) const;
+
+    // Export using PAM is faster than using PNG, but requires Orthanc
+    // core >= 1.4.3
+    void ExportDicom(Deprecated::OrthancApiClient& orthanc,
+                     const Orthanc::DicomMap& dicom,
+                     const std::string& parentOrthancId,
+                     double pixelSpacingX,
+                     double pixelSpacingY,
+                     bool invert,
+                     bool autoCrop,
+                     ImageInterpolation interpolation,
+                     bool usePam);
+
+    void ExportDicom(Deprecated::OrthancApiClient& orthanc,
+                     const Json::Value& dicomTags,
+                     const std::string& parentOrthancId,
+                     double pixelSpacingX,
+                     double pixelSpacingY,
+                     bool invert,
+                     bool autoCrop,
+                     ImageInterpolation interpolation,
+                     bool usePam);
+
+    void ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
+                                    const Json::Value& dicomTags,
+                                    const std::string& parentOrthancId,
+                                    double pixelSpacingX,
+                                    double pixelSpacingY,
+                                    bool invert,
+                                    bool autoCrop,
+                                    ImageInterpolation interpolation,
+                                    bool usePam);
+
+    Orthanc::Image* ExportToCreateDicomRequestAndImage(Json::Value& createDicomRequestContent,
+                                                       const Json::Value& dicomTags,
+                                                       const std::string& parentOrthancId,
+                                                       double pixelSpacingX,
+                                                       double pixelSpacingY,
+                                                       bool invert,
+                                                       bool autoCrop,
+                                                       ImageInterpolation interpolation);
+
+    Orthanc::Image* ExportToImage(double pixelSpacingX,
+                                  double pixelSpacingY,
+                                  ImageInterpolation interpolation,
+                                  bool autoCrop,
+                                  bool applyWindowing)
+    {
+      return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0, autoCrop, applyWindowing);
+    }
+
+    Orthanc::Image* ExportToImage(double pixelSpacingX,
+                                  double pixelSpacingY,
+                                  ImageInterpolation interpolation,
+                                  bool invert,
+                                  int64_t maxValue /* for inversion */,
+                                  bool autoCrop,
+                                  bool applyWindowing);
+
+    void ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
+                                       const Orthanc::ImageAccessor& renderedScene,
+                                       size_t layerIndex,
+                                       bool isCropped,
+                                       ImageInterpolation interpolation);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographySceneCommand.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,62 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographySceneCommand.h"
+
+
+namespace OrthancStone
+{
+  RadiographySceneCommand::RadiographySceneCommand(RadiographyScene& scene,
+                                                   size_t layer) :
+    scene_(scene),
+    layer_(layer)
+  {
+  }
+
+  
+  RadiographySceneCommand::RadiographySceneCommand(const RadiographyScene::LayerAccessor& accessor) :
+    scene_(accessor.GetScene()),
+    layer_(accessor.GetIndex())
+  {
+  }
+
+  
+  void RadiographySceneCommand::Undo() const
+  {
+    RadiographyScene::LayerAccessor accessor(scene_, layer_);
+
+    if (accessor.IsValid())
+    {
+      UndoInternal(accessor.GetLayer());
+    }
+  }
+
+  
+  void RadiographySceneCommand::Redo() const
+  {
+    RadiographyScene::LayerAccessor accessor(scene_, layer_);
+
+    if (accessor.IsValid())
+    {
+      RedoInternal(accessor.GetLayer());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographySceneCommand.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,50 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  class RadiographySceneCommand : public UndoRedoStack::ICommand
+  {
+  private:
+    RadiographyScene&  scene_;
+    size_t             layer_;
+
+  protected:
+    virtual void UndoInternal(RadiographyLayer& layer) const = 0;
+
+    virtual void RedoInternal(RadiographyLayer& layer) const = 0;
+
+  public:
+    RadiographySceneCommand(RadiographyScene& scene,
+                            size_t layer);
+
+    RadiographySceneCommand(const RadiographyScene::LayerAccessor& accessor);
+
+    virtual void Undo() const;
+
+    virtual void Redo() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographySceneReader.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,189 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographySceneReader.h"
+
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
+
+#include <Core/Images/FontRegistry.h>
+#include <Core/Images/PngReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+
+  void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */,
+                                     Deprecated::DicomFrameConverter* dicomFrameConverter  /* takes ownership */,
+                                     RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode
+                                     )
+  {
+    dicomImage_.reset(dicomImage);
+    dicomFrameConverter_.reset(dicomFrameConverter);
+    preferredPhotometricDisplayMode_ = preferredPhotometricDisplayMode;
+    Read(input);
+  }
+
+  RadiographyDicomLayer* RadiographySceneBuilder::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
+  {
+    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomImage(dicomImage_.release(), instanceId, frame, dicomFrameConverter_.release(), preferredPhotometricDisplayMode_, geometry)));
+  }
+
+
+  RadiographyDicomLayer* RadiographySceneReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
+  {
+    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry)));
+  }
+
+  RadiographyDicomLayer* RadiographySceneGeometryReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
+  {
+    std::unique_ptr<RadiographyPlaceholderLayer>  layer(new RadiographyPlaceholderLayer(scene_));
+    layer->SetGeometry(*geometry);
+    layer->SetSize(dicomImageWidth_, dicomImageHeight_);
+    scene_.RegisterLayer(layer.get());
+
+    return layer.release();
+  }
+
+  void RadiographySceneBuilder::Read(const Json::Value& input)
+  {
+    unsigned int version = input["version"].asUInt();
+
+    if (version != 1)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+
+    if (input.isMember("hasWindowing") && input["hasWindowing"].asBool())
+    {
+      scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat());
+    }
+
+    RadiographyDicomLayer* dicomLayer = NULL;
+    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
+    {
+      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
+      RadiographyLayer::Geometry geometry;
+
+      if (jsonLayer["type"].asString() == "dicom")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+        dicomLayer = LoadDicom(jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "mask")
+      {
+        if (dicomLayer == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask
+        }
+        ReadLayerGeometry(geometry, jsonLayer);
+
+        float foreground = jsonLayer["foreground"].asFloat();
+        std::vector<Orthanc::ImageProcessing::ImagePoint> corners;
+        for (size_t i = 0; i < jsonLayer["corners"].size(); i++)
+        {
+          Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(),
+              jsonLayer["corners"][(int)i]["y"].asInt());
+          corners.push_back(corner);
+        }
+
+        scene_.LoadMask(corners, *dicomLayer, foreground, &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "text")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+        scene_.LoadText(jsonLayer["text"].asString(), jsonLayer["font"].asString(), jsonLayer["fontSize"].asUInt(), static_cast<uint8_t>(jsonLayer["foreground"].asUInt()), &geometry, false);
+      }
+      else if (jsonLayer["type"].asString() == "alpha")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+
+        const std::string& pngContentBase64 = jsonLayer["content"].asString();
+        std::string pngContent;
+        std::string mimeType;
+        Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64);
+
+        std::unique_ptr<Orthanc::ImageAccessor>  image;
+        if (mimeType == "image/png")
+        {
+          image.reset(new Orthanc::PngReader());
+          dynamic_cast<Orthanc::PngReader*>(image.get())->ReadFromMemory(pngContent);
+        }
+        else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+
+        RadiographyAlphaLayer& layer = dynamic_cast<RadiographyAlphaLayer&>(scene_.LoadAlphaBitmap(image.release(), &geometry));
+
+        if (!jsonLayer["isUsingWindowing"].asBool())
+        {
+          layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble()));
+        }
+      }
+      else
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+
+
+  void RadiographySceneBuilder::ReadDicomLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input)
+  {
+    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
+    {
+      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
+      if (jsonLayer["type"].asString() == "dicom")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+        return;
+      }
+    }
+  }
+
+  void RadiographySceneBuilder::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer)
+  {
+    {// crop
+      unsigned int x, y, width, height;
+      if (jsonLayer["crop"]["hasCrop"].asBool())
+      {
+        x = jsonLayer["crop"]["x"].asUInt();
+        y = jsonLayer["crop"]["y"].asUInt();
+        width = jsonLayer["crop"]["width"].asUInt();
+        height = jsonLayer["crop"]["height"].asUInt();
+        geometry.SetCrop(x, y, width, height);
+      }
+    }
+
+    geometry.SetAngle(jsonLayer["angle"].asDouble());
+    geometry.SetResizeable(jsonLayer["isResizable"].asBool());
+    geometry.SetPan(jsonLayer["pan"]["x"].asDouble(), jsonLayer["pan"]["y"].asDouble());
+    geometry.SetPixelSpacing(jsonLayer["pixelSpacing"]["x"].asDouble(), jsonLayer["pixelSpacing"]["y"].asDouble());
+
+    // these fields were introduced later -> they might not exist
+    if (jsonLayer.isMember("flipVertical"))
+    {
+      geometry.SetFlipVertical(jsonLayer["flipVertical"].asBool());
+    }
+    if (jsonLayer.isMember("flipHorizontal"))
+    {
+      geometry.SetFlipHorizontal(jsonLayer["flipHorizontal"].asBool());
+    }
+
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographySceneReader.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,113 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyScene.h"
+#include "RadiographyAlphaLayer.h"
+#include "RadiographyDicomLayer.h"
+#include "RadiographyMaskLayer.h"
+#include "RadiographyTextLayer.h"
+#include "../Deprecated/Toolbox/OrthancApiClient.h"
+
+#include <json/value.h>
+#include <Core/Images/FontRegistry.h>
+
+namespace OrthancStone
+{
+  // a layer containing only the geometry of a DICOM layer (bit hacky !)
+  class RadiographyPlaceholderLayer : public RadiographyDicomLayer
+  {
+  public:
+    RadiographyPlaceholderLayer(const RadiographyScene& scene) :
+      RadiographyDicomLayer(scene)
+    {
+    }
+
+  };
+
+
+  // HACK: I had to introduce this builder class in order to be able to recreate a RadiographyScene
+  // from a serialized scene that is passed to web-workers.
+  // It needs some architecturing...
+  class RadiographySceneBuilder : public boost::noncopyable
+  {
+  protected:
+    RadiographyScene&                               scene_;
+    std::unique_ptr<Orthanc::ImageAccessor>           dicomImage_;
+    std::unique_ptr<Deprecated::DicomFrameConverter>  dicomFrameConverter_;
+    RadiographyPhotometricDisplayMode               preferredPhotometricDisplayMode_;
+
+  public:
+    RadiographySceneBuilder(RadiographyScene& scene) :
+      scene_(scene)
+    {
+    }
+
+    void Read(const Json::Value& input);
+    void Read(const Json::Value& input,
+              Orthanc::ImageAccessor* dicomImage, // takes ownership
+              Deprecated::DicomFrameConverter* dicomFrameConverter, // takes ownership
+              RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode
+              );
+
+    static void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input);
+    static void ReadDicomLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input);
+
+  protected:
+    virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
+
+  };
+
+
+  class RadiographySceneReader : public RadiographySceneBuilder
+  {
+    Deprecated::OrthancApiClient&             orthancApiClient_;
+
+  public:
+    RadiographySceneReader(RadiographyScene& scene, Deprecated::OrthancApiClient& orthancApiClient) :
+      RadiographySceneBuilder(scene),
+      orthancApiClient_(orthancApiClient)
+    {
+    }
+
+  protected:
+    virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
+  };
+
+  // reads the whole scene but the DICOM image such that we have the full geometry
+  class RadiographySceneGeometryReader : public RadiographySceneBuilder
+  {
+    unsigned int dicomImageWidth_;
+    unsigned int dicomImageHeight_;
+
+  public:
+    RadiographySceneGeometryReader(RadiographyScene& scene, unsigned int dicomImageWidth, unsigned int dicomImageHeight) :
+      RadiographySceneBuilder(scene),
+      dicomImageWidth_(dicomImageWidth),
+      dicomImageHeight_(dicomImageHeight)
+    {
+    }
+
+  protected:
+    virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographySceneWriter.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,168 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographySceneWriter.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Images/PngWriter.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+  void RadiographySceneWriter::Write(Json::Value& output, const RadiographyScene& scene)
+  {
+    output["version"] = 1;
+    float windowCenter, windowWidth;
+    bool hasWindowing = scene.GetWindowing(windowCenter, windowWidth);
+    output["hasWindowing"] = hasWindowing;
+    if (hasWindowing)
+    {
+      output["windowCenter"] = windowCenter;
+      output["windowWidth"] = windowWidth;
+    }
+    output["layers"] = Json::arrayValue;
+
+    std::vector<size_t> layersIndexes;
+    scene.GetLayersIndexes(layersIndexes);
+
+    for (std::vector<size_t>::iterator itLayerIndex = layersIndexes.begin(); itLayerIndex < layersIndexes.end(); itLayerIndex++)
+    {
+      Json::Value layer;
+      WriteLayer(layer, scene.GetLayer(*itLayerIndex));
+      output["layers"].append(layer);
+    }
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer)
+  {
+    output["type"] = "dicom";
+    output["instanceId"] = layer.GetInstanceId();
+    output["frame"] = layer.GetFrame();
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyTextLayer& layer)
+  {
+    output["type"] = "text";
+    output["text"] = layer.GetText();
+    output["font"] = layer.GetFont();
+    output["fontSize"] = layer.GetFontSize();
+    output["foreground"] = layer.GetForegroundGreyLevel();
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer)
+  {
+    output["type"] = "mask";
+    output["instanceId"] = layer.GetInstanceId(); // the dicom layer it's being linked to
+    output["foreground"] = layer.GetForeground();
+    output["corners"] = Json::arrayValue;
+    const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners = layer.GetCorners();
+    for (size_t i = 0; i < corners.size(); i++)
+    {
+      Json::Value corner;
+      corner["x"] = corners[i].GetX();
+      corner["y"] = corners[i].GetY();
+      output["corners"].append(corner);
+    }
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer)
+  {
+    output["type"] = "alpha";
+
+    //output["bitmap"] =
+    const Orthanc::ImageAccessor& alpha = layer.GetAlpha();
+
+    Orthanc::PngWriter pngWriter;
+    std::string pngContent;
+    std::string pngContentBase64;
+    pngWriter.WriteToMemory(pngContent, alpha);
+
+    Orthanc::Toolbox::EncodeDataUriScheme(pngContentBase64, "image/png", pngContent);
+    output["content"] = pngContentBase64;
+    output["foreground"] = layer.GetForegroundValue();
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyLayer& layer)
+  {
+    const RadiographyLayer::Geometry& geometry = layer.GetGeometry();
+
+    {// crop
+      Json::Value crop;
+      if (geometry.HasCrop())
+      {
+        unsigned int x, y, width, height;
+        geometry.GetCrop(x, y, width, height);
+        crop["hasCrop"] = true;
+        crop["x"] = x;
+        crop["y"] = y;
+        crop["width"] = width;
+        crop["height"] = height;
+      }
+      else
+      {
+        crop["hasCrop"] = false;
+      }
+
+      output["crop"] = crop;
+    }
+
+    output["angle"] = geometry.GetAngle();
+    output["isResizable"] = geometry.IsResizeable();
+
+    {// pan
+      Json::Value pan;
+      pan["x"] = geometry.GetPanX();
+      pan["y"] = geometry.GetPanY();
+      output["pan"] = pan;
+    }
+
+    {// pixelSpacing
+      Json::Value pan;
+      pan["x"] = geometry.GetPixelSpacingX();
+      pan["y"] = geometry.GetPixelSpacingY();
+      output["pixelSpacing"] = pan;
+    }
+
+    output["flipVertical"] = geometry.GetFlipVertical();
+    output["flipHorizontal"] = geometry.GetFlipHorizontal();
+
+    if (dynamic_cast<const RadiographyTextLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyTextLayer&>(layer));
+    }
+    else if (dynamic_cast<const RadiographyDicomLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyDicomLayer&>(layer));
+    }
+    else if (dynamic_cast<const RadiographyAlphaLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyAlphaLayer&>(layer));
+    }
+    else if (dynamic_cast<const RadiographyMaskLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyMaskLayer&>(layer));
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographySceneWriter.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyScene.h"
+#include "RadiographyAlphaLayer.h"
+#include "RadiographyDicomLayer.h"
+#include "RadiographyTextLayer.h"
+#include "RadiographyMaskLayer.h"
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  class RadiographySceneWriter : public boost::noncopyable
+  {
+
+  public:
+    RadiographySceneWriter()
+    {
+    }
+
+    void Write(Json::Value& output, const RadiographyScene& scene);
+
+  private:
+    void WriteLayer(Json::Value& output, const RadiographyLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyTextLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyTextLayer.cpp	Wed Apr 29 20:43:09 2020 +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-2020 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 "RadiographyTextLayer.h"
+
+#include "Core/OrthancException.h"
+#include "RadiographyScene.h"
+#include "../Toolbox/TextRenderer.h"
+
+namespace OrthancStone
+{
+  std::map<std::string, Orthanc::EmbeddedResources::FileResourceId> RadiographyTextLayer::fonts_;
+
+  void RadiographyTextLayer::SetText(const std::string& utf8,
+                                     const std::string& font,
+                                     unsigned int fontSize,
+                                     uint8_t foregroundGreyLevel)
+  {
+    if (fonts_.find(font) == fonts_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "The font has not been registered");
+    }
+
+    text_ = utf8;
+    font_ = font;
+    fontSize_ = fontSize;
+    foregroundGreyLevel_ = foregroundGreyLevel;
+
+    SetAlpha(TextRenderer::Render(fonts_[font_],
+                                  fontSize_,
+                                  text_));
+
+    SetForegroundValue(foregroundGreyLevel * 256.0f);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyTextLayer.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,72 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyAlphaLayer.h"
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  class RadiographyTextLayer : public RadiographyAlphaLayer
+  {
+  private:
+    std::string                 text_;
+    std::string                 font_;
+    unsigned int                fontSize_;
+    uint8_t                     foregroundGreyLevel_;
+
+    static std::map<std::string, Orthanc::EmbeddedResources::FileResourceId>  fonts_;
+  public:
+    RadiographyTextLayer(const RadiographyScene& scene) :
+      RadiographyAlphaLayer(scene)
+    {
+    }
+
+    void SetText(const std::string& utf8, const std::string& font, unsigned int fontSize, uint8_t foregroundGreyLevel);
+
+    const std::string& GetText() const
+    {
+      return text_;
+    }
+
+    const std::string& GetFont() const
+    {
+      return font_;
+    }
+
+    unsigned int GetFontSize() const
+    {
+      return fontSize_;
+    }
+
+    uint8_t GetForegroundGreyLevel() const
+    {
+      return foregroundGreyLevel_;
+    }
+
+    static void RegisterFont(const std::string& name, Orthanc::EmbeddedResources::FileResourceId fontResourceId)
+    {
+      fonts_[name] = fontResourceId;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyWidget.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,284 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyWidget.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+
+#include "RadiographyMaskLayer.h"
+
+namespace OrthancStone
+{
+
+  bool RadiographyWidget::IsInvertedInternal() const
+  {
+    // MONOCHROME1 images must be inverted and the user can invert the 
+    // image, too -> XOR the two
+    return (scene_->GetPreferredPhotomotricDisplayMode() == 
+            RadiographyPhotometricDisplayMode_Monochrome1) ^ invert_; 
+  }
+
+  void RadiographyWidget::RenderBackground(
+    Orthanc::ImageAccessor& image, float minValue, float maxValue)
+  {
+    // wipe background before rendering
+    float backgroundValue = minValue;
+
+    switch (scene_->GetPreferredPhotomotricDisplayMode())
+    {
+    case RadiographyPhotometricDisplayMode_Monochrome1:
+    case RadiographyPhotometricDisplayMode_Default:
+      if (IsInvertedInternal())
+        backgroundValue = maxValue;
+      else
+        backgroundValue = minValue;
+      break;
+    case RadiographyPhotometricDisplayMode_Monochrome2:
+      if (IsInvertedInternal())
+        backgroundValue = minValue;
+      else
+        backgroundValue = maxValue;
+      break;
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    Orthanc::ImageProcessing::Set(image, static_cast<int64_t>(backgroundValue));
+  }
+
+  bool RadiographyWidget::RenderInternal(unsigned int width,
+                                         unsigned int height,
+                                         ImageInterpolation interpolation)
+  {
+    if (floatBuffer_.get() == NULL ||
+        floatBuffer_->GetWidth() != width ||
+        floatBuffer_->GetHeight() != height)
+    {
+      floatBuffer_.reset(new Orthanc::Image(
+        Orthanc::PixelFormat_Float32, width, height, false));
+
+      if (floatBuffer_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "RadiographyWidget::RenderInternal: unable to allocate float buffer");
+      }
+    }
+
+    if (cairoBuffer_.get() == NULL ||
+        cairoBuffer_->GetWidth() != width ||
+        cairoBuffer_->GetHeight() != height)
+    {
+      cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */));
+
+      if (cairoBuffer_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "RadiographyWidget::RenderInternal: unable to allocate cairo buffer");
+      }
+    }
+
+    RenderBackground(*floatBuffer_, 0.0, 65535.0);
+
+    scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation, true);
+
+    // Conversion from Float32 to BGRA32 (cairo). Very similar to
+    // GrayscaleFrameRenderer => TODO MERGE?
+    Orthanc::ImageAccessor target;
+    cairoBuffer_->GetWriteableAccessor(target);
+
+    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 x = 0; x < width; x++, p++, q += 4)
+      {
+        uint8_t v = 0;
+        if (*p >= 65535.0)
+        {
+          v = 255;
+        }
+        else if (*p <= 0.0)
+        {
+          v = 0;
+        }
+        else
+        {
+          v = static_cast<uint8_t>(*p / 256.0);
+        }
+
+        if (invert)
+        {
+          v = 255 - v;
+        }
+
+        q[0] = v;
+        q[1] = v;
+        q[2] = v;
+        q[3] = 255;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool RadiographyWidget::RenderScene(CairoContext& context,
+                                      const Deprecated::ViewportGeometry& view)
+  {
+    cairo_t* cr = context.GetObject();
+
+    if (RenderInternal(context.GetWidth(), context.GetHeight(), interpolation_))
+    {
+      // https://www.cairographics.org/FAQ/#paint_from_a_surface
+      cairo_save(cr);
+      cairo_identity_matrix(cr);
+      cairo_set_source_surface(cr, cairoBuffer_->GetObject(), 0, 0);
+      cairo_paint(cr);
+      cairo_restore(cr);
+    }
+    else
+    {
+      // https://www.cairographics.org/FAQ/#clear_a_surface
+      context.SetSourceColor(0, 0, 0);
+      cairo_paint(cr);
+    }
+
+    if (hasSelection_)
+    {
+      scene_->DrawBorder(
+        context, static_cast<unsigned int>(selectedLayer_), view.GetZoom());
+    }
+
+    return true;
+  }
+
+
+  RadiographyWidget::RadiographyWidget(boost::shared_ptr<RadiographyScene> scene,
+                                       const std::string& name) :
+    WorldSceneWidget(name),
+    invert_(false),
+    interpolation_(ImageInterpolation_Nearest),
+    hasSelection_(false),
+    selectedLayer_(0)    // Dummy initialization
+  {
+    SetScene(scene);
+  }
+
+
+  void RadiographyWidget::Select(size_t layer)
+  {
+    hasSelection_ = true;
+    selectedLayer_ = layer;
+
+    NotifyContentChanged();
+    BroadcastMessage(SelectionChangedMessage(*this));
+  }
+
+  void RadiographyWidget::Unselect()
+  {
+    hasSelection_ = false;
+
+    NotifyContentChanged();
+    BroadcastMessage(SelectionChangedMessage(*this));
+  }
+
+  bool RadiographyWidget::LookupSelectedLayer(size_t& layer) const
+  {
+    if (hasSelection_)
+    {
+      layer = selectedLayer_;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  void RadiographyWidget::OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message)
+  {
+//    LOG(INFO) << "Scene geometry has changed";
+    FitContent();
+  }
+
+  
+  void RadiographyWidget::OnContentChanged(const RadiographyScene::ContentChangedMessage& message)
+  {
+//    LOG(INFO) << "Scene content has changed";
+    NotifyContentChanged();
+  }
+
+  void RadiographyWidget::OnLayerRemoved(const RadiographyScene::LayerRemovedMessage& message)
+  {
+    size_t removedLayerIndex = message.GetLayerIndex();
+    if (hasSelection_ && selectedLayer_ == removedLayerIndex)
+    {
+      Unselect();
+    }
+    NotifyContentChanged();
+  }
+  
+  void RadiographyWidget::SetInvert(bool invert)
+  {
+    if (invert_ != invert)
+    {
+      invert_ = invert;
+      NotifyContentChanged();
+    }
+  }
+
+  
+    void RadiographyWidget::SwitchInvert()
+  {
+    invert_ = !invert_;
+    NotifyContentChanged();
+  }
+
+
+  void RadiographyWidget::SetInterpolation(ImageInterpolation interpolation)
+  {
+    if (interpolation_ != interpolation)
+    {
+      interpolation_ = interpolation;
+      NotifyContentChanged();
+    }
+  }
+
+  void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene)
+  {
+    scene_ = scene;
+
+    Register<RadiographyScene::GeometryChangedMessage>(*scene_, &RadiographyWidget::OnGeometryChanged);
+    Register<RadiographyScene::ContentChangedMessage>(*scene_, &RadiographyWidget::OnContentChanged);
+    Register<RadiographyScene::LayerRemovedMessage>(*scene_, &RadiographyWidget::OnLayerRemoved);
+
+    Unselect();
+
+    NotifyContentChanged();
+
+    // force redraw
+    FitContent();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyWidget.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,131 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Deprecated/Widgets/WorldSceneWidget.h"
+#include "../Messages/ObserverBase.h"
+#include "RadiographyScene.h"
+
+
+namespace OrthancStone
+{
+  class RadiographyMaskLayer;
+
+  class RadiographyWidget :
+    public Deprecated::WorldSceneWidget,
+    public ObserverBase<RadiographyWidget>,
+    public IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SelectionChangedMessage, RadiographyWidget);
+
+  private:
+    boost::shared_ptr<RadiographyScene>    scene_;
+    std::unique_ptr<Orthanc::ImageAccessor>  floatBuffer_;
+    std::unique_ptr<CairoSurface>            cairoBuffer_;
+    bool                                   invert_;
+    ImageInterpolation                     interpolation_;
+    bool                                   hasSelection_;
+    size_t                                 selectedLayer_;
+
+    bool RenderInternal(unsigned int width,
+                        unsigned int height,
+                        ImageInterpolation interpolation);
+
+  protected:
+    virtual Extent2D GetSceneExtent()
+    {
+      return scene_->GetSceneExtent(false);
+    }
+
+    virtual bool RenderScene(CairoContext& context,
+                             const Deprecated::ViewportGeometry& view);
+
+    virtual void RenderBackground(Orthanc::ImageAccessor& image, float minValue, float maxValue);
+
+    bool IsInvertedInternal() const;
+
+  public:
+    RadiographyWidget(boost::shared_ptr<RadiographyScene> scene,  // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now)
+                      const std::string& name);
+
+    RadiographyScene& GetScene() const
+    {
+      return *scene_;
+    }
+
+    void SetScene(boost::shared_ptr<RadiographyScene> scene);
+
+    void Select(size_t layer);
+
+    void Unselect();
+
+    template<typename LayerType> bool SelectLayerByType(size_t index = 0);
+
+    bool LookupSelectedLayer(size_t& layer) const;
+
+    void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message);
+
+    void OnContentChanged(const RadiographyScene::ContentChangedMessage& message);
+
+    void OnLayerRemoved(const RadiographyScene::LayerRemovedMessage& message);
+
+    void SetInvert(bool invert);
+
+    void SwitchInvert();
+
+    bool IsInverted() const
+    {
+      return invert_;
+    }
+
+    void SetInterpolation(ImageInterpolation interpolation);
+
+    ImageInterpolation GetInterpolation() const
+    {
+      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;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyWindowingTracker.cpp	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,192 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "RadiographyWindowingTracker.h"
+#include "RadiographyWidget.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  class RadiographyWindowingTracker::UndoRedoCommand : public UndoRedoStack::ICommand
+  {
+  private:
+    RadiographyScene&  scene_;
+    float              sourceCenter_;
+    float              sourceWidth_;
+    float              targetCenter_;
+    float              targetWidth_;
+
+  public:
+    UndoRedoCommand(const RadiographyWindowingTracker& tracker) :
+      scene_(tracker.scene_),
+      sourceCenter_(tracker.sourceCenter_),
+      sourceWidth_(tracker.sourceWidth_)
+    {
+      scene_.GetWindowingWithDefault(targetCenter_, targetWidth_);
+    }
+
+    virtual void Undo() const
+    {
+      scene_.SetWindowing(sourceCenter_, sourceWidth_);
+    }
+      
+    virtual void Redo() const
+    {
+      scene_.SetWindowing(targetCenter_, targetWidth_);
+    }
+  };
+
+
+  void RadiographyWindowingTracker::ComputeAxisEffect(int& deltaCenter,
+                                                      int& deltaWidth,
+                                                      int delta,
+                                                      Action actionNegative,
+                                                      Action actionPositive)
+  {
+    if (delta < 0)
+    {
+      switch (actionNegative)
+      {
+        case Action_IncreaseWidth:
+          deltaWidth = -delta;
+          break;
+
+        case Action_DecreaseWidth:
+          deltaWidth = delta;
+          break;
+
+        case Action_IncreaseCenter:
+          deltaCenter = -delta;
+          break;
+
+        case Action_DecreaseCenter:
+          deltaCenter = delta;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+    else if (delta > 0)
+    {
+      switch (actionPositive)
+      {
+        case Action_IncreaseWidth:
+          deltaWidth = delta;
+          break;
+
+        case Action_DecreaseWidth:
+          deltaWidth = -delta;
+          break;
+
+        case Action_IncreaseCenter:
+          deltaCenter = delta;
+          break;
+
+        case Action_DecreaseCenter:
+          deltaCenter = -delta;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+    
+    
+  RadiographyWindowingTracker::RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
+                                                           RadiographyScene& scene,
+                                                           RadiographyWidget& widget,
+                                                           ImageInterpolation interpolationDuringTracking,
+                                                           int x,
+                                                           int y,
+                                                           Action leftAction,
+                                                           Action rightAction,
+                                                           Action upAction,
+                                                           Action downAction) :
+    undoRedoStack_(undoRedoStack),
+    scene_(scene),
+    widget_(widget),
+    initialWidgetInterpolation_(widget.GetInterpolation()),
+    clickX_(x),
+    clickY_(y),
+    leftAction_(leftAction),
+    rightAction_(rightAction),
+    upAction_(upAction),
+    downAction_(downAction)
+  {
+    scene_.GetWindowingWithDefault(sourceCenter_, sourceWidth_);
+    widget_.SetInterpolation(interpolationDuringTracking);
+
+    float minValue, maxValue;
+    scene.GetRange(minValue, maxValue);
+
+    assert(minValue <= maxValue);
+
+    float delta = (maxValue - minValue);
+    strength_ = delta / 1000.0f; // 1px move will change the ww/wc by 0.1%
+
+    if (strength_ < 1)
+    {
+      strength_ = 1;
+    }
+  }
+
+
+  void RadiographyWindowingTracker::Render(CairoContext& context,
+                                           double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+  
+
+  void RadiographyWindowingTracker::MouseUp()
+  {
+    widget_.SetInterpolation(initialWidgetInterpolation_);
+    undoRedoStack_.Add(new UndoRedoCommand(*this));
+  }
+
+
+  void RadiographyWindowingTracker::MouseMove(int displayX,
+                                              int displayY,
+                                              double sceneX,
+                                              double sceneY,
+                                              const std::vector<Deprecated::Touch>& displayTouches,
+                                              const std::vector<Deprecated::Touch>& sceneTouches)
+  {
+    // This follows the behavior of the Osimis Web viewer:
+    // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
+
+    static const float SCALE = 1.0;
+      
+    int deltaCenter = 0;
+    int deltaWidth = 0;
+
+    ComputeAxisEffect(deltaCenter, deltaWidth, displayX - clickX_, leftAction_, rightAction_);
+    ComputeAxisEffect(deltaCenter, deltaWidth, displayY - clickY_, upAction_, downAction_);
+
+    float newCenter = sourceCenter_ + (deltaCenter / SCALE * strength_);
+    float newWidth = sourceWidth_ + (deltaWidth / SCALE * strength_);
+    scene_.SetWindowing(newCenter, newWidth);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Radiography/RadiographyWindowingTracker.h	Wed Apr 29 20:43:09 2020 +0200
@@ -0,0 +1,96 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+
+  class RadiographyWidget;
+
+  class RadiographyWindowingTracker : public Deprecated::IWorldSceneMouseTracker
+  {   
+  public:
+    enum Action
+    {
+      Action_IncreaseWidth,
+      Action_DecreaseWidth,
+      Action_IncreaseCenter,
+      Action_DecreaseCenter
+    };
+    
+  private:
+    class UndoRedoCommand;
+
+    UndoRedoStack&      undoRedoStack_;
+    RadiographyScene&   scene_;
+    RadiographyWidget&  widget_;
+    ImageInterpolation  initialWidgetInterpolation_;
+    int                 clickX_;
+    int                 clickY_;
+    Action              leftAction_;
+    Action              rightAction_;
+    Action              upAction_;
+    Action              downAction_;
+    float               strength_;
+    float               sourceCenter_;
+    float               sourceWidth_;
+
+    static void ComputeAxisEffect(int& deltaCenter,
+                                  int& deltaWidth,
+                                  int delta,
+                                  Action actionNegative,
+                                  Action actionPositive);    
+    
+  public:
+    RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
+                                RadiographyScene& scene,
+                                RadiographyWidget& widget,
+                                ImageInterpolation interpolationDuringTracking,
+                                int x,
+                                int y,
+                                Action leftAction,
+                                Action rightAction,
+                                Action upAction,
+                                Action downAction);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Deprecated::Touch>& displayTouches,
+                           const std::vector<Deprecated::Touch>& sceneTouches);
+  };
+}
--- a/Framework/Radiography/RadiographyAlphaLayer.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyAlphaLayer.h"
-
-#include "RadiographyScene.h"
-
-#include <Core/Compatibility.h>
-#include <Core/Images/Image.h>
-#include <Core/OrthancException.h>
-
-#include "../Toolbox/ImageGeometry.h"
-
-namespace OrthancStone
-{
-
-  void RadiographyAlphaLayer::SetAlpha(Orthanc::ImageAccessor* image)
-  {
-    std::unique_ptr<Orthanc::ImageAccessor> raii(image);
-
-    if (image == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-
-    SetSize(image->GetWidth(), image->GetHeight());
-
-#if __cplusplus < 201103L
-    alpha_.reset(raii.release());
-#else
-    alpha_ = std::move(raii);
-#endif
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer,
-                                     const AffineTransform2D& viewTransform,
-                                     ImageInterpolation interpolation,
-                                     float windowCenter,
-                                     float windowWidth,
-                                     bool applyWindowing) const
-  {
-    if (alpha_.get() == NULL)
-    {
-      return;
-    }
-
-    if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-    const AffineTransform2D t = AffineTransform2D::Combine(
-          viewTransform, GetTransform(),
-          AffineTransform2D::CreateOffset(cropX, cropY));
-
-    Orthanc::ImageAccessor cropped;
-    alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
-
-    Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
-
-    unsigned int x1, y1, x2, y2;
-
-    if (!OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
-                                                    t.GetHomogeneousMatrix(),
-                                                    cropped.GetWidth(),
-                                                    cropped.GetHeight(),
-                                                    buffer.GetWidth(),
-                                                    buffer.GetHeight()))
-    {
-      return;  // layer is outside the buffer
-    }
-
-    t.Apply(tmp, cropped, interpolation, true /* clear */);
-
-    float value = foreground_;
-
-    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
-    {
-      // 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 = 0.0f;
-      }
-      if (value > 65535.0f)
-      {
-        value = 65535.0f;
-      }
-    }
-
-    for (unsigned int y = y1; y <= y2; 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 = x1; x <= x2; x++, p++, q++)
-      {
-        float a = static_cast<float>(*p) / 255.0f;
-
-        *q = (a * value + (1.0f - a) * (*q));
-      }
-    }
-  }
-
-  bool RadiographyAlphaLayer::GetRange(float& minValue,
-                                       float& maxValue) const
-  {
-    minValue = 0;
-    maxValue = 0;
-
-    if (foreground_ < 0)
-    {
-      minValue = foreground_;
-    }
-
-    if (foreground_ > 0)
-    {
-      maxValue = foreground_;
-    }
-
-    return true;
-  }
-}
--- a/Framework/Radiography/RadiographyAlphaLayer.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
-
-#include <Core/Compatibility.h>
-
-namespace OrthancStone
-{
-  class RadiographyScene;
-
-  // creates a transparent layer whose alpha channel is provided as a UINT8 image to SetAlpha.
-  // The color of the "mask" is either defined by a ForegroundValue or by the center value of the
-  // windowing from the scene.
-  class RadiographyAlphaLayer : public RadiographyLayer
-  {
-  private:
-    std::unique_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),
-      foreground_(0)
-    {
-    }
-
-
-    void SetForegroundValue(float foreground)
-    {
-      foreground_ = foreground;
-    }
-
-    float GetForegroundValue() const
-    {
-      return foreground_;
-    }
-
-    void SetAlpha(Orthanc::ImageAccessor* image);
-
-    virtual bool GetDefaultWindowing(float& center,
-                                     float& width) const
-    {
-      return false;
-    }
-
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation,
-                        float windowCenter,
-                        float windowWidth,
-                        bool applyWindowing) const;
-
-    virtual bool GetRange(float& minValue,
-                          float& maxValue) const;
-
-    const Orthanc::ImageAccessor& GetAlpha() const
-    {
-      return *(alpha_.get());
-    }
-
-
-  };
-}
--- a/Framework/Radiography/RadiographyDicomLayer.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,264 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyDicomLayer.h"
-
-#include "RadiographyScene.h"
-#include "../Deprecated/Toolbox/DicomFrameConverter.h"
-
-#include <Core/OrthancException.h>
-#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)
-{
-  return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
-}
-
-namespace OrthancStone
-{
-
-  void RadiographyDicomLayer::ApplyConverter()
-  {
-    if (source_.get() != NULL &&
-        converter_.get() != NULL)
-    {
-      converted_.reset(converter_->ConvertFrame(*source_));
-    }
-  }
-
-
-  RadiographyDicomLayer::RadiographyDicomLayer(const RadiographyScene& scene) :
-    RadiographyLayer(scene)
-  {
-
-  }
-
-  void RadiographyDicomLayer::SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
-  {
-    converter_.reset(new Deprecated::DicomFrameConverter);
-    converter_->ReadParameters(dataset);
-    ApplyConverter();
-
-    std::string tmp;
-    Vector pixelSpacing;
-
-    if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
-        LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
-        pixelSpacing.size() == 2)
-    {
-      SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
-    }
-
-    OrthancPlugins::DicomDatasetReader reader(dataset);
-
-    unsigned int width, height;
-    if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
-        !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      SetSize(width, height);
-    }
-
-    if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION)))
-    {
-      if (tmp == "MONOCHROME1")
-      {
-        SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome1);
-      }
-      else if (tmp == "MONOCHROME2")
-      {
-        SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome2);
-      }
-    }
-  }
-
-  void RadiographyDicomLayer::SetSourceImage(Orthanc::ImageAccessor* image)   // Takes ownership
-  {
-    std::unique_ptr<Orthanc::ImageAccessor> raii(image);
-
-    if (image == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    SetSize(image->GetWidth(), image->GetHeight());
-
-#if __cplusplus < 201103L
-      source_.reset(raii.release());
-#else
-      source_ = std::move(raii);
-#endif
-
-    ApplyConverter();
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyDicomLayer::SetSourceImage(Orthanc::ImageAccessor* image, double newPixelSpacingX, double newPixelSpacingY, bool emitLayerEditedEvent)   // Takes ownership
-  {
-    std::unique_ptr<Orthanc::ImageAccessor> raii(image);
-
-    if (image == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    SetSize(image->GetWidth(), image->GetHeight(), false);
-
-#if __cplusplus < 201103L
-    source_.reset(raii.release());
-#else
-    source_ = std::move(raii);
-#endif
-
-    ApplyConverter();
-
-    SetPixelSpacing(newPixelSpacingX, newPixelSpacingY, false);
-
-    if (emitLayerEditedEvent)
-    {
-      BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-    }
-  }
-
-
-  void RadiographyDicomLayer::SetDicomFrameConverter(Deprecated::DicomFrameConverter* converter)
-  {
-    converter_.reset(converter);
-  }
-
-  void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
-                                     const AffineTransform2D& viewTransform,
-                                     ImageInterpolation interpolation,
-                                     float windowCenter,
-                                     float windowWidth,
-                                     bool applyWindowing) const
-  {
-    if (converted_.get() != NULL)
-    {
-      if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      unsigned int cropX, cropY, cropWidth, cropHeight;
-      GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-      AffineTransform2D t = AffineTransform2D::Combine(
-            viewTransform, GetTransform(),
-            AffineTransform2D::CreateOffset(cropX, cropY));
-
-      Orthanc::ImageAccessor cropped;
-      converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
-
-      unsigned int x1, y1, x2, y2;
-      if (!OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
-                                                      t.GetHomogeneousMatrix(),
-                                                      cropped.GetWidth(),
-                                                      cropped.GetHeight(),
-                                                      buffer.GetWidth(),
-                                                      buffer.GetHeight()))
-      {
-        return;  // layer is outside the buffer
-      }
-
-      t.Apply(buffer, cropped, interpolation, false);
-
-      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);  // (*)
-              }
-            }
-          }
-        }
-      }
-
-    }
-  }
-
-
-  bool RadiographyDicomLayer::GetDefaultWindowing(float& center,
-                                                  float& width) const
-  {
-    if (converter_.get() != NULL &&
-        converter_->HasDefaultWindow())
-    {
-      center = static_cast<float>(converter_->GetDefaultWindowCenter());
-      width = static_cast<float>(converter_->GetDefaultWindowWidth());
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool RadiographyDicomLayer::GetRange(float& minValue,
-                                       float& maxValue) const
-  {
-    if (converted_.get() != NULL)
-    {
-      if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-}
--- a/Framework/Radiography/RadiographyDicomLayer.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Deprecated/Toolbox/DicomFrameConverter.h"
-#include "RadiographyLayer.h"
-
-#include <Plugins/Samples/Common/FullOrthancDataset.h>
-
-namespace OrthancStone
-{
-  class RadiographyScene;
-
-  class RadiographyDicomLayer : public RadiographyLayer
-  {
-  private:
-    std::unique_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
-    std::unique_ptr<Deprecated::DicomFrameConverter>     converter_;
-    std::unique_ptr<Orthanc::ImageAccessor>  converted_;  // Float32
-    std::string                            instanceId_;
-    unsigned int                           frame_;
-
-    void ApplyConverter();
-
-  public:
-    RadiographyDicomLayer(const RadiographyScene& scene);
-
-    void SetInstance(const std::string& instanceId, unsigned int frame)
-    {
-      instanceId_ = instanceId;
-      frame_ = frame;
-    }
-
-    std::string GetInstanceId() const
-    {
-      return instanceId_;
-    }
-
-    unsigned int GetFrame() const
-    {
-      return frame_;
-    }
-
-    virtual size_t GetApproximateMemoryUsage() const
-    {
-      size_t size = 0;
-      if (source_.get() != NULL)
-      {
-        size += source_->GetPitch() * source_->GetHeight();
-      }
-      if (converted_.get() != NULL)
-      {
-        size += converted_->GetPitch() * converted_->GetHeight();
-      }
-
-      return size;
-    }
-
-
-    void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset);
-
-    void SetSourceImage(Orthanc::ImageAccessor* image);   // Takes ownership
-
-    void SetSourceImage(Orthanc::ImageAccessor* image, double newPixelSpacingX, double newPixelSpacingY, bool emitLayerEditedEvent = true);   // Takes ownership
-
-    const Orthanc::ImageAccessor* GetSourceImage() const {return source_.get();}  // currently need this access to serialize scene in plain old data to send to a WASM worker
-
-    const Deprecated::DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker
-    
-     // Takes ownership
-    void SetDicomFrameConverter(Deprecated::DicomFrameConverter* converter);
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation,
-                        float windowCenter,
-                        float windowWidth,
-                        bool applyWindowing) const;
-
-    virtual bool GetDefaultWindowing(float& center,
-                                     float& width) const;
-
-    virtual bool GetRange(float& minValue,
-                          float& maxValue) const;
-  };
-}
--- a/Framework/Radiography/RadiographyLayer.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,402 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
-
-#include <Core/OrthancException.h>
-
-
-namespace OrthancStone
-{
-  static double Square(double x)
-  {
-    return x * x;
-  }
-
-
-  RadiographyLayer::Geometry::Geometry() :
-    hasCrop_(false),
-    flipVertical_(false),
-    flipHorizontal_(false),
-    panX_(0),
-    panY_(0),
-    angle_(0),
-    resizeable_(false),
-    pixelSpacingX_(1),
-    pixelSpacingY_(1)
-  {
-
-  }
-
-  void RadiographyLayer::Geometry::GetCrop(unsigned int &x, unsigned int &y, unsigned int &width, unsigned int &height) const
-  {
-    if (!hasCrop_)
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);  // you should probably use RadiographyLayer::GetCrop() or at least call HasCrop() before
-
-    x = cropX_;
-    y = cropY_;
-    width = cropWidth_;
-    height = cropHeight_;
-  }
-
-  void RadiographyLayer::UpdateTransform()
-  {
-    // important to update transform_ before getting the center to use the right scaling !!!
-    transform_ = AffineTransform2D::CreateScaling(geometry_.GetScalingX(), geometry_.GetScalingY());
-
-    double centerX, centerY;
-    GetCenter(centerX, centerY);
-
-    transform_ = AffineTransform2D::Combine(
-          AffineTransform2D::CreateOffset(geometry_.GetPanX(), geometry_.GetPanY()),
-          AffineTransform2D::CreateRotation(geometry_.GetAngle(), centerX, centerY),
-          transform_);
-
-    transformInverse_ = AffineTransform2D::Invert(transform_);
-  }
-
-
-  void RadiographyLayer::AddToExtent(Extent2D& extent,
-                                     double x,
-                                     double y) const
-  {
-    GetTransform().Apply(x, y);
-    extent.AddPoint(x, y);
-  }
-
-  bool RadiographyLayer::Contains(double x,
-                                  double y) const
-  {
-    GetTransformInverse().Apply(x, y);
-
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-    return (x >= cropX && x <= cropX + cropWidth &&
-            y >= cropY && y <= cropY + cropHeight);
-  }
-
-
-  void RadiographyLayer::DrawBorders(CairoContext& context,
-                                     double zoom)
-  {
-    if (GetControlPointCount() < 3 )
-      return;
-
-    cairo_t* cr = context.GetObject();
-    cairo_set_line_width(cr, 2.0 / zoom);
-
-    ControlPoint cp;
-    GetControlPoint(cp, 0);
-    cairo_move_to(cr, cp.x, cp.y);
-
-    for (size_t i = 0; i < GetControlPointCount(); i++)
-    {
-      GetControlPoint(cp, i);
-      cairo_line_to(cr, cp.x, cp.y);
-    }
-
-    cairo_close_path(cr);
-    cairo_stroke(cr);
-  }
-
-
-  RadiographyLayer::RadiographyLayer(const RadiographyScene& scene) :
-    index_(0),
-    hasSize_(false),
-    width_(0),
-    height_(0),
-    prefferedPhotometricDisplayMode_(RadiographyPhotometricDisplayMode_Default),
-    scene_(scene)
-  {
-    UpdateTransform();
-  }
-
-  void RadiographyLayer::ResetCrop()
-  {
-    geometry_.ResetCrop();
-    UpdateTransform();
-  }
-
-  void RadiographyLayer::SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode)
-  {
-    prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode;
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyLayer::SetCrop(unsigned int x,
-                                 unsigned int y,
-                                 unsigned int width,
-                                 unsigned int height)
-  {
-    if (!hasSize_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    if (x + width > width_ ||
-        y + height > height_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    geometry_.SetCrop(x, y, width, height);
-    UpdateTransform();
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyLayer::SetGeometry(const Geometry& geometry)
-  {
-    geometry_ = geometry;
-
-    if (hasSize_)
-    {
-      UpdateTransform();
-    }
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-
-  void RadiographyLayer::GetCrop(unsigned int& x,
-                                 unsigned int& y,
-                                 unsigned int& width,
-                                 unsigned int& height) const
-  {
-    if (GetGeometry().HasCrop())
-    {
-      GetGeometry().GetCrop(x, y, width, height);
-    }
-    else
-    {
-      x = 0;
-      y = 0;
-      width = width_;
-      height = height_;
-    }
-  }
-
-  
-  void RadiographyLayer::SetAngle(double angle)
-  {
-    geometry_.SetAngle(angle);
-    UpdateTransform();
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyLayer::SetFlipVertical(bool flip)
-  {
-    geometry_.SetFlipVertical(flip);
-    UpdateTransform();
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyLayer::SetFlipHorizontal(bool flip)
-  {
-    geometry_.SetFlipHorizontal(flip);
-    UpdateTransform();
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyLayer::SetSize(unsigned int width,
-                                 unsigned int height,
-                                 bool emitLayerEditedEvent)
-  {
-    hasSize_ = true;
-    width_ = width;
-    height_ = height;
-
-    UpdateTransform();
-
-    if (emitLayerEditedEvent)
-    {
-      BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-    }
-  }
-
-  Extent2D RadiographyLayer::GetSceneExtent(bool /*minimal*/) const
-  {
-    Extent2D extent;
-
-    unsigned int x, y, width, height;
-    GetCrop(x, y, width, height);
-
-    double dx = static_cast<double>(x);
-    double dy = static_cast<double>(y);
-    double dwidth = static_cast<double>(width);
-    double dheight = static_cast<double>(height);
-
-    // AddToExtent transforms the coordinates from image to scene
-    AddToExtent(extent, dx, dy);
-    AddToExtent(extent, dx + dwidth, dy);
-    AddToExtent(extent, dx, dy + dheight);
-    AddToExtent(extent, dx + dwidth, dy + dheight);
-
-    return extent;
-  }
-
-
-  bool RadiographyLayer::GetPixel(unsigned int& imageX,
-                                  unsigned int& imageY,
-                                  double sceneX,
-                                  double sceneY) const
-  {
-    if (width_ == 0 ||
-        height_ == 0)
-    {
-      return false;
-    }
-    else
-    {
-      GetTransformInverse().Apply(sceneX, sceneY);
-
-      int x = static_cast<int>(std::floor(sceneX));
-      int y = static_cast<int>(std::floor(sceneY));
-
-      if (x < 0)
-      {
-        imageX = 0;
-      }
-      else if (x >= static_cast<int>(width_))
-      {
-        imageX = width_;
-      }
-      else
-      {
-        imageX = static_cast<unsigned int>(x);
-      }
-
-      if (y < 0)
-      {
-        imageY = 0;
-      }
-      else if (y >= static_cast<int>(height_))
-      {
-        imageY = height_;
-      }
-      else
-      {
-        imageY = static_cast<unsigned int>(y);
-      }
-
-      return true;
-    }
-  }
-
-
-  void RadiographyLayer::SetPan(double x,
-                                double y)
-  {
-    geometry_.SetPan(x, y);
-    UpdateTransform();
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-
-  void RadiographyLayer::SetPixelSpacing(double x,
-                                         double y,
-                                         bool emitLayerEditedEvent)
-  {
-    geometry_.SetPixelSpacing(x, y);
-    UpdateTransform();
-    if (emitLayerEditedEvent)
-    {
-      BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-    }
-  }
-
-
-  void RadiographyLayer::GetCenter(double& centerX,
-                                   double& centerY) const
-  {
-    centerX = static_cast<double>(width_) / 2.0;
-    centerY = static_cast<double>(height_) / 2.0;
-    GetTransform().Apply(centerX, centerY);
-  }
-
-
-
-  size_t RadiographyLayer::GetControlPointCount() const {return 4;}
-
-  void RadiographyLayer::GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */,
-                                         size_t index) const
-  {
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-    ControlPoint cp;
-    switch (index)
-    {
-      case RadiographyControlPointType_TopLeftCorner:
-        cp = ControlPoint(cropX, cropY, RadiographyControlPointType_TopLeftCorner);
-        break;
-
-      case RadiographyControlPointType_TopRightCorner:
-        cp = ControlPoint(cropX + cropWidth, cropY, RadiographyControlPointType_TopRightCorner);
-        break;
-
-      case RadiographyControlPointType_BottomLeftCorner:
-        cp = ControlPoint(cropX, cropY + cropHeight, RadiographyControlPointType_BottomLeftCorner);
-        break;
-
-      case RadiographyControlPointType_BottomRightCorner:
-        cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, RadiographyControlPointType_BottomRightCorner);
-        break;
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    // transforms image coordinates into scene coordinates
-    GetTransform().Apply(cp.x, cp.y);
-    cpScene = cp;
-  }
-
-  bool RadiographyLayer::LookupControlPoint(ControlPoint& cpScene /* out */,
-                                            double x,
-                                            double y,
-                                            double zoom,
-                                            double viewportDistance) const
-  {
-    double threshold = Square(viewportDistance / zoom);
-
-    for (size_t i = 0; i < GetControlPointCount(); i++)
-    {
-      ControlPoint cp;
-      GetControlPoint(cp, i);
-
-      double d = Square(cp.x - x) + Square(cp.y - y);
-
-      if (d <= threshold)
-      {
-        cpScene = cp;
-        return true;
-      }
-    }
-
-    return false;
-  }
-}
--- a/Framework/Radiography/RadiographyLayer.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,395 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 <algorithm>
-
-#include "../Toolbox/AffineTransform2D.h"
-#include "../Toolbox/Extent2D.h"
-#include "../Wrappers/CairoContext.h"
-#include "../Messages/IMessage.h"
-#include "../Messages/IObservable.h"
-
-namespace OrthancStone
-{
-  class RadiographyScene;
-
-  enum RadiographyControlPointType
-  {
-    RadiographyControlPointType_TopLeftCorner = 0,
-    RadiographyControlPointType_TopRightCorner = 1,
-    RadiographyControlPointType_BottomRightCorner = 2,
-    RadiographyControlPointType_BottomLeftCorner = 3
-  };
-
-  enum RadiographyPhotometricDisplayMode
-  {
-    RadiographyPhotometricDisplayMode_Default,
-
-    RadiographyPhotometricDisplayMode_Monochrome1,
-    RadiographyPhotometricDisplayMode_Monochrome2
-  };
-
-  
-  struct ControlPoint
-  {
-    double x;
-    double y;
-    size_t index;
-
-    ControlPoint(double x, double y, size_t index)
-      : x(x),
-        y(y),
-        index(index)
-    {}
-
-    ControlPoint()
-      : x(0),
-        y(0),
-        index(std::numeric_limits<size_t>::max())
-    {}
-  };
-
-  class RadiographyLayer : public IObservable
-  {
-    friend class RadiographyScene;
-
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, LayerEditedMessage, RadiographyLayer);
-
-    class Geometry
-    {
-      bool               hasCrop_;
-      unsigned int       cropX_;
-      unsigned int       cropY_;
-      unsigned int       cropWidth_;
-      unsigned int       cropHeight_;
-      bool               flipVertical_;
-      bool               flipHorizontal_;
-      double             panX_;
-      double             panY_;
-      double             angle_;
-      bool               resizeable_;
-      double             pixelSpacingX_;
-      double             pixelSpacingY_;
-
-    public:
-      Geometry();
-
-      void ResetCrop()
-      {
-        hasCrop_ = false;
-      }
-
-      void SetCrop(unsigned int x,
-                   unsigned int y,
-                   unsigned int width,
-                   unsigned int height)
-      {
-        hasCrop_ = true;
-        cropX_ = x;
-        cropY_ = y;
-        cropWidth_ = width;
-        cropHeight_ = height;
-      }
-
-      bool HasCrop() const
-      {
-        return hasCrop_;
-      }
-
-      void GetCrop(unsigned int& x,
-                   unsigned int& y,
-                   unsigned int& width,
-                   unsigned int& height) const;
-
-      void SetAngle(double angle)
-      {
-        angle_ = angle;
-      }
-
-      double GetAngle() const
-      {
-        return angle_;
-      }
-
-      void SetPan(double x,
-                  double y)
-      {
-        panX_ = x;
-        panY_ = y;
-      }
-
-      double GetPanX() const
-      {
-        return panX_;
-      }
-
-      double GetPanY() const
-      {
-        return panY_;
-      }
-
-      bool IsResizeable() const
-      {
-        return resizeable_;
-      }
-
-      void SetResizeable(bool resizeable)
-      {
-        resizeable_ = resizeable;
-      }
-
-      void SetPixelSpacing(double x,
-                           double y)
-      {
-        pixelSpacingX_ = x;
-        pixelSpacingY_ = y;
-      }
-
-      double GetPixelSpacingX() const
-      {
-        return pixelSpacingX_;
-      }
-
-      double GetPixelSpacingY() const
-      {
-        return pixelSpacingY_;
-      }
-
-      void SetFlipVertical(bool flip) //  mirrors image around an horizontal axis (note: flip is applied before the rotation !)
-      {
-        flipVertical_ = flip;
-      }
-
-      void SetFlipHorizontal(bool flip) //  mirrors image around a vertical axis (note: flip is applied before the rotation !)
-      {
-        flipHorizontal_ = flip;
-      }
-
-      bool GetFlipVertical() const
-      {
-        return flipVertical_;
-      }
-
-      bool GetFlipHorizontal() const
-      {
-        return flipHorizontal_;
-      }
-
-      double GetScalingX() const
-      {
-        return (flipHorizontal_ ? - pixelSpacingX_: pixelSpacingX_);
-      }
-
-      double GetScalingY() const
-      {
-        return (flipVertical_ ? - pixelSpacingY_: pixelSpacingY_);
-      }
-    };
-
-  private:
-    size_t             index_;
-    bool               hasSize_;
-    unsigned int       width_;
-    unsigned int       height_;
-    AffineTransform2D  transform_;
-    AffineTransform2D  transformInverse_;
-    Geometry           geometry_;
-    RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode_;
-    const RadiographyScene&   scene_;
-
-  protected:
-    void SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode);
-
-  private:
-    void UpdateTransform();
-
-    void AddToExtent(Extent2D& extent,
-                     double x,
-                     double y) const;
-
-    void SetIndex(size_t index)
-    {
-      index_ = index;
-    }
-
-    bool Contains(double x,
-                  double y) const;
-
-    void DrawBorders(CairoContext& context,
-                     double zoom);
-
-  public:
-    RadiographyLayer(const RadiographyScene& scene);
-
-    virtual ~RadiographyLayer()
-    {
-    }
-
-    virtual const AffineTransform2D& GetTransform() const
-    {
-      return transform_;
-    }
-
-    virtual const AffineTransform2D& GetTransformInverse() const
-    {
-      return transformInverse_;
-    }
-
-    size_t GetIndex() const
-    {
-      return index_;
-    }
-
-    const RadiographyScene& GetScene() const
-    {
-      return scene_;
-    }
-
-    const Geometry& GetGeometry() const
-    {
-      return geometry_;
-    }
-
-    void SetGeometry(const Geometry& geometry);
-
-    void ResetCrop();
-
-    void SetCrop(unsigned int x,       // those are pixel coordinates/size
-                 unsigned int y,
-                 unsigned int width,
-                 unsigned int height);
-
-    void SetCrop(const Extent2D& sceneExtent)
-    {
-      Extent2D imageCrop;
-
-      {
-        double x = sceneExtent.GetX1();
-        double y = sceneExtent.GetY1();
-        GetTransformInverse().Apply(x, y);
-        imageCrop.AddPoint(x, y);
-      }
-
-      {
-        double x = sceneExtent.GetX2();
-        double y = sceneExtent.GetY2();
-        GetTransformInverse().Apply(x, y);
-        imageCrop.AddPoint(x, y);
-      }
-
-      SetCrop(static_cast<unsigned int>(std::max(0.0, std::floor(imageCrop.GetX1()))),
-              static_cast<unsigned int>(std::max(0.0, std::floor(imageCrop.GetY1()))),
-              std::min(width_, static_cast<unsigned int>(std::ceil(imageCrop.GetWidth()))),
-              std::min(height_, static_cast<unsigned int>(std::ceil(imageCrop.GetHeight())))
-              );
-    }
-
-
-    void GetCrop(unsigned int& x,
-                 unsigned int& y,
-                 unsigned int& width,
-                 unsigned int& height) const;
-
-    void SetAngle(double angle);
-
-    void SetPan(double x,
-                double y);
-
-    void SetFlipVertical(bool flip); //  mirrors image around an horizontal axis (note: flip is applied before the rotation !)
-
-    void SetFlipHorizontal(bool flip); //  mirrors image around a vertical axis (note: flip is applied before the rotation !)
-
-    void SetResizeable(bool resizeable)
-    {
-      geometry_.SetResizeable(resizeable);
-    }
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 bool emitLayerEditedEvent = true);
-
-    bool HasSize() const
-    {
-      return hasSize_;
-    }
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    virtual Extent2D GetSceneExtent(bool minimal) const;
-
-    virtual bool GetPixel(unsigned int& imageX,
-                          unsigned int& imageY,
-                          double sceneX,
-                          double sceneY) const;
-
-    void SetPixelSpacing(double x,
-                         double y,
-                         bool emitLayerEditedEvent = true);
-
-    void GetCenter(double& centerX,
-                   double& centerY) const;
-
-    virtual void GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */,
-                                 size_t index) const;
-
-    virtual size_t GetControlPointCount() const;
-
-    bool LookupControlPoint(ControlPoint& cpScene /* out */,
-                            double x,
-                            double y,
-                            double zoom,
-                            double viewportDistance) const;
-
-    virtual bool GetDefaultWindowing(float& center,
-                                     float& width) const = 0;
-
-    RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const
-    {
-      return prefferedPhotometricDisplayMode_;
-    }
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation,
-                        float windowCenter,
-                        float windowWidth,
-                        bool applyWindowing) const = 0;
-
-    virtual bool GetRange(float& minValue,
-                          float& maxValue) const = 0;
-
-    virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs)
-    {
-      return 0;
-    }
-  };
-}
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayerCropTracker.h"
-
-#include "RadiographySceneCommand.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  class RadiographyLayerCropTracker::UndoRedoCommand : public RadiographySceneCommand
-  {
-  private:
-    unsigned int  sourceCropX_;
-    unsigned int  sourceCropY_;
-    unsigned int  sourceCropWidth_;
-    unsigned int  sourceCropHeight_;
-    unsigned int  targetCropX_;
-    unsigned int  targetCropY_;
-    unsigned int  targetCropWidth_;
-    unsigned int  targetCropHeight_;
-
-  protected:
-    virtual void UndoInternal(RadiographyLayer& layer) const
-    {
-      layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
-    }
-
-    virtual void RedoInternal(RadiographyLayer& layer) const
-    {
-      layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
-    }
-
-  public:
-    UndoRedoCommand(const RadiographyLayerCropTracker& tracker) :
-      RadiographySceneCommand(tracker.accessor_),
-      sourceCropX_(tracker.cropX_),
-      sourceCropY_(tracker.cropY_),
-      sourceCropWidth_(tracker.cropWidth_),
-      sourceCropHeight_(tracker.cropHeight_)
-    {
-      tracker.accessor_.GetLayer().GetCrop(targetCropX_, targetCropY_,
-                                           targetCropWidth_, targetCropHeight_);
-    }
-  };
-
-
-  RadiographyLayerCropTracker::RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
-                                                           RadiographyScene& scene,
-                                                           const Deprecated::ViewportGeometry& view,
-                                                           size_t layer,
-                                                           const ControlPoint& startControlPoint) :
-    undoRedoStack_(undoRedoStack),
-    accessor_(scene, layer),
-    startControlPoint_(startControlPoint)
-  {
-    if (accessor_.IsValid())
-    {
-      accessor_.GetLayer().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);
-    }
-  }
-
-
-  void RadiographyLayerCropTracker::Render(CairoContext& context,
-                                           double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void RadiographyLayerCropTracker::MouseUp()
-  {
-    if (accessor_.IsValid())
-    {
-      undoRedoStack_.Add(new UndoRedoCommand(*this));
-    }
-  }
-
-  
-  void RadiographyLayerCropTracker::MouseMove(int displayX,
-                                              int displayY,
-                                              double sceneX,
-                                              double sceneY,
-                                              const std::vector<Deprecated::Touch>& displayTouches,
-                                              const std::vector<Deprecated::Touch>& sceneTouches)
-  {
-    if (accessor_.IsValid())
-    {
-      unsigned int x, y;
-        
-      RadiographyLayer& layer = accessor_.GetLayer();
-      if (layer.GetPixel(x, y, sceneX, sceneY))
-      {
-        unsigned int targetX, targetWidth;
-
-        if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner ||
-            startControlPoint_.index == RadiographyControlPointType_BottomLeftCorner)
-        {
-          targetX = std::min(x, cropX_ + cropWidth_);
-          targetWidth = cropX_ + cropWidth_ - targetX;
-        }
-        else
-        {
-          targetX = cropX_;
-          targetWidth = std::max(x, cropX_) - cropX_;
-        }
-
-        unsigned int targetY, targetHeight;
-
-        if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner ||
-            startControlPoint_.index == RadiographyControlPointType_TopRightCorner)
-        {
-          targetY = std::min(y, cropY_ + cropHeight_);
-          targetHeight = cropY_ + cropHeight_ - targetY;
-        }
-        else
-        {
-          targetY = cropY_;
-          targetHeight = std::max(y, cropY_) - cropY_;
-        }
-
-        layer.SetCrop(targetX, targetY, targetWidth, targetHeight);
-      }
-    }
-  }
-}
--- a/Framework/Radiography/RadiographyLayerCropTracker.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "../Deprecated/Toolbox/ViewportGeometry.h"
-#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
-#include "RadiographyScene.h"
-
-namespace OrthancStone
-{
-  class RadiographyLayerCropTracker : public Deprecated::IWorldSceneMouseTracker
-  {
-  private:
-    class UndoRedoCommand;
-
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    ControlPoint                     startControlPoint_;
-    unsigned int                     cropX_;
-    unsigned int                     cropY_;
-    unsigned int                     cropWidth_;
-    unsigned int                     cropHeight_;
-
-  public:
-    RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack,
-                                RadiographyScene& scene,
-                                const Deprecated::ViewportGeometry& view,
-                                size_t layer,
-                                const ControlPoint& startControlPoint);
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Deprecated::Touch>& displayTouches,
-                           const std::vector<Deprecated::Touch>& sceneTouches);
-  };
-}
--- a/Framework/Radiography/RadiographyLayerMaskTracker.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayerMaskTracker.h"
-#include "RadiographyMaskLayer.h"
-
-#include "RadiographySceneCommand.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  class RadiographyLayerMaskTracker::UndoRedoCommand : public RadiographySceneCommand
-  {
-  private:
-    ControlPoint sourceSceneCp_;
-    ControlPoint targetSceneCp_;
-
-  protected:
-    virtual void UndoInternal(RadiographyLayer& layer) const
-    {
-      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
-      if (maskLayer == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      unsigned int ix, iy; // image coordinates
-      if (maskLayer->GetPixel(ix, iy, sourceSceneCp_.x, sourceSceneCp_.y))
-      {
-        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), sourceSceneCp_.index);
-      }
-    }
-
-    virtual void RedoInternal(RadiographyLayer& layer) const
-    {
-      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
-      if (maskLayer == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      unsigned int ix, iy; // image coordinates
-      if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y))
-      {
-        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index);
-      }
-    }
-
-  public:
-    UndoRedoCommand(const RadiographyLayerMaskTracker& tracker) :
-      RadiographySceneCommand(tracker.accessor_),
-      sourceSceneCp_(tracker.startSceneCp_),
-      targetSceneCp_(tracker.endSceneCp_)
-    {
-      RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&(tracker.accessor_.GetLayer()));
-      if (maskLayer == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      unsigned int ix, iy; // image coordinates
-      if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y))
-      {
-        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index);
-      }
-    }
-  };
-
-
-  RadiographyLayerMaskTracker::RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack,
-                                                           RadiographyScene& scene,
-                                                           const Deprecated::ViewportGeometry& view,
-                                                           size_t layer,
-                                                           const ControlPoint& startSceneControlPoint) :
-    undoRedoStack_(undoRedoStack),
-    accessor_(scene, layer),
-    startSceneCp_(startSceneControlPoint),
-    endSceneCp_(startSceneControlPoint)
-  {
-  }
-
-
-  void RadiographyLayerMaskTracker::Render(CairoContext& context,
-                                           double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void RadiographyLayerMaskTracker::MouseUp()
-  {
-    if (accessor_.IsValid() && startSceneCp_.x != endSceneCp_.x && startSceneCp_.y != endSceneCp_.y)
-    {
-      undoRedoStack_.Add(new UndoRedoCommand(*this));
-    }
-  }
-
-  
-  void RadiographyLayerMaskTracker::MouseMove(int displayX,
-                                              int displayY,
-                                              double sceneX,
-                                              double sceneY,
-                                              const std::vector<Deprecated::Touch>& displayTouches,
-                                              const std::vector<Deprecated::Touch>& sceneTouches)
-  {
-    if (accessor_.IsValid())
-    {
-      unsigned int ix, iy; // image coordinates
-
-      RadiographyLayer& layer = accessor_.GetLayer();
-      if (layer.GetPixel(ix, iy, sceneX, sceneY))
-      {
-        endSceneCp_ = ControlPoint(sceneX, sceneY, startSceneCp_.index);
-
-        RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer);
-        if (maskLayer == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-        maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), startSceneCp_.index);
-      }
-    }
-  }
-}
--- a/Framework/Radiography/RadiographyLayerMaskTracker.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "../Deprecated/Toolbox/ViewportGeometry.h"
-#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
-#include "RadiographyScene.h"
-
-namespace OrthancStone
-{
-  class RadiographyLayerMaskTracker : public Deprecated::IWorldSceneMouseTracker
-  {
-  private:
-    class UndoRedoCommand;
-
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    ControlPoint                     startSceneCp_;
-    ControlPoint                     endSceneCp_;
-
-  public:
-    RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack,
-                                RadiographyScene& scene,
-                                const Deprecated::ViewportGeometry& view,
-                                size_t layer,
-                                const ControlPoint& startSceneControlPoint);
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Deprecated::Touch>& displayTouches,
-                           const std::vector<Deprecated::Touch>& sceneTouches);
-  };
-}
--- a/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayerMoveTracker.h"
-
-#include "RadiographySceneCommand.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  class RadiographyLayerMoveTracker::UndoRedoCommand : public RadiographySceneCommand
-  {
-  private:
-    double  sourceX_;
-    double  sourceY_;
-    double  targetX_;
-    double  targetY_;
-
-  protected:
-    virtual void UndoInternal(RadiographyLayer& layer) const
-    {
-      layer.SetPan(sourceX_, sourceY_);
-    }
-
-    virtual void RedoInternal(RadiographyLayer& layer) const
-    {
-      layer.SetPan(targetX_, targetY_);
-    }
-
-  public:
-    UndoRedoCommand(const RadiographyLayerMoveTracker& tracker) :
-      RadiographySceneCommand(tracker.accessor_),
-      sourceX_(tracker.panX_),
-      sourceY_(tracker.panY_),
-      targetX_(tracker.accessor_.GetLayer().GetGeometry().GetPanX()),
-      targetY_(tracker.accessor_.GetLayer().GetGeometry().GetPanY())
-    {
-    }
-  };
-
-
-  RadiographyLayerMoveTracker::RadiographyLayerMoveTracker(UndoRedoStack& undoRedoStack,
-                                                           RadiographyScene& scene,
-                                                           size_t layer,
-                                                           double x,
-                                                           double y,
-                                                           bool oneAxis) :
-    undoRedoStack_(undoRedoStack),
-    accessor_(scene, layer),
-    clickX_(x),
-    clickY_(y),
-    oneAxis_(oneAxis)
-  {
-    if (accessor_.IsValid())
-    {
-      panX_ = accessor_.GetLayer().GetGeometry().GetPanX();
-      panY_ = accessor_.GetLayer().GetGeometry().GetPanY();
-    }
-  }
-
-
-  void RadiographyLayerMoveTracker::Render(CairoContext& context,
-                                           double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-  
-  void RadiographyLayerMoveTracker::MouseUp()
-  {
-    if (accessor_.IsValid())
-    {
-      undoRedoStack_.Add(new UndoRedoCommand(*this));
-    }
-  }
-
-  
-  void RadiographyLayerMoveTracker::MouseMove(int displayX,
-                                              int displayY,
-                                              double sceneX,
-                                              double sceneY,
-                                              const std::vector<Deprecated::Touch>& displayTouches,
-                                              const std::vector<Deprecated::Touch>& sceneTouches)
-  {
-    if (accessor_.IsValid())
-    {
-      double dx = sceneX - clickX_;
-      double dy = sceneY - clickY_;
-
-      if (oneAxis_)
-      {
-        if (fabs(dx) > fabs(dy))
-        {
-          accessor_.GetLayer().SetPan(dx + panX_, panY_);
-        }
-        else
-        {
-          accessor_.GetLayer().SetPan(panX_, dy + panY_);
-        }
-      }
-      else
-      {
-        accessor_.GetLayer().SetPan(dx + panX_, dy + panY_);
-      }
-    }
-  }
-}
--- a/Framework/Radiography/RadiographyLayerMoveTracker.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
-#include "RadiographyScene.h"
-
-namespace OrthancStone
-{
-  class RadiographyLayerMoveTracker : public Deprecated::IWorldSceneMouseTracker
-  {
-  private:
-    class UndoRedoCommand;
-    
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    double                           clickX_;
-    double                           clickY_;
-    double                           panX_;
-    double                           panY_;
-    bool                             oneAxis_;
-
-  public:
-    RadiographyLayerMoveTracker(UndoRedoStack& undoRedoStack,
-                                RadiographyScene& scene,
-                                size_t layer,
-                                double x,
-                                double y,
-                                bool oneAxis);
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Deprecated::Touch>& displayTouches,
-                           const std::vector<Deprecated::Touch>& sceneTouches);
-  };
-}
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayerResizeTracker.h"
-
-#include "RadiographySceneCommand.h"
-
-#include <Core/OrthancException.h>
-
-#include <boost/math/special_functions/round.hpp>
-
-
-namespace OrthancStone
-{
-  static double ComputeDistance(double x1,
-                                double y1,
-                                double x2,
-                                double y2)
-  {
-    double dx = x1 - x2;
-    double dy = y1 - y2;
-    return sqrt(dx * dx + dy * dy);
-  }
-      
-
-  class RadiographyLayerResizeTracker::UndoRedoCommand : public RadiographySceneCommand
-  {
-  private:
-    double   sourceSpacingX_;
-    double   sourceSpacingY_;
-    double   sourcePanX_;
-    double   sourcePanY_;
-    double   targetSpacingX_;
-    double   targetSpacingY_;
-    double   targetPanX_;
-    double   targetPanY_;
-
-  protected:
-    virtual void UndoInternal(RadiographyLayer& layer) const
-    {
-      layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
-      layer.SetPan(sourcePanX_, sourcePanY_);
-    }
-
-    virtual void RedoInternal(RadiographyLayer& layer) const
-    {
-      layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
-      layer.SetPan(targetPanX_, targetPanY_);
-    }
-
-  public:
-    UndoRedoCommand(const RadiographyLayerResizeTracker& tracker) :
-      RadiographySceneCommand(tracker.accessor_),
-      sourceSpacingX_(tracker.originalSpacingX_),
-      sourceSpacingY_(tracker.originalSpacingY_),
-      sourcePanX_(tracker.originalPanX_),
-      sourcePanY_(tracker.originalPanY_),
-      targetSpacingX_(tracker.accessor_.GetLayer().GetGeometry().GetPixelSpacingX()),
-      targetSpacingY_(tracker.accessor_.GetLayer().GetGeometry().GetPixelSpacingY()),
-      targetPanX_(tracker.accessor_.GetLayer().GetGeometry().GetPanX()),
-      targetPanY_(tracker.accessor_.GetLayer().GetGeometry().GetPanY())
-    {
-    }
-  };
-
-
-  RadiographyLayerResizeTracker::RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
-                                                               RadiographyScene& scene,
-                                                               size_t layer,
-                                                               const ControlPoint& startControlPoint,
-                                                               bool roundScaling) :
-    undoRedoStack_(undoRedoStack),
-    accessor_(scene, layer),
-    roundScaling_(roundScaling)
-  {
-    if (accessor_.IsValid() &&
-        accessor_.GetLayer().GetGeometry().IsResizeable())
-    {
-      originalSpacingX_ = accessor_.GetLayer().GetGeometry().GetPixelSpacingX();
-      originalSpacingY_ = accessor_.GetLayer().GetGeometry().GetPixelSpacingY();
-      originalPanX_ = accessor_.GetLayer().GetGeometry().GetPanX();
-      originalPanY_ = accessor_.GetLayer().GetGeometry().GetPanY();
-
-      size_t oppositeControlPointType;
-      switch (startControlPoint.index)
-      {
-        case RadiographyControlPointType_TopLeftCorner:
-          oppositeControlPointType = RadiographyControlPointType_BottomRightCorner;
-          break;
-
-        case RadiographyControlPointType_TopRightCorner:
-          oppositeControlPointType = RadiographyControlPointType_BottomLeftCorner;
-          break;
-
-        case RadiographyControlPointType_BottomLeftCorner:
-          oppositeControlPointType = RadiographyControlPointType_TopRightCorner;
-          break;
-
-        case RadiographyControlPointType_BottomRightCorner:
-          oppositeControlPointType = RadiographyControlPointType_TopLeftCorner;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      accessor_.GetLayer().GetControlPoint(startOppositeControlPoint_, oppositeControlPointType);
-
-      double d = ComputeDistance(startControlPoint.x, startControlPoint.y, startOppositeControlPoint_.x, startOppositeControlPoint_.y);
-      if (d >= std::numeric_limits<float>::epsilon())
-      {
-        baseScaling_ = 1.0 / d;
-      }
-      else
-      {
-        // Avoid division by zero in extreme cases
-        accessor_.Invalidate();
-      }
-    }
-  }
-
-
-  void RadiographyLayerResizeTracker::Render(CairoContext& context,
-                                             double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void RadiographyLayerResizeTracker::MouseUp()
-  {
-    if (accessor_.IsValid() &&
-        accessor_.GetLayer().GetGeometry().IsResizeable())
-    {
-      undoRedoStack_.Add(new UndoRedoCommand(*this));
-    }
-  }
-
-  
-  void RadiographyLayerResizeTracker::MouseMove(int displayX,
-                                                int displayY,
-                                                double sceneX,
-                                                double sceneY,
-                                                const std::vector<Deprecated::Touch>& displayTouches,
-                                                const std::vector<Deprecated::Touch>& sceneTouches)
-  {
-    static const double ROUND_SCALING = 0.1;
-        
-    if (accessor_.IsValid() &&
-        accessor_.GetLayer().GetGeometry().IsResizeable())
-    {
-      double scaling = ComputeDistance(startOppositeControlPoint_.x, startOppositeControlPoint_.y, sceneX, sceneY) * baseScaling_;
-
-      if (roundScaling_)
-      {
-        scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING);
-      }
-          
-      RadiographyLayer& layer = accessor_.GetLayer();
-      layer.SetPixelSpacing(scaling * originalSpacingX_,
-                            scaling * originalSpacingY_);
-
-      // Keep the opposite corner at a fixed location
-      ControlPoint currentOppositeCorner;
-      layer.GetControlPoint(currentOppositeCorner, startOppositeControlPoint_.index);
-      layer.SetPan(layer.GetGeometry().GetPanX() + startOppositeControlPoint_.x - currentOppositeCorner.x,
-                   layer.GetGeometry().GetPanY() + startOppositeControlPoint_.y - currentOppositeCorner.y);
-    }
-  }
-}
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
-#include "RadiographyScene.h"
-
-namespace OrthancStone
-{
-  class RadiographyLayerResizeTracker : public Deprecated::IWorldSceneMouseTracker
-  {
-  private:
-    class UndoRedoCommand;
-
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    bool                             roundScaling_;
-    double                           originalSpacingX_;
-    double                           originalSpacingY_;
-    double                           originalPanX_;
-    double                           originalPanY_;
-    ControlPoint                     startOppositeControlPoint_;
-    double                           baseScaling_;
-
-  public:
-    RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack,
-                                  RadiographyScene& scene,
-                                  size_t layer,
-                                  const ControlPoint& startControlPoint,
-                                  bool roundScaling);
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Deprecated::Touch>& displayTouches,
-                           const std::vector<Deprecated::Touch>& sceneTouches);
-  };
-}
--- a/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayerRotateTracker.h"
-
-#include "RadiographySceneCommand.h"
-
-#include <Core/OrthancException.h>
-
-#include <boost/math/constants/constants.hpp>
-#include <boost/math/special_functions/round.hpp>
-
-namespace OrthancStone
-{
-  class RadiographyLayerRotateTracker::UndoRedoCommand : public RadiographySceneCommand
-  {
-  private:
-    double  sourceAngle_;
-    double  targetAngle_;
-
-    static int ToDegrees(double angle)
-    {
-      return boost::math::iround(angle * 180.0 / boost::math::constants::pi<double>());
-    }
-      
-  protected:
-    virtual void UndoInternal(RadiographyLayer& layer) const
-    {
-      LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
-      layer.SetAngle(sourceAngle_);
-    }
-
-    virtual void RedoInternal(RadiographyLayer& layer) const
-    {
-      LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
-      layer.SetAngle(targetAngle_);
-    }
-
-  public:
-    UndoRedoCommand(const RadiographyLayerRotateTracker& tracker) :
-      RadiographySceneCommand(tracker.accessor_),
-      sourceAngle_(tracker.originalAngle_),
-      targetAngle_(tracker.accessor_.GetLayer().GetGeometry().GetAngle())
-    {
-    }
-  };
-
-
-  bool RadiographyLayerRotateTracker::ComputeAngle(double& angle /* out */,
-                                                   double sceneX,
-                                                   double sceneY) const
-  {
-    Vector u;
-    LinearAlgebra::AssignVector(u, sceneX - centerX_, sceneY - centerY_);
-
-    double nu = boost::numeric::ublas::norm_2(u);
-
-    if (!LinearAlgebra::IsCloseToZero(nu))
-    {
-      u /= nu;
-      angle = atan2(u[1], u[0]);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  RadiographyLayerRotateTracker::RadiographyLayerRotateTracker(UndoRedoStack& undoRedoStack,
-                                                               RadiographyScene& scene,
-                                                               const Deprecated::ViewportGeometry& view,
-                                                               size_t layer,
-                                                               double x,
-                                                               double y,
-                                                               bool roundAngles) :
-    undoRedoStack_(undoRedoStack),
-    accessor_(scene, layer),
-    roundAngles_(roundAngles)
-  {
-    if (accessor_.IsValid())
-    {
-      accessor_.GetLayer().GetCenter(centerX_, centerY_);
-      originalAngle_ = accessor_.GetLayer().GetGeometry().GetAngle();
-
-      double sceneX, sceneY;
-      view.MapDisplayToScene(sceneX, sceneY, x, y);
-
-      if (!ComputeAngle(clickAngle_, x, y))
-      {
-        accessor_.Invalidate();
-      }
-    }
-  }
-
-
-  void RadiographyLayerRotateTracker::Render(CairoContext& context,
-                                             double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void RadiographyLayerRotateTracker::MouseUp()
-  {
-    if (accessor_.IsValid())
-    {
-      undoRedoStack_.Add(new UndoRedoCommand(*this));
-    }
-  }
-
-  
-  void RadiographyLayerRotateTracker::MouseMove(int displayX,
-                                                int displayY,
-                                                double sceneX,
-                                                double sceneY,
-                                                const std::vector<Deprecated::Touch>& displayTouches,
-                                                const std::vector<Deprecated::Touch>& sceneTouches)
-  {
-    static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>(); 
-        
-    double angle;
-        
-    if (accessor_.IsValid() &&
-        ComputeAngle(angle, sceneX, sceneY))
-    {
-      angle = angle - clickAngle_ + originalAngle_;
-
-      if (roundAngles_)
-      {
-        angle = boost::math::round<double>((angle / ROUND_ANGLE) * ROUND_ANGLE);
-      }
-          
-      accessor_.GetLayer().SetAngle(angle);
-    }
-  }
-}
--- a/Framework/Radiography/RadiographyLayerRotateTracker.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "../Deprecated/Toolbox/ViewportGeometry.h"
-#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
-#include "RadiographyScene.h"
-
-
-namespace OrthancStone
-{
-  class RadiographyLayerRotateTracker : public Deprecated::IWorldSceneMouseTracker
-  {
-  private:
-    class UndoRedoCommand;
-
-    UndoRedoStack&                   undoRedoStack_;
-    RadiographyScene::LayerAccessor  accessor_;
-    double                           centerX_;
-    double                           centerY_;
-    double                           originalAngle_;
-    double                           clickAngle_;
-    bool                             roundAngles_;
-
-    bool ComputeAngle(double& angle /* out */,
-                      double sceneX,
-                      double sceneY) const;
-
-  public:
-    RadiographyLayerRotateTracker(UndoRedoStack& undoRedoStack,
-                                  RadiographyScene& scene,
-                                  const Deprecated::ViewportGeometry& view,
-                                  size_t layer,
-                                  double x,
-                                  double y,
-                                  bool roundAngles);
-
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Deprecated::Touch>& displayTouches,
-                           const std::vector<Deprecated::Touch>& sceneTouches);
-  };
-}
--- a/Framework/Radiography/RadiographyMaskLayer.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyMaskLayer.h"
-#include "RadiographyDicomLayer.h"
-
-#include "RadiographyScene.h"
-#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 = 0x77;
-  const unsigned char OUT_MASK_VALUE = 0xFF;
-
-  const AffineTransform2D& RadiographyMaskLayer::GetTransform() const
-  {
-    return dicomLayer_.GetTransform();
-  }
-
-  const AffineTransform2D& RadiographyMaskLayer::GetTransformInverse() const
-  {
-    return dicomLayer_.GetTransformInverse();
-  }
-
-  bool RadiographyMaskLayer::GetPixel(unsigned int& imageX,
-                        unsigned int& imageY,
-                        double sceneX,
-                        double sceneY) const
-  {
-    return dicomLayer_.GetPixel(imageX, imageY, sceneX, sceneY);
-  }
-
-  std::string RadiographyMaskLayer::GetInstanceId() const
-  {
-    return dicomLayer_.GetInstanceId();
-  }
-
-  void RadiographyMaskLayer::SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index)
-  {
-    if (index < corners_.size())
-      corners_[index] = corner;
-    else
-      corners_.push_back(corner);
-    invalidated_ = true;
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  void RadiographyMaskLayer::SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners)
-  {
-    corners_ = corners;
-    invalidated_ = true;
-
-    BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this));
-  }
-
-  Extent2D RadiographyMaskLayer::GetSceneExtent(bool minimal) const
-  {
-    if (!minimal)
-    {
-      return RadiographyLayer::GetSceneExtent(minimal);
-    }
-    else
-    { // get the extent of the in-mask area
-      Extent2D sceneExtent;
-
-      for (std::vector<Orthanc::ImageProcessing::ImagePoint>::const_iterator corner = corners_.begin(); corner != corners_.end(); ++corner)
-      {
-        double x = static_cast<double>(corner->GetX());
-        double y = static_cast<double>(corner->GetY());
-
-        dicomLayer_.GetTransform().Apply(x, y);
-        sceneExtent.AddPoint(x, y);
-      }
-      return sceneExtent;
-    }
-  }
-
-
-
-  void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer,
-                                    const AffineTransform2D& viewTransform,
-                                    ImageInterpolation interpolation,
-                                    float windowCenter,
-                                    float windowWidth,
-                                    bool applyWindowing) const
-  {
-    if (dicomLayer_.GetWidth() == 0 || dicomLayer_.GetSourceImage() == NULL) // nothing to do if the DICOM layer is not displayed (or not loaded)
-      return;
-
-    if (invalidated_)
-    {
-      mask_.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, dicomLayer_.GetWidth(), dicomLayer_.GetHeight(), false));
-
-      DrawMask();
-
-      invalidated_ = false;
-    }
-
-    {// rendering
-      if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      unsigned int cropX, cropY, cropWidth, cropHeight;
-      dicomLayer_.GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-      const AffineTransform2D t = AffineTransform2D::Combine(
-            viewTransform, dicomLayer_.GetTransform(),
-            AffineTransform2D::CreateOffset(cropX, cropY));
-
-      Orthanc::ImageAccessor cropped;
-      mask_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
-
-      Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
-
-
-      unsigned int x1, y1, x2, y2;
-      if (!OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2,
-                                                      t.GetHomogeneousMatrix(),
-                                                      cropped.GetWidth(),
-                                                      cropped.GetHeight(),
-                                                      buffer.GetWidth(),
-                                                      buffer.GetHeight()))
-      {
-        return;  // layer is outside the buffer
-      }
-
-      t.Apply(tmp, cropped, ImageInterpolation_Nearest, true /* clear */);
-
-      // 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
-      for (unsigned int y = y1; y <= y2; 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 = x1; x <= x2; x++, p++, q++)
-        {
-          if (*p != IN_MASK_VALUE)
-            *q = foreground_;
-          // else keep the underlying pixel value
-        }
-      }
-
-    }
-  }
-
-  void RadiographyMaskLayer::DrawMask() const
-  {
-    // first fill the complete image
-    Orthanc::ImageProcessing::Set(*mask_, OUT_MASK_VALUE);
-
-    // clip corners
-    std::vector<Orthanc::ImageProcessing::ImagePoint> clippedCorners;
-    for (size_t i = 0; i < corners_.size(); i++)
-    {
-      clippedCorners.push_back(corners_[i]);
-      clippedCorners[i].ClipTo(0, mask_->GetWidth() - 1, 0, mask_->GetHeight() - 1);
-    }
-
-    // fill mask
-    Orthanc::ImageProcessing::FillPolygon(*mask_, clippedCorners, IN_MASK_VALUE);
-
-  }
-
-}
--- a/Framework/Radiography/RadiographyMaskLayer.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
-
-#include <Core/Compatibility.h>
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-
-namespace OrthancStone
-{
-  class RadiographyScene;
-  class RadiographyDicomLayer;
-
-  class RadiographyMaskLayer : public RadiographyLayer
-  {
-  private:
-    std::vector<Orthanc::ImageProcessing::ImagePoint>            corners_;
-    const RadiographyDicomLayer&      dicomLayer_;
-    mutable bool                      invalidated_;
-    float                             foreground_;
-
-    mutable std::unique_ptr<Orthanc::ImageAccessor>  mask_;
-  public:
-    RadiographyMaskLayer(const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer,
-                         float foreground) :
-      RadiographyLayer(scene),
-      dicomLayer_(dicomLayer),
-      invalidated_(true),
-      foreground_(foreground)
-    {
-    }
-
-    virtual size_t GetApproximateMemoryUsage() const
-    {
-      size_t size = 0;
-      if (mask_.get() != NULL)
-      {
-        size += mask_->GetPitch() * mask_->GetHeight();
-      }
-
-      return size;
-    }
-
-
-    void SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners);
-    void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index);
-
-    const std::vector<Orthanc::ImageProcessing::ImagePoint>& GetCorners() const
-    {
-      return corners_;
-    }
-
-    float GetForeground() const
-    {
-      return foreground_;
-    }
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation,
-                        float windowCenter,
-                        float windowWidth,
-                        bool applyWindowing) const;
-
-    std::string GetInstanceId() const;
-
-    virtual size_t GetControlPointCount() const
-    {
-      return corners_.size();
-    }
-
-    virtual void GetControlPoint(ControlPoint& cpScene,
-                                         size_t index) const
-    {
-      ControlPoint cp(corners_[index].GetX(), corners_[index].GetY(), index);
-
-      // transforms image coordinates into scene coordinates
-      GetTransform().Apply(cp.x, cp.y);
-      cpScene = cp;
-    }
-
-    virtual Extent2D GetSceneExtent(bool minimal) const;
-
-    virtual bool GetDefaultWindowing(float& center,
-                                     float& width) const
-    {
-      return false;
-    }
-
-    virtual bool GetRange(float& minValue,
-                          float& maxValue) const
-    {
-      minValue = 0;
-      maxValue = 0;
-
-      if (foreground_ < 0)
-      {
-        minValue = foreground_;
-      }
-
-      if (foreground_ > 0)
-      {
-        maxValue = foreground_;
-      }
-
-      return true;
-
-    }
-
-    virtual bool GetPixel(unsigned int& imageX,
-                          unsigned int& imageY,
-                          double sceneX,
-                          double sceneY) const;
-
-  protected:
-    virtual const AffineTransform2D& GetTransform() const;
-
-    virtual const AffineTransform2D& GetTransformInverse() const;
-
-
-  private:
-    void DrawMask() const;
-
-  };
-}
--- a/Framework/Radiography/RadiographyScene.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,968 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyScene.h"
-
-#include "RadiographyAlphaLayer.h"
-#include "RadiographyDicomLayer.h"
-#include "RadiographyTextLayer.h"
-#include "RadiographyMaskLayer.h"
-#include "../Deprecated/Toolbox/DicomFrameConverter.h"
-#include "../Scene2D/CairoCompositor.h"
-#include "../Scene2D/FloatTextureSceneLayer.h"
-#include "../Scene2D/TextSceneLayer.h"
-
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Images/PamReader.h>
-#include <Core/Images/PamWriter.h>
-#include <Core/Images/PngWriter.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-#include <Plugins/Samples/Common/DicomDatasetReader.h>
-#include <Plugins/Samples/Common/FullOrthancDataset.h>
-
-#include <boost/math/special_functions/round.hpp>
-
-
-namespace OrthancStone
-{
-  RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
-                                                 size_t index) :
-    scene_(scene),
-    index_(index)
-  {
-    Layers::iterator layer = scene.layers_.find(index);
-    if (layer == scene.layers_.end())
-    {
-      layer_ = NULL;
-    }
-    else
-    {
-      assert(layer->second != NULL);
-      layer_ = layer->second;
-    }
-  }
-
-
-  RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
-                                                 double x,
-                                                 double y) :
-    scene_(scene),
-    index_(0)  // Dummy initialization
-  {
-    if (scene.LookupLayer(index_, x, y))
-    {
-      Layers::iterator layer = scene.layers_.find(index_);
-
-      if (layer == scene.layers_.end())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      else
-      {
-        assert(layer->second != NULL);
-        layer_ = layer->second;
-      }
-    }
-    else
-    {
-      layer_ = NULL;
-    }
-  }
-
-
-  RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const
-  {
-    if (IsValid())
-    {
-      return scene_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
-  size_t RadiographyScene::LayerAccessor::GetIndex() const
-  {
-    if (IsValid())
-    {
-      return index_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  
-  RadiographyLayer& RadiographyScene::LayerAccessor::GetLayer() const
-  {
-    if (IsValid())
-    {
-      return *layer_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  void RadiographyScene::_RegisterLayer(RadiographyLayer* layer)
-  {
-    std::unique_ptr<RadiographyLayer> raii(layer);
-
-    // LOG(INFO) << "Registering layer: " << countLayers_;
-
-    size_t index = nextLayerIndex_++;
-    raii->SetIndex(index);
-    layers_[index] = raii.release();
-  }
-
-  RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer)
-  {
-    if (layer == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    _RegisterLayer(layer);
-
-    BroadcastMessage(GeometryChangedMessage(*this, *layer));
-    BroadcastMessage(ContentChangedMessage(*this, *layer));
-    Register<RadiographyLayer::LayerEditedMessage>(*layer, &RadiographyScene::OnLayerEdited);
-
-    return *layer;
-  }
-
-  size_t RadiographyScene::GetApproximateMemoryUsage() const
-  {
-    size_t size = 0;
-    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
-    {
-      size += it->second->GetApproximateMemoryUsage();
-    }
-    return size;
-  }
-
-  void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message)
-  {
-    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
-  }
-
-  
-  RadiographyScene::RadiographyScene() :
-    nextLayerIndex_(0),
-    hasWindowing_(false),
-    windowingCenter_(0),  // Dummy initialization
-    windowingWidth_(0)    // Dummy initialization
-  {
-  }
-
-
-  RadiographyScene::~RadiographyScene()
-  {
-    for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++)
-    {
-      assert(it->second != NULL);
-      delete it->second;
-    }
-  }
-
-  RadiographyPhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const
-  {
-    // return the mode of the first layer who "cares" about its display mode (normaly, the one and only layer that is a DicomLayer)
-    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
-    {
-      if (it->second->GetPreferredPhotomotricDisplayMode() != RadiographyPhotometricDisplayMode_Default)
-      {
-        return it->second->GetPreferredPhotomotricDisplayMode();
-      }
-    }
-
-    return RadiographyPhotometricDisplayMode_Default;
-  }
-
-
-  void RadiographyScene::GetLayersIndexes(std::vector<size_t>& output) const
-  {
-    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
-    {
-      output.push_back(it->first);
-    }
-  }
-
-  void RadiographyScene::RemoveLayer(size_t layerIndex)
-  {
-    LOG(INFO) << "Removing layer: " << layerIndex;
-
-    Layers::iterator found = layers_.find(layerIndex);
-
-    if (found == layers_.end())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(found->second != NULL);
-      delete found->second;
-      
-      layers_.erase(found);
-      
-      LOG(INFO) << "Removing layer, there are now : " << layers_.size() << " layers";
-
-      _OnLayerRemoved();
-
-      BroadcastMessage(RadiographyScene::LayerRemovedMessage(*this, layerIndex));
-    }
-  }
-
-  const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const
-  {
-    Layers::const_iterator found = layers_.find(layerIndex);
-    
-    if (found == layers_.end())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(found->second != NULL);
-      return *found->second;
-    }
-  }
-
-  RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex)
-  {
-    Layers::const_iterator found = layers_.find(layerIndex);
-
-    if (found == layers_.end())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(found->second != NULL);
-      return *found->second;
-    }
-  }
-
-  bool RadiographyScene::GetWindowing(float& center,
-                                      float& width) const
-  {
-    if (hasWindowing_)
-    {
-      center = windowingCenter_;
-      width = windowingWidth_;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  void RadiographyScene::GetWindowingWithDefault(float& center,
-                                                 float& width) const
-  {
-    if (!GetWindowing(center, width))
-    {
-      center = 128;
-      width = 256;
-    }
-  }
-
-
-  void RadiographyScene::SetWindowing(float center,
-                                      float width)
-  {
-    hasWindowing_ = true;
-    windowingCenter_ = center;
-    windowingWidth_ = width;
-
-    BroadcastMessage(RadiographyScene::WindowingChangedMessage(*this));
-  }
-
-
-  RadiographyLayer& RadiographyScene::UpdateText(size_t layerIndex,
-                                                 const std::string& utf8,
-                                                 const std::string& font,
-                                                 unsigned int fontSize,
-                                                 uint8_t foreground)
-  {
-    RadiographyTextLayer& textLayer = dynamic_cast<RadiographyTextLayer&>(GetLayer(layerIndex));
-    textLayer.SetText(utf8, font, fontSize, foreground);
-
-    BroadcastMessage(RadiographyScene::ContentChangedMessage(*this, textLayer));
-    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, textLayer));
-    return textLayer;
-  }
-
-
-  RadiographyLayer& RadiographyScene::LoadText(const std::string& utf8,
-                                               const std::string& font,
-                                               unsigned int fontSize,
-                                               uint8_t foreground,
-                                               RadiographyLayer::Geometry* centerGeometry,
-                                               bool isCenterGeometry)
-  {
-    std::unique_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(*this));
-    alpha->SetText(utf8, font, fontSize, foreground);
-    if (centerGeometry != NULL)
-    {
-      if (isCenterGeometry)
-      {
-        // modify geometry to reference the top left corner
-        double tlx = centerGeometry->GetPanX();
-        double tly = centerGeometry->GetPanY();
-        Extent2D textExtent = alpha->GetSceneExtent(false);
-        tlx = tlx - (textExtent.GetWidth() / 2) * centerGeometry->GetPixelSpacingX();
-        tly = tly - (textExtent.GetHeight() / 2) * centerGeometry->GetPixelSpacingY();
-        centerGeometry->SetPan(tlx, tly);
-      }
-      alpha->SetGeometry(*centerGeometry);
-    }
-
-    RadiographyLayer& registeredLayer = RegisterLayer(alpha.release());
-
-    BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, registeredLayer));
-    return registeredLayer;
-  }
-
-
-  RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width,
-                                                    unsigned int height,
-                                                    RadiographyLayer::Geometry* geometry)
-  {
-    std::unique_ptr<Orthanc::Image>  block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
-
-    for (unsigned int padding = 0;
-         (width > 2 * padding) && (height > 2 * padding);
-         padding++)
-    {
-      uint8_t color;
-      if (255 > 10 * padding)
-      {
-        color = 255 - 10 * padding;
-      }
-      else
-      {
-        color = 0;
-      }
-
-      Orthanc::ImageAccessor region;
-      block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding);
-      Orthanc::ImageProcessing::Set(region, color);
-    }
-
-    return LoadAlphaBitmap(block.release(), geometry);
-  }
-
-  RadiographyLayer& RadiographyScene::LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
-                                               const RadiographyDicomLayer& dicomLayer,
-                                               float foreground,
-                                               RadiographyLayer::Geometry* geometry)
-  {
-    std::unique_ptr<RadiographyMaskLayer>  mask(new RadiographyMaskLayer(*this, dicomLayer, foreground));
-    mask->SetCorners(corners);
-    if (geometry != NULL)
-    {
-      mask->SetGeometry(*geometry);
-    }
-
-    return RegisterLayer(mask.release());
-  }
-
-
-  RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry)
-  {
-    std::unique_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(*this));
-    alpha->SetAlpha(bitmap);
-    if (geometry != NULL)
-    {
-      alpha->SetGeometry(*geometry);
-    }
-
-    return RegisterLayer(alpha.release());
-  }
-
-  RadiographyLayer& RadiographyScene::LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
-                                                     const std::string& instance,
-                                                     unsigned int frame,
-                                                     Deprecated::DicomFrameConverter* converter,  // takes ownership
-                                                     RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
-                                                     RadiographyLayer::Geometry* geometry)
-  {
-    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(*this)));
-
-    layer.SetInstance(instance, frame);
-
-    if (geometry != NULL)
-    {
-      layer.SetGeometry(*geometry);
-    }
-
-    layer.SetDicomFrameConverter(converter);
-    layer.SetSourceImage(dicomImage);
-    layer.SetPreferredPhotomotricDisplayMode(preferredPhotometricDisplayMode);
-
-    return layer;
-  }
-
-  RadiographyLayer& RadiographyScene::LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
-                                                     const std::string& instance,
-                                                     unsigned int frame,
-                                                     bool httpCompression,
-                                                     RadiographyLayer::Geometry* geometry)
-  {
-    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer( *this)));
-    layer.SetInstance(instance, frame);
-
-    if (geometry != NULL)
-    {
-      layer.SetGeometry(*geometry);
-    }
-
-    {
-      Deprecated::IWebService::HttpHeaders headers;
-      std::string uri = "/instances/" + instance + "/tags";
-
-      orthanc.GetBinaryAsync(
-            uri, headers,
-            new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
-            (GetSharedObserver(), &RadiographyScene::OnTagsReceived), NULL,
-            new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
-    }
-
-    {
-      Deprecated::IWebService::HttpHeaders headers;
-      headers["Accept"] = "image/x-portable-arbitrarymap";
-
-      if (httpCompression)
-      {
-        headers["Accept-Encoding"] = "gzip";
-      }
-
-      std::string uri = ("/instances/" + instance + "/frames/" +
-                         boost::lexical_cast<std::string>(frame) + "/image-uint16");
-
-      orthanc.GetBinaryAsync(
-            uri, headers,
-            new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
-            (GetSharedObserver(), &RadiographyScene::OnFrameReceived), NULL,
-            new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
-    }
-
-    return layer;
-  }
-
-
-  RadiographyLayer& RadiographyScene::LoadDicomWebFrame(Deprecated::IWebService& web)
-  {
-    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(*this));
-
-
-    return layer;
-  }
-
-
-
-  void RadiographyScene::OnTagsReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message)
-  {
-    size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>
-        (message.GetPayload()).GetValue();
-
-    VLOG(1) << "JSON received: " << message.GetUri().c_str()
-            << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
-
-    Layers::iterator layer = layers_.find(index);
-    if (layer != layers_.end())
-    {
-      assert(layer->second != NULL);
-
-      OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize());
-      dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetDicomTags(dicom);
-
-      float c, w;
-      if (!hasWindowing_ &&
-          layer->second->GetDefaultWindowing(c, w))
-      {
-        hasWindowing_ = true;
-        windowingCenter_ = c;
-        windowingWidth_ = w;
-      }
-
-      BroadcastMessage(GeometryChangedMessage(*this, *(layer->second)));
-    }
-  }
-
-
-  void RadiographyScene::OnFrameReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message)
-  {
-    size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue();
-
-    VLOG(1) << "DICOM frame received: " << message.GetUri().c_str()
-            << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
-
-    Layers::iterator layer = layers_.find(index);
-    if (layer != layers_.end())
-    {
-      assert(layer->second != NULL);
-
-      std::string content;
-      if (message.GetAnswerSize() > 0)
-      {
-        content.assign(reinterpret_cast<const char*>(message.GetAnswer()), message.GetAnswerSize());
-      }
-
-      std::unique_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
-      reader->ReadFromMemory(content);
-      dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release());
-
-      BroadcastMessage(ContentChangedMessage(*this, *(layer->second)));
-    }
-  }
-
-
-  Extent2D RadiographyScene::GetSceneExtent(bool minimal) const
-  {
-    Extent2D extent;
-
-    for (Layers::const_iterator it = layers_.begin();
-         it != layers_.end(); ++it)
-    {
-      assert(it->second != NULL);
-      extent.Union(it->second->GetSceneExtent(minimal));
-    }
-
-    return extent;
-  }
-
-
-  void RadiographyScene::Render(Orthanc::ImageAccessor& buffer,
-                                const AffineTransform2D& viewTransform,
-                                ImageInterpolation interpolation,
-                                bool applyWindowing) const
-  {
-    // Render layers in the background-to-foreground order
-    for (size_t index = 0; index < nextLayerIndex_; index++)
-    {
-      try
-      {
-        Layers::const_iterator it = layers_.find(index);
-        if (it != layers_.end())
-        {
-          assert(it->second != NULL);
-          it->second->Render(buffer, viewTransform, interpolation, windowingCenter_, windowingWidth_, applyWindowing);
-        }
-      }
-      catch (Orthanc::OrthancException& ex)
-      {
-        LOG(ERROR) << "RadiographyScene::Render: " << index << ", OrthancException: " << ex.GetDetails();
-        throw ex; // rethrow because we want it to crash to see there's a problem !
-      }
-      catch (...)
-      {
-        LOG(ERROR) << "RadiographyScene::Render: " << index << ", unkown exception: ";
-        throw; // rethrow because we want it to crash to see there's a problem !
-      }
-    }
-  }
-
-
-  bool RadiographyScene::LookupLayer(size_t& index /* out */,
-                                     double x,
-                                     double y) const
-  {
-    // Render layers in the foreground-to-background order
-    for (size_t i = nextLayerIndex_; i > 0; i--)
-    {
-      index = i - 1;
-      Layers::const_iterator it = layers_.find(index);
-      if (it != layers_.end())
-      {
-        assert(it->second != NULL);
-        if (it->second->Contains(x, y))
-        {
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
-
-
-  void RadiographyScene::DrawBorder(CairoContext& context,
-                                    unsigned int layer,
-                                    double zoom)
-  {
-    Layers::const_iterator found = layers_.find(layer);
-
-    if (found != layers_.end())
-    {
-      context.SetSourceColor(255, 0, 0);
-      found->second->DrawBorders(context, zoom);
-    }
-  }
-
-
-  void RadiographyScene::GetRange(float& minValue,
-                                  float& maxValue) const
-  {
-    bool first = true;
-
-    for (Layers::const_iterator it = layers_.begin();
-         it != layers_.end(); it++)
-    {
-      assert(it->second != NULL);
-
-      float a, b;
-      if (it->second->GetRange(a, b))
-      {
-        if (first)
-        {
-          minValue = a;
-          maxValue = b;
-          first = false;
-        }
-        else
-        {
-          minValue = std::min(a, minValue);
-          maxValue = std::max(b, maxValue);
-        }
-      }
-    }
-
-    if (first)
-    {
-      minValue = 0;
-      maxValue = 0;
-    }
-  }
-
-  void RadiographyScene::ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
-                                                       const Orthanc::ImageAccessor& renderedScene,
-                                                       size_t layerIndex,
-                                                       bool isCropped,
-                                                       ImageInterpolation interpolation)
-  {
-    Extent2D sceneExtent = GetSceneExtent(isCropped);
-
-    double pixelSpacingX = sceneExtent.GetWidth() / renderedScene.GetWidth();
-    double pixelSpacingY = sceneExtent.GetHeight() / renderedScene.GetHeight();
-
-    AffineTransform2D view = AffineTransform2D::Combine(
-          AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
-          AffineTransform2D::CreateOffset(-sceneExtent.GetX1(), -sceneExtent.GetY1()));
-
-    AffineTransform2D layerToSceneTransform = AffineTransform2D::Combine(
-          view,
-          GetLayer(layerIndex).GetTransform());
-
-    AffineTransform2D sceneToLayerTransform = AffineTransform2D::Invert(layerToSceneTransform);
-    sceneToLayerTransform.Apply(layer, renderedScene, interpolation, false);
-  }
-
-  Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX,
-                                                  double pixelSpacingY,
-                                                  ImageInterpolation interpolation,
-                                                  bool invert,
-                                                  int64_t maxValue /* for inversion */,
-                                                  bool autoCrop,
-                                                  bool applyWindowing)
-  {
-    if (pixelSpacingX <= 0 ||
-        pixelSpacingY <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    Extent2D extent = GetSceneExtent(autoCrop);
-
-    int w = boost::math::iround(extent.GetWidth() / pixelSpacingX);
-    int h = boost::math::iround(extent.GetHeight() / pixelSpacingY);
-
-    if (w < 0 || h < 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    Orthanc::Image layers(Orthanc::PixelFormat_Float32,
-                          static_cast<unsigned int>(w),
-                          static_cast<unsigned int>(h), false);
-
-    AffineTransform2D view = AffineTransform2D::Combine(
-          AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY),
-          AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1()));
-
-    // wipe background before rendering
-    if (GetPreferredPhotomotricDisplayMode() == RadiographyPhotometricDisplayMode_Monochrome1)
-    {
-      Orthanc::ImageProcessing::Set(layers, 65535);
-    }
-    else
-    {
-      Orthanc::ImageProcessing::Set(layers, 0);
-    }
-
-    Render(layers, view, interpolation, applyWindowing);
-
-    std::unique_ptr<Orthanc::Image> rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16,
-                                                              layers.GetWidth(), layers.GetHeight(), false));
-
-    Orthanc::ImageProcessing::Convert(*rendered, layers);
-    if (invert)
-      Orthanc::ImageProcessing::Invert(*rendered, maxValue);
-
-    return rendered.release();
-  }
-
-
-  Orthanc::Image* RadiographyScene::ExportToCreateDicomRequestAndImage(Json::Value& createDicomRequestContent,
-                                                                       const Json::Value& dicomTags,
-                                                                       const std::string& parentOrthancId,
-                                                                       double pixelSpacingX,
-                                                                       double pixelSpacingY,
-                                                                       bool invert,
-                                                                       bool autoCrop,
-                                                                       ImageInterpolation interpolation)
-  {
-    LOG(INFO) << "Exporting RadiographyScene to DICOM";
-
-    std::unique_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, autoCrop, 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;
-
-    RadiographyPhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode();
-    if ((invert && photometricMode != RadiographyPhotometricDisplayMode_Monochrome2) ||
-        (!invert && photometricMode == RadiographyPhotometricDisplayMode_Monochrome1))
-    {
-      createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1";
-    }
-    else
-    {
-      createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME2";
-    }
-
-    // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to
-    // avoid floating-point numbers to grow over 16 characters,
-    // which would be invalid according to DICOM standard
-    // ("dciodvfy" would complain).
-    char buf[32];
-    sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
-
-    createDicomRequestContent["Tags"]["PixelSpacing"] = buf;
-
-    float center, width;
-    if (GetWindowing(center, width))
-    {
-      createDicomRequestContent["Tags"]["WindowCenter"] =
-          boost::lexical_cast<std::string>(boost::math::iround(center));
-
-      createDicomRequestContent["Tags"]["WindowWidth"] =
-          boost::lexical_cast<std::string>(boost::math::iround(width));
-    }
-
-    if (!parentOrthancId.empty())
-    {
-      createDicomRequestContent["Parent"] = parentOrthancId;
-    }
-
-    return rendered.release();
-  }
-
-
-  void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
-                                                    const Json::Value& dicomTags,
-                                                    const std::string& parentOrthancId,
-                                                    double pixelSpacingX,
-                                                    double pixelSpacingY,
-                                                    bool invert,
-                                                    bool autoCrop,
-                                                    ImageInterpolation interpolation,
-                                                    bool usePam)
-  {
-    LOG(INFO) << "Exporting RadiographyScene to DICOM";
-    VLOG(1) << "Exporting RadiographyScene to: export to image";
-
-    std::unique_ptr<Orthanc::Image> rendered(ExportToCreateDicomRequestAndImage(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation));
-
-    // convert the image into base64 for inclusing in the createDicomRequest
-    std::string base64;
-
-    {
-      std::string content;
-
-      if (usePam)
-      {
-        VLOG(1) << "Exporting RadiographyScene: convert to PAM";
-        Orthanc::PamWriter writer;
-        writer.WriteToMemory(content, *rendered);
-      }
-      else
-      {
-        Orthanc::PngWriter writer;
-        writer.WriteToMemory(content, *rendered);
-      }
-
-      VLOG(1) << "Exporting RadiographyScene: encoding to base64";
-      Orthanc::Toolbox::EncodeBase64(base64, content);
-    }
-
-    // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme
-    createDicomRequestContent["Content"] = ("data:" +
-                                            std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) +
-                                            ";base64," + base64);
-
-    VLOG(1) << "Exporting RadiographyScene: create-dicom request is ready";
-  }
-
-
-  void RadiographyScene::ExportDicom(Deprecated::OrthancApiClient& orthanc,
-                                     const Json::Value& dicomTags,
-                                     const std::string& parentOrthancId,
-                                     double pixelSpacingX,
-                                     double pixelSpacingY,
-                                     bool invert,
-                                     bool autoCrop,
-                                     ImageInterpolation interpolation,
-                                     bool usePam)
-  {
-    Json::Value createDicomRequestContent;
-
-    ExportToCreateDicomRequest(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation, usePam);
-
-    orthanc.PostJsonAsyncExpectJson(
-          "/tools/create-dicom", createDicomRequestContent,
-          new Deprecated::DeprecatedCallable<RadiographyScene, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
-          (GetSharedObserver(), &RadiographyScene::OnDicomExported),
-          NULL, NULL);
-
-  }
-
-
-  // Export using PAM is faster than using PNG, but requires Orthanc
-  // core >= 1.4.3
-  void RadiographyScene::ExportDicom(Deprecated::OrthancApiClient& orthanc,
-                                     const Orthanc::DicomMap& dicom,
-                                     const std::string& parentOrthancId,
-                                     double pixelSpacingX,
-                                     double pixelSpacingY,
-                                     bool invert,
-                                     bool autoCrop,
-                                     ImageInterpolation interpolation,
-                                     bool usePam)
-  {
-    std::set<Orthanc::DicomTag> tags;
-    dicom.GetTags(tags);
-
-    Json::Value jsonTags = Json::objectValue;
-
-    for (std::set<Orthanc::DicomTag>::const_iterator
-         tag = tags.begin(); tag != tags.end(); ++tag)
-    {
-      const Orthanc::DicomValue& value = dicom.GetValue(*tag);
-      if (!value.IsNull() &&
-          !value.IsBinary())
-      {
-        jsonTags[tag->Format()] = value.GetContent();
-      }
-    }
-
-    ExportDicom(orthanc, jsonTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, autoCrop, interpolation, usePam);
-  }
-
-  void RadiographyScene::OnDicomExported(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    LOG(INFO) << "DICOM export was successful: "
-              << message.GetJson().toStyledString();
-  }
-
-
-  void RadiographyScene::OnDicomWebReceived(const Deprecated::IWebService::HttpRequestSuccessMessage& message)
-  {
-    LOG(INFO) << "DICOMweb WADO-RS received: " << message.GetAnswerSize() << " bytes";
-
-    const Deprecated::IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders();
-    for (Deprecated::IWebService::HttpHeaders::const_iterator
-         it = h.begin(); it != h.end(); ++it)
-    {
-      printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str());
-    }
-  }
-
-  void RadiographyScene::ExportToScene2D(Scene2D& output) const
-  {
-    int depth = 0;
-    for (Layers::const_iterator it = layers_.begin();
-         it != layers_.end(); ++it)
-    {
-      assert(it->second != NULL);
-
-      std::unique_ptr<ISceneLayer> layer;
-      if (dynamic_cast<RadiographyDicomLayer*>(it->second))
-      {
-        RadiographyDicomLayer* oldLayer = dynamic_cast<RadiographyDicomLayer*>(it->second);
-
-        std::unique_ptr<FloatTextureSceneLayer> newLayer(new FloatTextureSceneLayer(*(oldLayer->GetSourceImage())));
-
-        newLayer->SetOrigin(oldLayer->GetGeometry().GetPanX(),
-                            oldLayer->GetGeometry().GetPanY()
-                            );
-        newLayer->SetAngle(oldLayer->GetGeometry().GetAngle());
-
-        layer.reset(newLayer.release());
-
-        // TODO: windowing dynamic_cast
-      }
-      else if (dynamic_cast<RadiographyTextLayer*>(it->second))
-      {
-        RadiographyTextLayer* oldLayer = dynamic_cast<RadiographyTextLayer*>(it->second);
-
-        std::unique_ptr<TextSceneLayer> newLayer(new TextSceneLayer());
-
-        newLayer->SetText(oldLayer->GetText());
-        newLayer->SetColor(oldLayer->GetForegroundGreyLevel(),
-                           oldLayer->GetForegroundGreyLevel(),
-                           oldLayer->GetForegroundGreyLevel()
-                           );
-        newLayer->SetPosition(oldLayer->GetGeometry().GetPanX(),
-                              oldLayer->GetGeometry().GetPanY()
-                              );
-        newLayer->SetFontIndex(1);
-        newLayer->SetAnchor(BitmapAnchor_TopLeft);
-        //newLayer->SetAngle(oldLayer->GetGeometry().GetAngle());
-
-        layer.reset(newLayer.release());
-      }
-
-      output.SetLayer(depth++, layer.release());
-
-    }
-
-  }
-
-}
-
--- a/Framework/Radiography/RadiographyScene.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,370 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyLayer.h"
-#include "../Messages/ObserverBase.h"
-#include "../Deprecated/Toolbox/DicomFrameConverter.h"
-#include "../Deprecated/Toolbox/OrthancApiClient.h"
-#include "../StoneEnumerations.h"
-#include "Core/Images/Image.h"
-#include "Core/Images/ImageProcessing.h"
-
-#include "../Scene2D/Scene2D.h"
-
-namespace OrthancStone
-{
-  class RadiographyDicomLayer;
-
-  class RadiographyScene :
-    public ObserverBase<RadiographyScene>,
-    public IObservable
-  {
-    friend class RadiographySceneGeometryReader;
-  public:
-    class GeometryChangedMessage : public OriginMessage<RadiographyScene>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      RadiographyLayer&        layer_;
-
-    public:
-      GeometryChangedMessage(const RadiographyScene& origin,
-                             RadiographyLayer& layer) :
-        OriginMessage(origin),
-        layer_(layer)
-      {
-      }
-
-      RadiographyLayer& GetLayer() const
-      {
-        return layer_;
-      }
-    };
-
-    class ContentChangedMessage : public OriginMessage<RadiographyScene>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      RadiographyLayer&        layer_;
-
-    public:
-      ContentChangedMessage(const RadiographyScene& origin,
-                            RadiographyLayer& layer) :
-        OriginMessage(origin),
-        layer_(layer)
-      {
-      }
-
-      RadiographyLayer& GetLayer() const
-      {
-        return layer_;
-      }
-    };
-
-    class LayerEditedMessage : public OriginMessage<RadiographyScene>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      const RadiographyLayer&        layer_;
-
-    public:
-      LayerEditedMessage(const RadiographyScene& origin,
-                         const RadiographyLayer& layer) :
-        OriginMessage(origin),
-        layer_(layer)
-      {
-      }
-
-      const RadiographyLayer& GetLayer() const
-      {
-        return layer_;
-      }
-    };
-
-    class LayerRemovedMessage : public OriginMessage<RadiographyScene>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      size_t&        layerIndex_;
-
-    public:
-      LayerRemovedMessage(const RadiographyScene& origin,
-                          size_t& layerIndex) :
-        OriginMessage(origin),
-        layerIndex_(layerIndex)
-      {
-      }
-
-      size_t& GetLayerIndex() const
-      {
-        return layerIndex_;
-      }
-    };
-
-
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, WindowingChangedMessage, RadiographyScene);
-
-    
-    class LayerAccessor : public boost::noncopyable
-    {
-    private:
-      RadiographyScene&  scene_;
-      size_t             index_;
-      RadiographyLayer*  layer_;
-
-    public:
-      LayerAccessor(RadiographyScene& scene,
-                    size_t index);
-
-      LayerAccessor(RadiographyScene& scene,
-                    double x,
-                    double y);
-
-      void Invalidate()
-      {
-        layer_ = NULL;
-      }
-
-      bool IsValid() const
-      {
-        return layer_ != NULL;
-      }
-
-      RadiographyScene& GetScene() const;
-
-      size_t GetIndex() const;
-
-      RadiographyLayer& GetLayer() const;
-    };
-
-
-  protected:
-    typedef std::map<size_t, RadiographyLayer*>  Layers;
-
-    size_t  nextLayerIndex_;
-    bool    hasWindowing_;
-    float   windowingCenter_;
-    float   windowingWidth_;
-    Layers  layers_;
-
-  public:
-    RadiographyLayer& RegisterLayer(RadiographyLayer* layer);
-
-  protected:
-    virtual void _RegisterLayer(RadiographyLayer* layer);
-    virtual void _OnLayerRemoved() {}
-
-    void SetLayerIndex(RadiographyLayer* layer, size_t index)
-    {
-      layer->SetIndex(index);
-    }
-
-    virtual void OnTagsReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message);
-
-    virtual void OnFrameReceived(const Deprecated::OrthancApiClient::BinaryResponseReadyMessage& message);
-    
-    void OnDicomExported(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void OnDicomWebReceived(const Deprecated::IWebService::HttpRequestSuccessMessage& message);
-
-    virtual void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message);
-
-  public:
-    RadiographyScene();
-    
-    virtual ~RadiographyScene();
-
-    virtual size_t GetApproximateMemoryUsage() const;
-
-    bool GetWindowing(float& center,
-                      float& width) const;
-
-    void GetWindowingWithDefault(float& center,
-                                 float& width) const;
-
-    virtual void SetWindowing(float center,
-                              float width);
-
-    RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const;
-
-    RadiographyLayer& LoadText(const std::string& utf8,
-                               const std::string& font,
-                               unsigned int fontSize,
-                               uint8_t foreground,
-                               RadiographyLayer::Geometry* geometry,
-                               bool isCenterGeometry);
-
-    RadiographyLayer& UpdateText(size_t layerIndex,
-                                 const std::string& font,
-                                 const std::string& utf8,
-                                 unsigned int fontSize,
-                                 uint8_t foreground);
-
-    RadiographyLayer& LoadTestBlock(unsigned int width,
-                                    unsigned int height,
-                                    RadiographyLayer::Geometry* geometry);
-
-    RadiographyLayer& LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners,
-                               const RadiographyDicomLayer& dicomLayer,
-                               float foreground,
-                               RadiographyLayer::Geometry* geometry);
-
-    RadiographyLayer& LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap,  // takes ownership
-                                      RadiographyLayer::Geometry* geometry);
-
-    virtual RadiographyLayer& LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
-                                             const std::string& instance,
-                                             unsigned int frame,
-                                             Deprecated::DicomFrameConverter* converter,  // takes ownership
-                                             RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
-                                             RadiographyLayer::Geometry* geometry);
-
-    virtual RadiographyLayer& LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
-                                             const std::string& instance,
-                                             unsigned int frame,
-                                             bool httpCompression,
-                                             RadiographyLayer::Geometry* geometry); // pass NULL if you want default geometry
-
-    RadiographyLayer& LoadDicomWebFrame(Deprecated::IWebService& web);
-
-    void RemoveLayer(size_t layerIndex);
-
-    RadiographyLayer& GetLayer(size_t layerIndex);
-
-    const RadiographyLayer& GetLayer(size_t layerIndex) const;
-
-    template <typename TypeLayer>
-    TypeLayer* GetTypedLayer(size_t indexOfType = 0)
-    {
-      std::vector<size_t> layerIndexes;
-      GetLayersIndexes(layerIndexes);
-
-      size_t count = 0;
-
-      for (size_t i = 0; i < layerIndexes.size(); ++i)
-      {
-        TypeLayer* typedLayer = dynamic_cast<TypeLayer*>(layers_[layerIndexes[i]]);
-        if (typedLayer != NULL)
-        {
-          if (count == indexOfType)
-          {
-            return typedLayer;
-          }
-          count++;
-        }
-      }
-
-      return NULL;
-    }
-
-    void GetLayersIndexes(std::vector<size_t>& output) const;
-
-    virtual Extent2D GetSceneExtent(bool minimal) const;
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation,
-                        bool applyWindowing) const;
-
-    bool LookupLayer(size_t& index /* out */,
-                     double x,
-                     double y) const;
-    
-    void DrawBorder(CairoContext& context,
-                    unsigned int layer,
-                    double zoom);
-
-    void GetRange(float& minValue,
-                  float& maxValue) const;
-
-    void ExportToScene2D(Scene2D& output) const;
-
-    // Export using PAM is faster than using PNG, but requires Orthanc
-    // core >= 1.4.3
-    void ExportDicom(Deprecated::OrthancApiClient& orthanc,
-                     const Orthanc::DicomMap& dicom,
-                     const std::string& parentOrthancId,
-                     double pixelSpacingX,
-                     double pixelSpacingY,
-                     bool invert,
-                     bool autoCrop,
-                     ImageInterpolation interpolation,
-                     bool usePam);
-
-    void ExportDicom(Deprecated::OrthancApiClient& orthanc,
-                     const Json::Value& dicomTags,
-                     const std::string& parentOrthancId,
-                     double pixelSpacingX,
-                     double pixelSpacingY,
-                     bool invert,
-                     bool autoCrop,
-                     ImageInterpolation interpolation,
-                     bool usePam);
-
-    void ExportToCreateDicomRequest(Json::Value& createDicomRequestContent,
-                                    const Json::Value& dicomTags,
-                                    const std::string& parentOrthancId,
-                                    double pixelSpacingX,
-                                    double pixelSpacingY,
-                                    bool invert,
-                                    bool autoCrop,
-                                    ImageInterpolation interpolation,
-                                    bool usePam);
-
-    Orthanc::Image* ExportToCreateDicomRequestAndImage(Json::Value& createDicomRequestContent,
-                                                       const Json::Value& dicomTags,
-                                                       const std::string& parentOrthancId,
-                                                       double pixelSpacingX,
-                                                       double pixelSpacingY,
-                                                       bool invert,
-                                                       bool autoCrop,
-                                                       ImageInterpolation interpolation);
-
-    Orthanc::Image* ExportToImage(double pixelSpacingX,
-                                  double pixelSpacingY,
-                                  ImageInterpolation interpolation,
-                                  bool autoCrop,
-                                  bool applyWindowing)
-    {
-      return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0, autoCrop, applyWindowing);
-    }
-
-    Orthanc::Image* ExportToImage(double pixelSpacingX,
-                                  double pixelSpacingY,
-                                  ImageInterpolation interpolation,
-                                  bool invert,
-                                  int64_t maxValue /* for inversion */,
-                                  bool autoCrop,
-                                  bool applyWindowing);
-
-    void ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer,
-                                       const Orthanc::ImageAccessor& renderedScene,
-                                       size_t layerIndex,
-                                       bool isCropped,
-                                       ImageInterpolation interpolation);
-  };
-}
--- a/Framework/Radiography/RadiographySceneCommand.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographySceneCommand.h"
-
-
-namespace OrthancStone
-{
-  RadiographySceneCommand::RadiographySceneCommand(RadiographyScene& scene,
-                                                   size_t layer) :
-    scene_(scene),
-    layer_(layer)
-  {
-  }
-
-  
-  RadiographySceneCommand::RadiographySceneCommand(const RadiographyScene::LayerAccessor& accessor) :
-    scene_(accessor.GetScene()),
-    layer_(accessor.GetIndex())
-  {
-  }
-
-  
-  void RadiographySceneCommand::Undo() const
-  {
-    RadiographyScene::LayerAccessor accessor(scene_, layer_);
-
-    if (accessor.IsValid())
-    {
-      UndoInternal(accessor.GetLayer());
-    }
-  }
-
-  
-  void RadiographySceneCommand::Redo() const
-  {
-    RadiographyScene::LayerAccessor accessor(scene_, layer_);
-
-    if (accessor.IsValid())
-    {
-      RedoInternal(accessor.GetLayer());
-    }
-  }
-}
--- a/Framework/Radiography/RadiographySceneCommand.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "RadiographyScene.h"
-
-namespace OrthancStone
-{
-  class RadiographySceneCommand : public UndoRedoStack::ICommand
-  {
-  private:
-    RadiographyScene&  scene_;
-    size_t             layer_;
-
-  protected:
-    virtual void UndoInternal(RadiographyLayer& layer) const = 0;
-
-    virtual void RedoInternal(RadiographyLayer& layer) const = 0;
-
-  public:
-    RadiographySceneCommand(RadiographyScene& scene,
-                            size_t layer);
-
-    RadiographySceneCommand(const RadiographyScene::LayerAccessor& accessor);
-
-    virtual void Undo() const;
-
-    virtual void Redo() const;
-  };
-}
--- a/Framework/Radiography/RadiographySceneReader.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographySceneReader.h"
-
-#include "../Deprecated/Toolbox/DicomFrameConverter.h"
-
-#include <Core/Images/FontRegistry.h>
-#include <Core/Images/PngReader.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-
-namespace OrthancStone
-{
-
-  void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */,
-                                     Deprecated::DicomFrameConverter* dicomFrameConverter  /* takes ownership */,
-                                     RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode
-                                     )
-  {
-    dicomImage_.reset(dicomImage);
-    dicomFrameConverter_.reset(dicomFrameConverter);
-    preferredPhotometricDisplayMode_ = preferredPhotometricDisplayMode;
-    Read(input);
-  }
-
-  RadiographyDicomLayer* RadiographySceneBuilder::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
-  {
-    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomImage(dicomImage_.release(), instanceId, frame, dicomFrameConverter_.release(), preferredPhotometricDisplayMode_, geometry)));
-  }
-
-
-  RadiographyDicomLayer* RadiographySceneReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
-  {
-    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry)));
-  }
-
-  RadiographyDicomLayer* RadiographySceneGeometryReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
-  {
-    std::unique_ptr<RadiographyPlaceholderLayer>  layer(new RadiographyPlaceholderLayer(scene_));
-    layer->SetGeometry(*geometry);
-    layer->SetSize(dicomImageWidth_, dicomImageHeight_);
-    scene_.RegisterLayer(layer.get());
-
-    return layer.release();
-  }
-
-  void RadiographySceneBuilder::Read(const Json::Value& input)
-  {
-    unsigned int version = input["version"].asUInt();
-
-    if (version != 1)
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-
-    if (input.isMember("hasWindowing") && input["hasWindowing"].asBool())
-    {
-      scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat());
-    }
-
-    RadiographyDicomLayer* dicomLayer = NULL;
-    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
-    {
-      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
-      RadiographyLayer::Geometry geometry;
-
-      if (jsonLayer["type"].asString() == "dicom")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-        dicomLayer = LoadDicom(jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), &geometry);
-      }
-      else if (jsonLayer["type"].asString() == "mask")
-      {
-        if (dicomLayer == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask
-        }
-        ReadLayerGeometry(geometry, jsonLayer);
-
-        float foreground = jsonLayer["foreground"].asFloat();
-        std::vector<Orthanc::ImageProcessing::ImagePoint> corners;
-        for (size_t i = 0; i < jsonLayer["corners"].size(); i++)
-        {
-          Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(),
-              jsonLayer["corners"][(int)i]["y"].asInt());
-          corners.push_back(corner);
-        }
-
-        scene_.LoadMask(corners, *dicomLayer, foreground, &geometry);
-      }
-      else if (jsonLayer["type"].asString() == "text")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-        scene_.LoadText(jsonLayer["text"].asString(), jsonLayer["font"].asString(), jsonLayer["fontSize"].asUInt(), static_cast<uint8_t>(jsonLayer["foreground"].asUInt()), &geometry, false);
-      }
-      else if (jsonLayer["type"].asString() == "alpha")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-
-        const std::string& pngContentBase64 = jsonLayer["content"].asString();
-        std::string pngContent;
-        std::string mimeType;
-        Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64);
-
-        std::unique_ptr<Orthanc::ImageAccessor>  image;
-        if (mimeType == "image/png")
-        {
-          image.reset(new Orthanc::PngReader());
-          dynamic_cast<Orthanc::PngReader*>(image.get())->ReadFromMemory(pngContent);
-        }
-        else
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-
-        RadiographyAlphaLayer& layer = dynamic_cast<RadiographyAlphaLayer&>(scene_.LoadAlphaBitmap(image.release(), &geometry));
-
-        if (!jsonLayer["isUsingWindowing"].asBool())
-        {
-          layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble()));
-        }
-      }
-      else
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
-
-
-
-
-  void RadiographySceneBuilder::ReadDicomLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input)
-  {
-    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
-    {
-      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
-      if (jsonLayer["type"].asString() == "dicom")
-      {
-        ReadLayerGeometry(geometry, jsonLayer);
-        return;
-      }
-    }
-  }
-
-  void RadiographySceneBuilder::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer)
-  {
-    {// crop
-      unsigned int x, y, width, height;
-      if (jsonLayer["crop"]["hasCrop"].asBool())
-      {
-        x = jsonLayer["crop"]["x"].asUInt();
-        y = jsonLayer["crop"]["y"].asUInt();
-        width = jsonLayer["crop"]["width"].asUInt();
-        height = jsonLayer["crop"]["height"].asUInt();
-        geometry.SetCrop(x, y, width, height);
-      }
-    }
-
-    geometry.SetAngle(jsonLayer["angle"].asDouble());
-    geometry.SetResizeable(jsonLayer["isResizable"].asBool());
-    geometry.SetPan(jsonLayer["pan"]["x"].asDouble(), jsonLayer["pan"]["y"].asDouble());
-    geometry.SetPixelSpacing(jsonLayer["pixelSpacing"]["x"].asDouble(), jsonLayer["pixelSpacing"]["y"].asDouble());
-
-    // these fields were introduced later -> they might not exist
-    if (jsonLayer.isMember("flipVertical"))
-    {
-      geometry.SetFlipVertical(jsonLayer["flipVertical"].asBool());
-    }
-    if (jsonLayer.isMember("flipHorizontal"))
-    {
-      geometry.SetFlipHorizontal(jsonLayer["flipHorizontal"].asBool());
-    }
-
-  }
-}
--- a/Framework/Radiography/RadiographySceneReader.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyScene.h"
-#include "RadiographyAlphaLayer.h"
-#include "RadiographyDicomLayer.h"
-#include "RadiographyMaskLayer.h"
-#include "RadiographyTextLayer.h"
-#include "../Deprecated/Toolbox/OrthancApiClient.h"
-
-#include <json/value.h>
-#include <Core/Images/FontRegistry.h>
-
-namespace OrthancStone
-{
-  // a layer containing only the geometry of a DICOM layer (bit hacky !)
-  class RadiographyPlaceholderLayer : public RadiographyDicomLayer
-  {
-  public:
-    RadiographyPlaceholderLayer(const RadiographyScene& scene) :
-      RadiographyDicomLayer(scene)
-    {
-    }
-
-  };
-
-
-  // HACK: I had to introduce this builder class in order to be able to recreate a RadiographyScene
-  // from a serialized scene that is passed to web-workers.
-  // It needs some architecturing...
-  class RadiographySceneBuilder : public boost::noncopyable
-  {
-  protected:
-    RadiographyScene&                               scene_;
-    std::unique_ptr<Orthanc::ImageAccessor>           dicomImage_;
-    std::unique_ptr<Deprecated::DicomFrameConverter>  dicomFrameConverter_;
-    RadiographyPhotometricDisplayMode               preferredPhotometricDisplayMode_;
-
-  public:
-    RadiographySceneBuilder(RadiographyScene& scene) :
-      scene_(scene)
-    {
-    }
-
-    void Read(const Json::Value& input);
-    void Read(const Json::Value& input,
-              Orthanc::ImageAccessor* dicomImage, // takes ownership
-              Deprecated::DicomFrameConverter* dicomFrameConverter, // takes ownership
-              RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode
-              );
-
-    static void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input);
-    static void ReadDicomLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input);
-
-  protected:
-    virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
-
-  };
-
-
-  class RadiographySceneReader : public RadiographySceneBuilder
-  {
-    Deprecated::OrthancApiClient&             orthancApiClient_;
-
-  public:
-    RadiographySceneReader(RadiographyScene& scene, Deprecated::OrthancApiClient& orthancApiClient) :
-      RadiographySceneBuilder(scene),
-      orthancApiClient_(orthancApiClient)
-    {
-    }
-
-  protected:
-    virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
-  };
-
-  // reads the whole scene but the DICOM image such that we have the full geometry
-  class RadiographySceneGeometryReader : public RadiographySceneBuilder
-  {
-    unsigned int dicomImageWidth_;
-    unsigned int dicomImageHeight_;
-
-  public:
-    RadiographySceneGeometryReader(RadiographyScene& scene, unsigned int dicomImageWidth, unsigned int dicomImageHeight) :
-      RadiographySceneBuilder(scene),
-      dicomImageWidth_(dicomImageWidth),
-      dicomImageHeight_(dicomImageHeight)
-    {
-    }
-
-  protected:
-    virtual RadiographyDicomLayer*  LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry);
-  };
-}
--- a/Framework/Radiography/RadiographySceneWriter.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographySceneWriter.h"
-
-#include <Core/OrthancException.h>
-#include <Core/Images/PngWriter.h>
-#include <Core/Toolbox.h>
-
-namespace OrthancStone
-{
-  void RadiographySceneWriter::Write(Json::Value& output, const RadiographyScene& scene)
-  {
-    output["version"] = 1;
-    float windowCenter, windowWidth;
-    bool hasWindowing = scene.GetWindowing(windowCenter, windowWidth);
-    output["hasWindowing"] = hasWindowing;
-    if (hasWindowing)
-    {
-      output["windowCenter"] = windowCenter;
-      output["windowWidth"] = windowWidth;
-    }
-    output["layers"] = Json::arrayValue;
-
-    std::vector<size_t> layersIndexes;
-    scene.GetLayersIndexes(layersIndexes);
-
-    for (std::vector<size_t>::iterator itLayerIndex = layersIndexes.begin(); itLayerIndex < layersIndexes.end(); itLayerIndex++)
-    {
-      Json::Value layer;
-      WriteLayer(layer, scene.GetLayer(*itLayerIndex));
-      output["layers"].append(layer);
-    }
-  }
-
-  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer)
-  {
-    output["type"] = "dicom";
-    output["instanceId"] = layer.GetInstanceId();
-    output["frame"] = layer.GetFrame();
-  }
-
-  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyTextLayer& layer)
-  {
-    output["type"] = "text";
-    output["text"] = layer.GetText();
-    output["font"] = layer.GetFont();
-    output["fontSize"] = layer.GetFontSize();
-    output["foreground"] = layer.GetForegroundGreyLevel();
-  }
-
-  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer)
-  {
-    output["type"] = "mask";
-    output["instanceId"] = layer.GetInstanceId(); // the dicom layer it's being linked to
-    output["foreground"] = layer.GetForeground();
-    output["corners"] = Json::arrayValue;
-    const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners = layer.GetCorners();
-    for (size_t i = 0; i < corners.size(); i++)
-    {
-      Json::Value corner;
-      corner["x"] = corners[i].GetX();
-      corner["y"] = corners[i].GetY();
-      output["corners"].append(corner);
-    }
-  }
-
-  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer)
-  {
-    output["type"] = "alpha";
-
-    //output["bitmap"] =
-    const Orthanc::ImageAccessor& alpha = layer.GetAlpha();
-
-    Orthanc::PngWriter pngWriter;
-    std::string pngContent;
-    std::string pngContentBase64;
-    pngWriter.WriteToMemory(pngContent, alpha);
-
-    Orthanc::Toolbox::EncodeDataUriScheme(pngContentBase64, "image/png", pngContent);
-    output["content"] = pngContentBase64;
-    output["foreground"] = layer.GetForegroundValue();
-  }
-
-  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyLayer& layer)
-  {
-    const RadiographyLayer::Geometry& geometry = layer.GetGeometry();
-
-    {// crop
-      Json::Value crop;
-      if (geometry.HasCrop())
-      {
-        unsigned int x, y, width, height;
-        geometry.GetCrop(x, y, width, height);
-        crop["hasCrop"] = true;
-        crop["x"] = x;
-        crop["y"] = y;
-        crop["width"] = width;
-        crop["height"] = height;
-      }
-      else
-      {
-        crop["hasCrop"] = false;
-      }
-
-      output["crop"] = crop;
-    }
-
-    output["angle"] = geometry.GetAngle();
-    output["isResizable"] = geometry.IsResizeable();
-
-    {// pan
-      Json::Value pan;
-      pan["x"] = geometry.GetPanX();
-      pan["y"] = geometry.GetPanY();
-      output["pan"] = pan;
-    }
-
-    {// pixelSpacing
-      Json::Value pan;
-      pan["x"] = geometry.GetPixelSpacingX();
-      pan["y"] = geometry.GetPixelSpacingY();
-      output["pixelSpacing"] = pan;
-    }
-
-    output["flipVertical"] = geometry.GetFlipVertical();
-    output["flipHorizontal"] = geometry.GetFlipHorizontal();
-
-    if (dynamic_cast<const RadiographyTextLayer*>(&layer) != NULL)
-    {
-      WriteLayer(output, dynamic_cast<const RadiographyTextLayer&>(layer));
-    }
-    else if (dynamic_cast<const RadiographyDicomLayer*>(&layer) != NULL)
-    {
-      WriteLayer(output, dynamic_cast<const RadiographyDicomLayer&>(layer));
-    }
-    else if (dynamic_cast<const RadiographyAlphaLayer*>(&layer) != NULL)
-    {
-      WriteLayer(output, dynamic_cast<const RadiographyAlphaLayer&>(layer));
-    }
-    else if (dynamic_cast<const RadiographyMaskLayer*>(&layer) != NULL)
-    {
-      WriteLayer(output, dynamic_cast<const RadiographyMaskLayer&>(layer));
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
-}
--- a/Framework/Radiography/RadiographySceneWriter.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyScene.h"
-#include "RadiographyAlphaLayer.h"
-#include "RadiographyDicomLayer.h"
-#include "RadiographyTextLayer.h"
-#include "RadiographyMaskLayer.h"
-#include <json/value.h>
-
-namespace OrthancStone
-{
-  class RadiographyScene;
-
-  class RadiographySceneWriter : public boost::noncopyable
-  {
-
-  public:
-    RadiographySceneWriter()
-    {
-    }
-
-    void Write(Json::Value& output, const RadiographyScene& scene);
-
-  private:
-    void WriteLayer(Json::Value& output, const RadiographyLayer& layer);
-    void WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer);
-    void WriteLayer(Json::Value& output, const RadiographyTextLayer& layer);
-    void WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer);
-    void WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer);
-  };
-}
--- a/Framework/Radiography/RadiographyTextLayer.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyTextLayer.h"
-
-#include "Core/OrthancException.h"
-#include "RadiographyScene.h"
-#include "../Toolbox/TextRenderer.h"
-
-namespace OrthancStone
-{
-  std::map<std::string, Orthanc::EmbeddedResources::FileResourceId> RadiographyTextLayer::fonts_;
-
-  void RadiographyTextLayer::SetText(const std::string& utf8,
-                                     const std::string& font,
-                                     unsigned int fontSize,
-                                     uint8_t foregroundGreyLevel)
-  {
-    if (fonts_.find(font) == fonts_.end())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "The font has not been registered");
-    }
-
-    text_ = utf8;
-    font_ = font;
-    fontSize_ = fontSize;
-    foregroundGreyLevel_ = foregroundGreyLevel;
-
-    SetAlpha(TextRenderer::Render(fonts_[font_],
-                                  fontSize_,
-                                  text_));
-
-    SetForegroundValue(foregroundGreyLevel * 256.0f);
-  }
-
-}
--- a/Framework/Radiography/RadiographyTextLayer.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyAlphaLayer.h"
-
-namespace OrthancStone
-{
-  class RadiographyScene;
-
-  class RadiographyTextLayer : public RadiographyAlphaLayer
-  {
-  private:
-    std::string                 text_;
-    std::string                 font_;
-    unsigned int                fontSize_;
-    uint8_t                     foregroundGreyLevel_;
-
-    static std::map<std::string, Orthanc::EmbeddedResources::FileResourceId>  fonts_;
-  public:
-    RadiographyTextLayer(const RadiographyScene& scene) :
-      RadiographyAlphaLayer(scene)
-    {
-    }
-
-    void SetText(const std::string& utf8, const std::string& font, unsigned int fontSize, uint8_t foregroundGreyLevel);
-
-    const std::string& GetText() const
-    {
-      return text_;
-    }
-
-    const std::string& GetFont() const
-    {
-      return font_;
-    }
-
-    unsigned int GetFontSize() const
-    {
-      return fontSize_;
-    }
-
-    uint8_t GetForegroundGreyLevel() const
-    {
-      return foregroundGreyLevel_;
-    }
-
-    static void RegisterFont(const std::string& name, Orthanc::EmbeddedResources::FileResourceId fontResourceId)
-    {
-      fonts_[name] = fontResourceId;
-    }
-  };
-}
--- a/Framework/Radiography/RadiographyWidget.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyWidget.h"
-
-#include <Core/OrthancException.h>
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-
-#include "RadiographyMaskLayer.h"
-
-namespace OrthancStone
-{
-
-  bool RadiographyWidget::IsInvertedInternal() const
-  {
-    // MONOCHROME1 images must be inverted and the user can invert the 
-    // image, too -> XOR the two
-    return (scene_->GetPreferredPhotomotricDisplayMode() == 
-            RadiographyPhotometricDisplayMode_Monochrome1) ^ invert_; 
-  }
-
-  void RadiographyWidget::RenderBackground(
-    Orthanc::ImageAccessor& image, float minValue, float maxValue)
-  {
-    // wipe background before rendering
-    float backgroundValue = minValue;
-
-    switch (scene_->GetPreferredPhotomotricDisplayMode())
-    {
-    case RadiographyPhotometricDisplayMode_Monochrome1:
-    case RadiographyPhotometricDisplayMode_Default:
-      if (IsInvertedInternal())
-        backgroundValue = maxValue;
-      else
-        backgroundValue = minValue;
-      break;
-    case RadiographyPhotometricDisplayMode_Monochrome2:
-      if (IsInvertedInternal())
-        backgroundValue = minValue;
-      else
-        backgroundValue = maxValue;
-      break;
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    Orthanc::ImageProcessing::Set(image, static_cast<int64_t>(backgroundValue));
-  }
-
-  bool RadiographyWidget::RenderInternal(unsigned int width,
-                                         unsigned int height,
-                                         ImageInterpolation interpolation)
-  {
-    if (floatBuffer_.get() == NULL ||
-        floatBuffer_->GetWidth() != width ||
-        floatBuffer_->GetHeight() != height)
-    {
-      floatBuffer_.reset(new Orthanc::Image(
-        Orthanc::PixelFormat_Float32, width, height, false));
-
-      if (floatBuffer_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "RadiographyWidget::RenderInternal: unable to allocate float buffer");
-      }
-    }
-
-    if (cairoBuffer_.get() == NULL ||
-        cairoBuffer_->GetWidth() != width ||
-        cairoBuffer_->GetHeight() != height)
-    {
-      cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */));
-
-      if (cairoBuffer_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "RadiographyWidget::RenderInternal: unable to allocate cairo buffer");
-      }
-    }
-
-    RenderBackground(*floatBuffer_, 0.0, 65535.0);
-
-    scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation, true);
-
-    // Conversion from Float32 to BGRA32 (cairo). Very similar to
-    // GrayscaleFrameRenderer => TODO MERGE?
-    Orthanc::ImageAccessor target;
-    cairoBuffer_->GetWriteableAccessor(target);
-
-    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 x = 0; x < width; x++, p++, q += 4)
-      {
-        uint8_t v = 0;
-        if (*p >= 65535.0)
-        {
-          v = 255;
-        }
-        else if (*p <= 0.0)
-        {
-          v = 0;
-        }
-        else
-        {
-          v = static_cast<uint8_t>(*p / 256.0);
-        }
-
-        if (invert)
-        {
-          v = 255 - v;
-        }
-
-        q[0] = v;
-        q[1] = v;
-        q[2] = v;
-        q[3] = 255;
-      }
-    }
-
-    return true;
-  }
-
-
-  bool RadiographyWidget::RenderScene(CairoContext& context,
-                                      const Deprecated::ViewportGeometry& view)
-  {
-    cairo_t* cr = context.GetObject();
-
-    if (RenderInternal(context.GetWidth(), context.GetHeight(), interpolation_))
-    {
-      // https://www.cairographics.org/FAQ/#paint_from_a_surface
-      cairo_save(cr);
-      cairo_identity_matrix(cr);
-      cairo_set_source_surface(cr, cairoBuffer_->GetObject(), 0, 0);
-      cairo_paint(cr);
-      cairo_restore(cr);
-    }
-    else
-    {
-      // https://www.cairographics.org/FAQ/#clear_a_surface
-      context.SetSourceColor(0, 0, 0);
-      cairo_paint(cr);
-    }
-
-    if (hasSelection_)
-    {
-      scene_->DrawBorder(
-        context, static_cast<unsigned int>(selectedLayer_), view.GetZoom());
-    }
-
-    return true;
-  }
-
-
-  RadiographyWidget::RadiographyWidget(boost::shared_ptr<RadiographyScene> scene,
-                                       const std::string& name) :
-    WorldSceneWidget(name),
-    invert_(false),
-    interpolation_(ImageInterpolation_Nearest),
-    hasSelection_(false),
-    selectedLayer_(0)    // Dummy initialization
-  {
-    SetScene(scene);
-  }
-
-
-  void RadiographyWidget::Select(size_t layer)
-  {
-    hasSelection_ = true;
-    selectedLayer_ = layer;
-
-    NotifyContentChanged();
-    BroadcastMessage(SelectionChangedMessage(*this));
-  }
-
-  void RadiographyWidget::Unselect()
-  {
-    hasSelection_ = false;
-
-    NotifyContentChanged();
-    BroadcastMessage(SelectionChangedMessage(*this));
-  }
-
-  bool RadiographyWidget::LookupSelectedLayer(size_t& layer) const
-  {
-    if (hasSelection_)
-    {
-      layer = selectedLayer_;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  void RadiographyWidget::OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message)
-  {
-//    LOG(INFO) << "Scene geometry has changed";
-    FitContent();
-  }
-
-  
-  void RadiographyWidget::OnContentChanged(const RadiographyScene::ContentChangedMessage& message)
-  {
-//    LOG(INFO) << "Scene content has changed";
-    NotifyContentChanged();
-  }
-
-  void RadiographyWidget::OnLayerRemoved(const RadiographyScene::LayerRemovedMessage& message)
-  {
-    size_t removedLayerIndex = message.GetLayerIndex();
-    if (hasSelection_ && selectedLayer_ == removedLayerIndex)
-    {
-      Unselect();
-    }
-    NotifyContentChanged();
-  }
-  
-  void RadiographyWidget::SetInvert(bool invert)
-  {
-    if (invert_ != invert)
-    {
-      invert_ = invert;
-      NotifyContentChanged();
-    }
-  }
-
-  
-    void RadiographyWidget::SwitchInvert()
-  {
-    invert_ = !invert_;
-    NotifyContentChanged();
-  }
-
-
-  void RadiographyWidget::SetInterpolation(ImageInterpolation interpolation)
-  {
-    if (interpolation_ != interpolation)
-    {
-      interpolation_ = interpolation;
-      NotifyContentChanged();
-    }
-  }
-
-  void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene)
-  {
-    scene_ = scene;
-
-    Register<RadiographyScene::GeometryChangedMessage>(*scene_, &RadiographyWidget::OnGeometryChanged);
-    Register<RadiographyScene::ContentChangedMessage>(*scene_, &RadiographyWidget::OnContentChanged);
-    Register<RadiographyScene::LayerRemovedMessage>(*scene_, &RadiographyWidget::OnLayerRemoved);
-
-    Unselect();
-
-    NotifyContentChanged();
-
-    // force redraw
-    FitContent();
-  }
-}
--- a/Framework/Radiography/RadiographyWidget.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Deprecated/Widgets/WorldSceneWidget.h"
-#include "../Messages/ObserverBase.h"
-#include "RadiographyScene.h"
-
-
-namespace OrthancStone
-{
-  class RadiographyMaskLayer;
-
-  class RadiographyWidget :
-    public Deprecated::WorldSceneWidget,
-    public ObserverBase<RadiographyWidget>,
-    public IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SelectionChangedMessage, RadiographyWidget);
-
-  private:
-    boost::shared_ptr<RadiographyScene>    scene_;
-    std::unique_ptr<Orthanc::ImageAccessor>  floatBuffer_;
-    std::unique_ptr<CairoSurface>            cairoBuffer_;
-    bool                                   invert_;
-    ImageInterpolation                     interpolation_;
-    bool                                   hasSelection_;
-    size_t                                 selectedLayer_;
-
-    bool RenderInternal(unsigned int width,
-                        unsigned int height,
-                        ImageInterpolation interpolation);
-
-  protected:
-    virtual Extent2D GetSceneExtent()
-    {
-      return scene_->GetSceneExtent(false);
-    }
-
-    virtual bool RenderScene(CairoContext& context,
-                             const Deprecated::ViewportGeometry& view);
-
-    virtual void RenderBackground(Orthanc::ImageAccessor& image, float minValue, float maxValue);
-
-    bool IsInvertedInternal() const;
-
-  public:
-    RadiographyWidget(boost::shared_ptr<RadiographyScene> scene,  // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now)
-                      const std::string& name);
-
-    RadiographyScene& GetScene() const
-    {
-      return *scene_;
-    }
-
-    void SetScene(boost::shared_ptr<RadiographyScene> scene);
-
-    void Select(size_t layer);
-
-    void Unselect();
-
-    template<typename LayerType> bool SelectLayerByType(size_t index = 0);
-
-    bool LookupSelectedLayer(size_t& layer) const;
-
-    void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message);
-
-    void OnContentChanged(const RadiographyScene::ContentChangedMessage& message);
-
-    void OnLayerRemoved(const RadiographyScene::LayerRemovedMessage& message);
-
-    void SetInvert(bool invert);
-
-    void SwitchInvert();
-
-    bool IsInverted() const
-    {
-      return invert_;
-    }
-
-    void SetInterpolation(ImageInterpolation interpolation);
-
-    ImageInterpolation GetInterpolation() const
-    {
-      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/Radiography/RadiographyWindowingTracker.cpp	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "RadiographyWindowingTracker.h"
-#include "RadiographyWidget.h"
-
-#include <Core/OrthancException.h>
-
-
-namespace OrthancStone
-{
-  class RadiographyWindowingTracker::UndoRedoCommand : public UndoRedoStack::ICommand
-  {
-  private:
-    RadiographyScene&  scene_;
-    float              sourceCenter_;
-    float              sourceWidth_;
-    float              targetCenter_;
-    float              targetWidth_;
-
-  public:
-    UndoRedoCommand(const RadiographyWindowingTracker& tracker) :
-      scene_(tracker.scene_),
-      sourceCenter_(tracker.sourceCenter_),
-      sourceWidth_(tracker.sourceWidth_)
-    {
-      scene_.GetWindowingWithDefault(targetCenter_, targetWidth_);
-    }
-
-    virtual void Undo() const
-    {
-      scene_.SetWindowing(sourceCenter_, sourceWidth_);
-    }
-      
-    virtual void Redo() const
-    {
-      scene_.SetWindowing(targetCenter_, targetWidth_);
-    }
-  };
-
-
-  void RadiographyWindowingTracker::ComputeAxisEffect(int& deltaCenter,
-                                                      int& deltaWidth,
-                                                      int delta,
-                                                      Action actionNegative,
-                                                      Action actionPositive)
-  {
-    if (delta < 0)
-    {
-      switch (actionNegative)
-      {
-        case Action_IncreaseWidth:
-          deltaWidth = -delta;
-          break;
-
-        case Action_DecreaseWidth:
-          deltaWidth = delta;
-          break;
-
-        case Action_IncreaseCenter:
-          deltaCenter = -delta;
-          break;
-
-        case Action_DecreaseCenter:
-          deltaCenter = delta;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-    else if (delta > 0)
-    {
-      switch (actionPositive)
-      {
-        case Action_IncreaseWidth:
-          deltaWidth = delta;
-          break;
-
-        case Action_DecreaseWidth:
-          deltaWidth = -delta;
-          break;
-
-        case Action_IncreaseCenter:
-          deltaCenter = delta;
-          break;
-
-        case Action_DecreaseCenter:
-          deltaCenter = -delta;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-  }
-    
-    
-  RadiographyWindowingTracker::RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
-                                                           RadiographyScene& scene,
-                                                           RadiographyWidget& widget,
-                                                           ImageInterpolation interpolationDuringTracking,
-                                                           int x,
-                                                           int y,
-                                                           Action leftAction,
-                                                           Action rightAction,
-                                                           Action upAction,
-                                                           Action downAction) :
-    undoRedoStack_(undoRedoStack),
-    scene_(scene),
-    widget_(widget),
-    initialWidgetInterpolation_(widget.GetInterpolation()),
-    clickX_(x),
-    clickY_(y),
-    leftAction_(leftAction),
-    rightAction_(rightAction),
-    upAction_(upAction),
-    downAction_(downAction)
-  {
-    scene_.GetWindowingWithDefault(sourceCenter_, sourceWidth_);
-    widget_.SetInterpolation(interpolationDuringTracking);
-
-    float minValue, maxValue;
-    scene.GetRange(minValue, maxValue);
-
-    assert(minValue <= maxValue);
-
-    float delta = (maxValue - minValue);
-    strength_ = delta / 1000.0f; // 1px move will change the ww/wc by 0.1%
-
-    if (strength_ < 1)
-    {
-      strength_ = 1;
-    }
-  }
-
-
-  void RadiographyWindowingTracker::Render(CairoContext& context,
-                                           double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-  
-
-  void RadiographyWindowingTracker::MouseUp()
-  {
-    widget_.SetInterpolation(initialWidgetInterpolation_);
-    undoRedoStack_.Add(new UndoRedoCommand(*this));
-  }
-
-
-  void RadiographyWindowingTracker::MouseMove(int displayX,
-                                              int displayY,
-                                              double sceneX,
-                                              double sceneY,
-                                              const std::vector<Deprecated::Touch>& displayTouches,
-                                              const std::vector<Deprecated::Touch>& sceneTouches)
-  {
-    // This follows the behavior of the Osimis Web viewer:
-    // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
-
-    static const float SCALE = 1.0;
-      
-    int deltaCenter = 0;
-    int deltaWidth = 0;
-
-    ComputeAxisEffect(deltaCenter, deltaWidth, displayX - clickX_, leftAction_, rightAction_);
-    ComputeAxisEffect(deltaCenter, deltaWidth, displayY - clickY_, upAction_, downAction_);
-
-    float newCenter = sourceCenter_ + (deltaCenter / SCALE * strength_);
-    float newWidth = sourceWidth_ + (deltaWidth / SCALE * strength_);
-    scene_.SetWindowing(newCenter, newWidth);
-  }
-}
--- a/Framework/Radiography/RadiographyWindowingTracker.h	Wed Apr 29 20:41:36 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 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 "../Toolbox/UndoRedoStack.h"
-#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
-#include "RadiographyScene.h"
-
-namespace OrthancStone
-{
-
-  class RadiographyWidget;
-
-  class RadiographyWindowingTracker : public Deprecated::IWorldSceneMouseTracker
-  {   
-  public:
-    enum Action
-    {
-      Action_IncreaseWidth,
-      Action_DecreaseWidth,
-      Action_IncreaseCenter,
-      Action_DecreaseCenter
-    };
-    
-  private:
-    class UndoRedoCommand;
-
-    UndoRedoStack&      undoRedoStack_;
-    RadiographyScene&   scene_;
-    RadiographyWidget&  widget_;
-    ImageInterpolation  initialWidgetInterpolation_;
-    int                 clickX_;
-    int                 clickY_;
-    Action              leftAction_;
-    Action              rightAction_;
-    Action              upAction_;
-    Action              downAction_;
-    float               strength_;
-    float               sourceCenter_;
-    float               sourceWidth_;
-
-    static void ComputeAxisEffect(int& deltaCenter,
-                                  int& deltaWidth,
-                                  int delta,
-                                  Action actionNegative,
-                                  Action actionPositive);    
-    
-  public:
-    RadiographyWindowingTracker(UndoRedoStack& undoRedoStack,
-                                RadiographyScene& scene,
-                                RadiographyWidget& widget,
-                                ImageInterpolation interpolationDuringTracking,
-                                int x,
-                                int y,
-                                Action leftAction,
-                                Action rightAction,
-                                Action upAction,
-                                Action downAction);
-    
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void Render(CairoContext& context,
-                        double zoom);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Deprecated::Touch>& displayTouches,
-                           const std::vector<Deprecated::Touch>& sceneTouches);
-  };
-}