changeset 410:6decc0ba9da5

rename RadiographyScene::Layer as RadiographyLayer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 12 Nov 2018 15:52:03 +0100
parents 99c9b3238008
children 3aa058dcd5fb
files Applications/Samples/SingleFrameEditorApplication.h Framework/Radiography/RadiographyLayer.cpp Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/StoneEnumerations.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 7 files changed, 661 insertions(+), 611 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SingleFrameEditorApplication.h	Mon Nov 12 15:38:11 2018 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Mon Nov 12 15:52:03 2018 +0100
@@ -58,9 +58,9 @@
     size_t             layer_;
 
   protected:
-    virtual void UndoInternal(RadiographyScene::Layer& layer) const = 0;
+    virtual void UndoInternal(RadiographyLayer& layer) const = 0;
 
-    virtual void RedoInternal(RadiographyScene::Layer& layer) const = 0;
+    virtual void RedoInternal(RadiographyLayer& layer) const = 0;
 
   public:
     RadiographyLayerCommand(RadiographyScene& scene,
@@ -143,13 +143,13 @@
       }
       
     protected:
-      virtual void UndoInternal(RadiographyScene::Layer& layer) const
+      virtual void UndoInternal(RadiographyLayer& layer) const
       {
         LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
         layer.SetAngle(sourceAngle_);
       }
 
-      virtual void RedoInternal(RadiographyScene::Layer& layer) const
+      virtual void RedoInternal(RadiographyLayer& layer) const
       {
         LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
         layer.SetAngle(targetAngle_);
@@ -256,12 +256,12 @@
       double  targetY_;
 
     protected:
-      virtual void UndoInternal(RadiographyScene::Layer& layer) const
+      virtual void UndoInternal(RadiographyLayer& layer) const
       {
         layer.SetPan(sourceX_, sourceY_);
       }
 
-      virtual void RedoInternal(RadiographyScene::Layer& layer) const
+      virtual void RedoInternal(RadiographyLayer& layer) const
       {
         layer.SetPan(targetX_, targetY_);
       }
@@ -352,7 +352,7 @@
   private:
     UndoRedoStack&                   undoRedoStack_;
     RadiographyScene::LayerAccessor  accessor_;
-    RadiographyScene::Corner         corner_;
+    Corner                           corner_;
     unsigned int                     cropX_;
     unsigned int                     cropY_;
     unsigned int                     cropWidth_;
@@ -371,12 +371,12 @@
       unsigned int  targetCropHeight_;
 
     protected:
-      virtual void UndoInternal(RadiographyScene::Layer& layer) const
+      virtual void UndoInternal(RadiographyLayer& layer) const
       {
         layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
       }
 
-      virtual void RedoInternal(RadiographyScene::Layer& layer) const
+      virtual void RedoInternal(RadiographyLayer& layer) const
       {
         layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
       }
@@ -402,7 +402,7 @@
                                 size_t layer,
                                 double x,
                                 double y,
-                                RadiographyScene::Corner corner) :
+                                Corner corner) :
       undoRedoStack_(undoRedoStack),
       accessor_(scene, layer),
       corner_(corner)
@@ -441,13 +441,13 @@
       {
         unsigned int x, y;
         
-        RadiographyScene::Layer& layer = accessor_.GetLayer();
+        RadiographyLayer& layer = accessor_.GetLayer();
         if (layer.GetPixel(x, y, sceneX, sceneY))
         {
           unsigned int targetX, targetWidth;
 
-          if (corner_ == RadiographyScene::Corner_TopLeft ||
-              corner_ == RadiographyScene::Corner_BottomLeft)
+          if (corner_ == Corner_TopLeft ||
+              corner_ == Corner_BottomLeft)
           {
             targetX = std::min(x, cropX_ + cropWidth_);
             targetWidth = cropX_ + cropWidth_ - targetX;
@@ -460,8 +460,8 @@
 
           unsigned int targetY, targetHeight;
 
-          if (corner_ == RadiographyScene::Corner_TopLeft ||
-              corner_ == RadiographyScene::Corner_TopRight)
+          if (corner_ == Corner_TopLeft ||
+              corner_ == Corner_TopRight)
           {
             targetY = std::min(y, cropY_ + cropHeight_);
             targetHeight = cropY_ + cropHeight_ - targetY;
@@ -489,7 +489,7 @@
     double                           originalSpacingY_;
     double                           originalPanX_;
     double                           originalPanY_;
-    RadiographyScene::Corner         oppositeCorner_;
+    Corner                           oppositeCorner_;
     double                           oppositeX_;
     double                           oppositeY_;
     double                           baseScaling_;
@@ -517,13 +517,13 @@
       double   targetPanY_;
 
     protected:
-      virtual void UndoInternal(RadiographyScene::Layer& layer) const
+      virtual void UndoInternal(RadiographyLayer& layer) const
       {
         layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
         layer.SetPan(sourcePanX_, sourcePanY_);
       }
 
-      virtual void RedoInternal(RadiographyScene::Layer& layer) const
+      virtual void RedoInternal(RadiographyLayer& layer) const
       {
         layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
         layer.SetPan(targetPanX_, targetPanY_);
@@ -551,7 +551,7 @@
                                   size_t layer,
                                   double x,
                                   double y,
-                                  RadiographyScene::Corner corner,
+                                  Corner corner,
                                   bool roundScaling) :
       undoRedoStack_(undoRedoStack),
       accessor_(scene, layer),
@@ -567,20 +567,20 @@
 
         switch (corner)
         {
-          case RadiographyScene::Corner_TopLeft:
-            oppositeCorner_ = RadiographyScene::Corner_BottomRight;
+          case Corner_TopLeft:
+            oppositeCorner_ = Corner_BottomRight;
             break;
 
-          case RadiographyScene::Corner_TopRight:
-            oppositeCorner_ = RadiographyScene::Corner_BottomLeft;
+          case Corner_TopRight:
+            oppositeCorner_ = Corner_BottomLeft;
             break;
 
-          case RadiographyScene::Corner_BottomLeft:
-            oppositeCorner_ = RadiographyScene::Corner_TopRight;
+          case Corner_BottomLeft:
+            oppositeCorner_ = Corner_TopRight;
             break;
 
-          case RadiographyScene::Corner_BottomRight:
-            oppositeCorner_ = RadiographyScene::Corner_TopLeft;
+          case Corner_BottomRight:
+            oppositeCorner_ = Corner_TopLeft;
             break;
 
           default:
@@ -639,7 +639,7 @@
           scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING);
         }
           
-        RadiographyScene::Layer& layer = accessor_.GetLayer();
+        RadiographyLayer& layer = accessor_.GetLayer();
         layer.SetPixelSpacing(scaling * originalSpacingX_,
                               scaling * originalSpacingY_);
 
@@ -1148,7 +1148,8 @@
                    tool_ == Tool_Resize)
           {
             RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
-            RadiographyScene::Corner corner;
+            
+            Corner corner;
             if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
             {
               switch (tool_)
@@ -1253,7 +1254,7 @@
         {
           RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
         
-          RadiographyScene::Corner corner;
+          Corner corner;
           if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
           {
             accessor.GetLayer().GetCorner(x, y, corner);
@@ -1465,16 +1466,14 @@
         //scene_->LoadDicomFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false);
 
         {
-          RadiographyScene::Layer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld");
-          //dynamic_cast<RadiographyScene::Layer&>(layer).SetForegroundValue(256);
-          dynamic_cast<RadiographyScene::Layer&>(layer).SetResizeable(true);
+          RadiographyLayer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld");
+          layer.SetResizeable(true);
         }
         
         {
-          RadiographyScene::Layer& layer = scene_->LoadTestBlock(100, 50);
-          //dynamic_cast<RadiographyScene::Layer&>(layer).SetForegroundValue(256);
-          dynamic_cast<RadiographyScene::Layer&>(layer).SetResizeable(true);
-          dynamic_cast<RadiographyScene::Layer&>(layer).SetPan(0, 200);
+          RadiographyLayer& layer = scene_->LoadTestBlock(100, 50);
+          layer.SetResizeable(true);
+          layer.SetPan(0, 200);
         }
         
         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayer.cpp	Mon Nov 12 15:52:03 2018 +0100
@@ -0,0 +1,391 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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;
+  }
+
+
+  void RadiographyLayer::UpdateTransform()
+  {
+    transform_ = AffineTransform2D::CreateScaling(pixelSpacingX_, pixelSpacingY_);
+
+    double centerX, centerY;
+    GetCenter(centerX, centerY);
+
+    transform_ = AffineTransform2D::Combine(
+      AffineTransform2D::CreateOffset(panX_ + centerX, panY_ + centerY),
+      AffineTransform2D::CreateRotation(angle_),
+      AffineTransform2D::CreateOffset(-centerX, -centerY),
+      transform_);
+
+    transformInverse_ = AffineTransform2D::Invert(transform_);
+  }
+
+
+  void RadiographyLayer::AddToExtent(Extent2D& extent,
+                                     double x,
+                                     double y) const
+  {
+    transform_.Apply(x, y);
+    extent.AddPoint(x, y);
+  }
+
+
+  void RadiographyLayer::GetCornerInternal(double& x,
+                                           double& y,
+                                           Corner corner,
+                                           unsigned int cropX,
+                                           unsigned int cropY,
+                                           unsigned int cropWidth,
+                                           unsigned int cropHeight) const
+  {
+    double dx = static_cast<double>(cropX);
+    double dy = static_cast<double>(cropY);
+    double dwidth = static_cast<double>(cropWidth);
+    double dheight = static_cast<double>(cropHeight);
+
+    switch (corner)
+    {
+      case Corner_TopLeft:
+        x = dx;
+        y = dy;
+        break;
+
+      case Corner_TopRight:
+        x = dx + dwidth;
+        y = dy;
+        break;
+
+      case Corner_BottomLeft:
+        x = dx;
+        y = dy + dheight;
+        break;
+
+      case Corner_BottomRight:
+        x = dx + dwidth;
+        y = dy + dheight;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    transform_.Apply(x, y);
+  }
+
+
+  bool RadiographyLayer::Contains(double x,
+                                  double y) const
+  {
+    transformInverse_.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)
+  {
+    unsigned int cx, cy, width, height;
+    GetCrop(cx, cy, width, height);
+
+    double dx = static_cast<double>(cx);
+    double dy = static_cast<double>(cy);
+    double dwidth = static_cast<double>(width);
+    double dheight = static_cast<double>(height);
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_line_width(cr, 2.0 / zoom);
+        
+    double x, y;
+    x = dx;
+    y = dy;
+    transform_.Apply(x, y);
+    cairo_move_to(cr, x, y);
+
+    x = dx + dwidth;
+    y = dy;
+    transform_.Apply(x, y);
+    cairo_line_to(cr, x, y);
+
+    x = dx + dwidth;
+    y = dy + dheight;
+    transform_.Apply(x, y);
+    cairo_line_to(cr, x, y);
+
+    x = dx;
+    y = dy + dheight;
+    transform_.Apply(x, y);
+    cairo_line_to(cr, x, y);
+
+    x = dx;
+    y = dy;
+    transform_.Apply(x, y);
+    cairo_line_to(cr, x, y);
+
+    cairo_stroke(cr);
+  }
+
+
+  RadiographyLayer::RadiographyLayer() :
+    index_(0),
+    hasSize_(false),
+    width_(0),
+    height_(0),
+    hasCrop_(false),
+    pixelSpacingX_(1),
+    pixelSpacingY_(1),
+    panX_(0),
+    panY_(0),
+    angle_(0),
+    resizeable_(false)
+  {
+    UpdateTransform();
+  }
+
+
+  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);
+    }
+        
+    hasCrop_ = true;
+    cropX_ = x;
+    cropY_ = y;
+    cropWidth_ = width;
+    cropHeight_ = height;
+
+    UpdateTransform();
+  }
+
+      
+  void RadiographyLayer::GetCrop(unsigned int& x,
+                                 unsigned int& y,
+                                 unsigned int& width,
+                                 unsigned int& height) const
+  {
+    if (hasCrop_)
+    {
+      x = cropX_;
+      y = cropY_;
+      width = cropWidth_;
+      height = cropHeight_;
+    }
+    else 
+    {
+      x = 0;
+      y = 0;
+      width = width_;
+      height = height_;
+    }
+  }
+
+      
+  void RadiographyLayer::SetAngle(double angle)
+  {
+    angle_ = angle;
+    UpdateTransform();
+  }
+
+
+  void RadiographyLayer::SetSize(unsigned int width,
+                                 unsigned int height)
+  {
+    if (hasSize_ &&
+        (width != width_ ||
+         height != height_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+        
+    hasSize_ = true;
+    width_ = width;
+    height_ = height;
+
+    UpdateTransform();
+  }
+
+
+  Extent2D RadiographyLayer::GetExtent() 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(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
+    {
+      transformInverse_.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)
+  {
+    panX_ = x;
+    panY_ = y;
+    UpdateTransform();
+  }
+
+
+  void RadiographyLayer::SetPixelSpacing(double x,
+                                         double y)
+  {
+    pixelSpacingX_ = x;
+    pixelSpacingY_ = y;
+    UpdateTransform();
+  }
+
+
+  void RadiographyLayer::GetCenter(double& centerX,
+                                   double& centerY) const
+  {
+    centerX = static_cast<double>(width_) / 2.0;
+    centerY = static_cast<double>(height_) / 2.0;
+    transform_.Apply(centerX, centerY);
+  }
+
+
+  void RadiographyLayer::GetCorner(double& x /* out */,
+                                   double& y /* out */,
+                                   Corner corner) const
+  {
+    unsigned int cropX, cropY, cropWidth, cropHeight;
+    GetCrop(cropX, cropY, cropWidth, cropHeight);
+    GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight);
+  }
+      
+      
+  bool RadiographyLayer::LookupCorner(Corner& corner /* out */,
+                                      double x,
+                                      double y,
+                                      double zoom,
+                                      double viewportDistance) const
+  {
+    static const Corner CORNERS[] = {
+      Corner_TopLeft,
+      Corner_TopRight,
+      Corner_BottomLeft,
+      Corner_BottomRight
+    };
+        
+    unsigned int cropX, cropY, cropWidth, cropHeight;
+    GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+    double threshold = Square(viewportDistance / zoom);
+        
+    for (size_t i = 0; i < 4; i++)
+    {
+      double cx, cy;
+      GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight);
+
+      double d = Square(cx - x) + Square(cy - y);
+        
+      if (d <= threshold)
+      {
+        corner = CORNERS[i];
+        return true;
+      }
+    }
+        
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyLayer.h	Mon Nov 12 15:52:03 2018 +0100
@@ -0,0 +1,198 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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/AffineTransform2D.h"
+#include "../Toolbox/Extent2D.h"
+#include "../Viewport/CairoContext.h"
+
+namespace OrthancStone
+{
+  class RadiographyLayer : public boost::noncopyable
+  {
+    friend class RadiographyScene;
+      
+  private:
+    size_t             index_;
+    bool               hasSize_;
+    unsigned int       width_;
+    unsigned int       height_;
+    bool               hasCrop_;
+    unsigned int       cropX_;
+    unsigned int       cropY_;
+    unsigned int       cropWidth_;
+    unsigned int       cropHeight_;
+    AffineTransform2D  transform_;
+    AffineTransform2D  transformInverse_;
+    double             pixelSpacingX_;
+    double             pixelSpacingY_;
+    double             panX_;
+    double             panY_;
+    double             angle_;
+    bool               resizeable_;
+
+  protected:
+    const AffineTransform2D& GetTransform() const
+    {
+      return transform_;
+    }
+
+  private:
+    void UpdateTransform();
+      
+    void AddToExtent(Extent2D& extent,
+                     double x,
+                     double y) const;
+
+    void GetCornerInternal(double& x,
+                           double& y,
+                           Corner corner,
+                           unsigned int cropX,
+                           unsigned int cropY,
+                           unsigned int cropWidth,
+                           unsigned int cropHeight) const;
+
+    void SetIndex(size_t index)
+    {
+      index_ = index;
+    }
+      
+    bool Contains(double x,
+                  double y) const;
+      
+    void DrawBorders(CairoContext& context,
+                     double zoom);
+
+  public:
+    RadiographyLayer();
+
+    virtual ~RadiographyLayer()
+    {
+    }
+
+    size_t GetIndex() const
+    {
+      return index_;
+    }
+
+    void ResetCrop()
+    {
+      hasCrop_ = false;
+    }
+
+    void SetCrop(unsigned int x,
+                 unsigned int y,
+                 unsigned int width,
+                 unsigned int height);
+
+    void GetCrop(unsigned int& x,
+                 unsigned int& y,
+                 unsigned int& width,
+                 unsigned int& height) const;
+
+    void SetAngle(double angle);
+
+    double GetAngle() const
+    {
+      return angle_;
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }        
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }       
+
+    Extent2D GetExtent() const;
+
+    bool GetPixel(unsigned int& imageX,
+                  unsigned int& imageY,
+                  double sceneX,
+                  double sceneY) const;
+
+    void SetPan(double x,
+                double y);
+
+    void SetPixelSpacing(double x,
+                         double y);
+
+    double GetPixelSpacingX() const
+    {
+      return pixelSpacingX_;
+    }   
+
+    double GetPixelSpacingY() const
+    {
+      return pixelSpacingY_;
+    }   
+
+    double GetPanX() const
+    {
+      return panX_;
+    }
+
+    double GetPanY() const
+    {
+      return panY_;
+    }
+
+    void GetCenter(double& centerX,
+                   double& centerY) const;
+
+    void GetCorner(double& x /* out */,
+                   double& y /* out */,
+                   Corner corner) const;
+      
+    bool LookupCorner(Corner& corner /* out */,
+                      double x,
+                      double y,
+                      double zoom,
+                      double viewportDistance) const;
+
+    bool IsResizeable() const
+    {
+      return resizeable_;
+    }
+
+    void SetResizeable(bool resizeable)
+    {
+      resizeable_ = resizeable;
+    }
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const = 0;
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation) const = 0;
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const = 0;
+  }; 
+}
--- a/Framework/Radiography/RadiographyScene.cpp	Mon Nov 12 15:38:11 2018 +0100
+++ b/Framework/Radiography/RadiographyScene.cpp	Mon Nov 12 15:52:03 2018 +0100
@@ -21,7 +21,6 @@
 
 #include "RadiographyScene.h"
 
-#include "../Toolbox/ImageGeometry.h"
 #include "../Toolbox/DicomFrameConverter.h"
 
 #include <Core/Images/Image.h>
@@ -37,371 +36,6 @@
 
 namespace OrthancStone
 {
-  static double Square(double x)
-  {
-    return x * x;
-  }
-
-
-  void RadiographyScene::Layer::UpdateTransform()
-  {
-    transform_ = AffineTransform2D::CreateScaling(pixelSpacingX_, pixelSpacingY_);
-
-    double centerX, centerY;
-    GetCenter(centerX, centerY);
-
-    transform_ = AffineTransform2D::Combine(
-      AffineTransform2D::CreateOffset(panX_ + centerX, panY_ + centerY),
-      AffineTransform2D::CreateRotation(angle_),
-      AffineTransform2D::CreateOffset(-centerX, -centerY),
-      transform_);
-
-    transformInverse_ = AffineTransform2D::Invert(transform_);
-  }
-
-
-  void RadiographyScene::Layer::AddToExtent(Extent2D& extent,
-                                            double x,
-                                            double y) const
-  {
-    transform_.Apply(x, y);
-    extent.AddPoint(x, y);
-  }
-
-
-  void RadiographyScene::Layer::GetCornerInternal(double& x,
-                                                  double& y,
-                                                  Corner corner,
-                                                  unsigned int cropX,
-                                                  unsigned int cropY,
-                                                  unsigned int cropWidth,
-                                                  unsigned int cropHeight) const
-  {
-    double dx = static_cast<double>(cropX);
-    double dy = static_cast<double>(cropY);
-    double dwidth = static_cast<double>(cropWidth);
-    double dheight = static_cast<double>(cropHeight);
-
-    switch (corner)
-    {
-      case Corner_TopLeft:
-        x = dx;
-        y = dy;
-        break;
-
-      case Corner_TopRight:
-        x = dx + dwidth;
-        y = dy;
-        break;
-
-      case Corner_BottomLeft:
-        x = dx;
-        y = dy + dheight;
-        break;
-
-      case Corner_BottomRight:
-        x = dx + dwidth;
-        y = dy + dheight;
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    transform_.Apply(x, y);
-  }
-
-
-  bool RadiographyScene::Layer::Contains(double x,
-                                         double y) const
-  {
-    transformInverse_.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 RadiographyScene::Layer::DrawBorders(CairoContext& context,
-                                            double zoom)
-  {
-    unsigned int cx, cy, width, height;
-    GetCrop(cx, cy, width, height);
-
-    double dx = static_cast<double>(cx);
-    double dy = static_cast<double>(cy);
-    double dwidth = static_cast<double>(width);
-    double dheight = static_cast<double>(height);
-
-    cairo_t* cr = context.GetObject();
-    cairo_set_line_width(cr, 2.0 / zoom);
-        
-    double x, y;
-    x = dx;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_move_to(cr, x, y);
-
-    x = dx + dwidth;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
-    x = dx + dwidth;
-    y = dy + dheight;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
-    x = dx;
-    y = dy + dheight;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
-    x = dx;
-    y = dy;
-    transform_.Apply(x, y);
-    cairo_line_to(cr, x, y);
-
-    cairo_stroke(cr);
-  }
-
-
-  RadiographyScene::Layer::Layer() :
-    index_(0),
-    hasSize_(false),
-    width_(0),
-    height_(0),
-    hasCrop_(false),
-    pixelSpacingX_(1),
-    pixelSpacingY_(1),
-    panX_(0),
-    panY_(0),
-    angle_(0),
-    resizeable_(false)
-  {
-    UpdateTransform();
-  }
-
-
-  void RadiographyScene::Layer::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);
-    }
-        
-    hasCrop_ = true;
-    cropX_ = x;
-    cropY_ = y;
-    cropWidth_ = width;
-    cropHeight_ = height;
-
-    UpdateTransform();
-  }
-
-      
-  void RadiographyScene::Layer::GetCrop(unsigned int& x,
-                                        unsigned int& y,
-                                        unsigned int& width,
-                                        unsigned int& height) const
-  {
-    if (hasCrop_)
-    {
-      x = cropX_;
-      y = cropY_;
-      width = cropWidth_;
-      height = cropHeight_;
-    }
-    else 
-    {
-      x = 0;
-      y = 0;
-      width = width_;
-      height = height_;
-    }
-  }
-
-      
-  void RadiographyScene::Layer::SetAngle(double angle)
-  {
-    angle_ = angle;
-    UpdateTransform();
-  }
-
-
-  void RadiographyScene::Layer::SetSize(unsigned int width,
-                                        unsigned int height)
-  {
-    if (hasSize_ &&
-        (width != width_ ||
-         height != height_))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
-    }
-        
-    hasSize_ = true;
-    width_ = width;
-    height_ = height;
-
-    UpdateTransform();
-  }
-
-
-  Extent2D RadiographyScene::Layer::GetExtent() 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(extent, dx, dy);
-    AddToExtent(extent, dx + dwidth, dy);
-    AddToExtent(extent, dx, dy + dheight);
-    AddToExtent(extent, dx + dwidth, dy + dheight);
-        
-    return extent;
-  }
-
-
-  bool RadiographyScene::Layer::GetPixel(unsigned int& imageX,
-                                         unsigned int& imageY,
-                                         double sceneX,
-                                         double sceneY) const
-  {
-    if (width_ == 0 ||
-        height_ == 0)
-    {
-      return false;
-    }
-    else
-    {
-      transformInverse_.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 RadiographyScene::Layer::SetPan(double x,
-                                       double y)
-  {
-    panX_ = x;
-    panY_ = y;
-    UpdateTransform();
-  }
-
-
-  void RadiographyScene::Layer::SetPixelSpacing(double x,
-                                                double y)
-  {
-    pixelSpacingX_ = x;
-    pixelSpacingY_ = y;
-    UpdateTransform();
-  }
-
-
-  void RadiographyScene::Layer::GetCenter(double& centerX,
-                                          double& centerY) const
-  {
-    centerX = static_cast<double>(width_) / 2.0;
-    centerY = static_cast<double>(height_) / 2.0;
-    transform_.Apply(centerX, centerY);
-  }
-
-
-  void RadiographyScene::Layer::GetCorner(double& x /* out */,
-                                          double& y /* out */,
-                                          Corner corner) const
-  {
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-    GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight);
-  }
-      
-      
-  bool RadiographyScene::Layer::LookupCorner(Corner& corner /* out */,
-                                             double x,
-                                             double y,
-                                             double zoom,
-                                             double viewportDistance) const
-  {
-    static const Corner CORNERS[] = {
-      Corner_TopLeft,
-      Corner_TopRight,
-      Corner_BottomLeft,
-      Corner_BottomRight
-    };
-        
-    unsigned int cropX, cropY, cropWidth, cropHeight;
-    GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-    double threshold = Square(viewportDistance / zoom);
-        
-    for (size_t i = 0; i < 4; i++)
-    {
-      double cx, cy;
-      GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight);
-
-      double d = Square(cx - x) + Square(cy - y);
-        
-      if (d <= threshold)
-      {
-        corner = CORNERS[i];
-        return true;
-      }
-    }
-        
-    return false;
-  }
-
-      
-
   RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene,
                                                  size_t index) :
     scene_(scene),
@@ -473,7 +107,7 @@
   }
 
   
-  RadiographyScene::Layer& RadiographyScene::LayerAccessor::GetLayer() const
+  RadiographyLayer& RadiographyScene::LayerAccessor::GetLayer() const
   {
     if (IsValid())
     {
@@ -487,7 +121,7 @@
 
 
 
-  class RadiographyScene::AlphaLayer : public Layer
+  class RadiographyScene::AlphaLayer : public RadiographyLayer
   {
   private:
     const RadiographyScene&                scene_;
@@ -631,7 +265,7 @@
     
     
 
-  class RadiographyScene::DicomLayer : public Layer
+  class RadiographyScene::DicomLayer : public RadiographyLayer
   {
   private:
     std::auto_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
@@ -767,14 +401,14 @@
   };
 
 
-  RadiographyScene::Layer& RadiographyScene::RegisterLayer(RadiographyScene::Layer* layer)
+  RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer)
   {
     if (layer == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
 
-    std::auto_ptr<Layer> raii(layer);
+    std::auto_ptr<RadiographyLayer> raii(layer);
       
     size_t index = countLayers_++;
     raii->SetIndex(index);
@@ -846,8 +480,8 @@
   }
 
 
-  RadiographyScene::Layer& RadiographyScene::LoadText(const Orthanc::Font& font,
-                                                      const std::string& utf8)
+  RadiographyLayer& RadiographyScene::LoadText(const Orthanc::Font& font,
+                                               const std::string& utf8)
   {
     std::auto_ptr<AlphaLayer>  alpha(new AlphaLayer(*this));
     alpha->LoadText(font, utf8);
@@ -856,8 +490,8 @@
   }
 
     
-  RadiographyScene::Layer& RadiographyScene::LoadTestBlock(unsigned int width,
-                                                           unsigned int height)
+  RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width,
+                                                    unsigned int height)
   {
     std::auto_ptr<Orthanc::Image>  block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
 
@@ -887,11 +521,11 @@
   }
 
     
-  RadiographyScene::Layer& RadiographyScene::LoadDicomFrame(const std::string& instance,
-                                                            unsigned int frame,
-                                                            bool httpCompression)
+  RadiographyLayer& RadiographyScene::LoadDicomFrame(const std::string& instance,
+                                                     unsigned int frame,
+                                                     bool httpCompression)
   {
-    Layer& layer = RegisterLayer(new DicomLayer);
+    RadiographyLayer& layer = RegisterLayer(new DicomLayer);
 
     {
       IWebService::Headers headers;
--- a/Framework/Radiography/RadiographyScene.h	Mon Nov 12 15:38:11 2018 +0100
+++ b/Framework/Radiography/RadiographyScene.h	Mon Nov 12 15:52:03 2018 +0100
@@ -21,10 +21,8 @@
 
 #pragma once
 
-#include "../Toolbox/AffineTransform2D.h"
-#include "../Toolbox/Extent2D.h"
+#include "RadiographyLayer.h"
 #include "../Toolbox/OrthancApiClient.h"
-#include "../Viewport/CairoContext.h"
 
 
 namespace OrthancStone
@@ -37,193 +35,12 @@
     typedef OriginMessage<MessageType_Widget_GeometryChanged, RadiographyScene> GeometryChangedMessage;
     typedef OriginMessage<MessageType_Widget_ContentChanged, RadiographyScene> ContentChangedMessage;
 
-    enum Corner
-    {
-      Corner_TopLeft,
-      Corner_TopRight,
-      Corner_BottomLeft,
-      Corner_BottomRight
-    };
-
-
-    class Layer : public boost::noncopyable
-    {
-      friend class RadiographyScene;
-      
-    private:
-      size_t             index_;
-      bool               hasSize_;
-      unsigned int       width_;
-      unsigned int       height_;
-      bool               hasCrop_;
-      unsigned int       cropX_;
-      unsigned int       cropY_;
-      unsigned int       cropWidth_;
-      unsigned int       cropHeight_;
-      AffineTransform2D  transform_;
-      AffineTransform2D  transformInverse_;
-      double             pixelSpacingX_;
-      double             pixelSpacingY_;
-      double             panX_;
-      double             panY_;
-      double             angle_;
-      bool               resizeable_;
-
-
-    protected:
-      const AffineTransform2D& GetTransform() const
-      {
-        return transform_;
-      }
-
-
-    private:
-      void UpdateTransform();
-      
-      void AddToExtent(Extent2D& extent,
-                       double x,
-                       double y) const;
-
-      void GetCornerInternal(double& x,
-                             double& y,
-                             Corner corner,
-                             unsigned int cropX,
-                             unsigned int cropY,
-                             unsigned int cropWidth,
-                             unsigned int cropHeight) const;
-
-      void SetIndex(size_t index)
-      {
-        index_ = index;
-      }
-      
-      bool Contains(double x,
-                    double y) const;
-      
-      void DrawBorders(CairoContext& context,
-                       double zoom);
-
-    public:
-      Layer();
-
-      virtual ~Layer()
-      {
-      }
-
-      size_t GetIndex() const
-      {
-        return index_;
-      }
-
-      void ResetCrop()
-      {
-        hasCrop_ = false;
-      }
-
-      void SetCrop(unsigned int x,
-                   unsigned int y,
-                   unsigned int width,
-                   unsigned int height);
-
-      void GetCrop(unsigned int& x,
-                   unsigned int& y,
-                   unsigned int& width,
-                   unsigned int& height) const;
-
-      void SetAngle(double angle);
-
-      double GetAngle() const
-      {
-        return angle_;
-      }
-
-      void SetSize(unsigned int width,
-                   unsigned int height);
-
-      unsigned int GetWidth() const
-      {
-        return width_;
-      }        
-
-      unsigned int GetHeight() const
-      {
-        return height_;
-      }       
-
-      Extent2D GetExtent() const;
-
-      bool GetPixel(unsigned int& imageX,
-                    unsigned int& imageY,
-                    double sceneX,
-                    double sceneY) const;
-
-      void SetPan(double x,
-                  double y);
-
-      void SetPixelSpacing(double x,
-                           double y);
-
-      double GetPixelSpacingX() const
-      {
-        return pixelSpacingX_;
-      }   
-
-      double GetPixelSpacingY() const
-      {
-        return pixelSpacingY_;
-      }   
-
-      double GetPanX() const
-      {
-        return panX_;
-      }
-
-      double GetPanY() const
-      {
-        return panY_;
-      }
-
-      void GetCenter(double& centerX,
-                     double& centerY) const;
-
-      void GetCorner(double& x /* out */,
-                     double& y /* out */,
-                     Corner corner) const;
-      
-      bool LookupCorner(Corner& corner /* out */,
-                        double x,
-                        double y,
-                        double zoom,
-                        double viewportDistance) const;
-
-      bool IsResizeable() const
-      {
-        return resizeable_;
-      }
-
-      void SetResizeable(bool resizeable)
-      {
-        resizeable_ = resizeable;
-      }
-
-      virtual bool GetDefaultWindowing(float& center,
-                                       float& width) const = 0;
-
-      virtual void Render(Orthanc::ImageAccessor& buffer,
-                          const AffineTransform2D& viewTransform,
-                          ImageInterpolation interpolation) const = 0;
-
-      virtual bool GetRange(float& minValue,
-                            float& maxValue) const = 0;
-    }; 
-
-
     class LayerAccessor : public boost::noncopyable
     {
     private:
       RadiographyScene&  scene_;
       size_t             index_;
-      Layer*             layer_;
+      RadiographyLayer*  layer_;
 
     public:
       LayerAccessor(RadiographyScene& scene,
@@ -247,7 +64,7 @@
 
       size_t GetIndex() const;
 
-      Layer& GetLayer() const;
+      RadiographyLayer& GetLayer() const;
     };
 
 
@@ -255,7 +72,7 @@
     class AlphaLayer;    
     class DicomLayer;
 
-    typedef std::map<size_t, Layer*>  Layers;
+    typedef std::map<size_t, RadiographyLayer*>  Layers;
         
     OrthancApiClient&  orthanc_;
     size_t             countLayers_;
@@ -264,7 +81,7 @@
     float              windowingWidth_;
     Layers             layers_;
 
-    Layer& RegisterLayer(Layer* layer);
+    RadiographyLayer& RegisterLayer(RadiographyLayer* layer);
 
     void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
@@ -287,15 +104,15 @@
     void SetWindowing(float center,
                       float width);
 
-    Layer& LoadText(const Orthanc::Font& font,
-                    const std::string& utf8);
+    RadiographyLayer& LoadText(const Orthanc::Font& font,
+                               const std::string& utf8);
     
-    Layer& LoadTestBlock(unsigned int width,
-                         unsigned int height);
+    RadiographyLayer& LoadTestBlock(unsigned int width,
+                                    unsigned int height);
     
-    Layer& LoadDicomFrame(const std::string& instance,
-                          unsigned int frame,
-                          bool httpCompression);
+    RadiographyLayer& LoadDicomFrame(const std::string& instance,
+                                     unsigned int frame,
+                                     bool httpCompression);
 
     Extent2D GetSceneExtent() const;
 
--- a/Framework/StoneEnumerations.h	Mon Nov 12 15:38:11 2018 +0100
+++ b/Framework/StoneEnumerations.h	Mon Nov 12 15:52:03 2018 +0100
@@ -166,7 +166,16 @@
     MessageType_CustomMessage // Custom messages ids ust be greater than this (this one must remain in last position)
   };
 
+  
+  enum Corner
+  {
+    Corner_TopLeft,
+    Corner_TopRight,
+    Corner_BottomLeft,
+    Corner_BottomRight
+  };
 
+  
   bool StringToSopClassUid(SopClassUid& result,
                            const std::string& source);
 
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Nov 12 15:38:11 2018 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Nov 12 15:52:03 2018 +0100
@@ -246,6 +246,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.h
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.h
   ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp