changeset 97:d18dcc963930 wasm

separation of the renderers vs. viewport slice
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 30 May 2017 14:09:11 +0200
parents f8bce1bebe01
children a33abae66344
files Applications/Samples/SingleFrameApplication.h Framework/Layers/FrameRenderer.cpp Framework/Layers/FrameRenderer.h Framework/Layers/ILayerRenderer.h Framework/Layers/ILayerSource.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Toolbox/Extent.cpp Framework/Toolbox/Extent.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Resources/CMake/OrthancStone.cmake
diffstat 13 files changed, 331 insertions(+), 289 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SingleFrameApplication.h	Tue May 30 10:30:57 2017 +0200
+++ b/Applications/Samples/SingleFrameApplication.h	Tue May 30 14:09:11 2017 +0200
@@ -103,7 +103,22 @@
 
         if (frame.GetSliceCount() > 0)
         {
+#if 1
+          GeometryToolbox::Print(frame.GetSlice(0).GetGeometry().GetOrigin());
           widget_->SetSlice(frame.GetSlice(0).GetGeometry());
+#else
+          // TEST for scene extents - Rotate the axes
+          double a = 15.0 / 180.0 * M_PI;
+          
+          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+          Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0);
+          GeometryToolbox::Print(frame.GetSlice(0).GetGeometry().GetOrigin());
+          GeometryToolbox::Print(x);
+          GeometryToolbox::Print(y);
+          SliceGeometry s(frame.GetSlice(0).GetGeometry().GetOrigin(), x, y);
+          widget_->SetSlice(s);
+#endif
+          
           widget_->SetDefaultView();
         }
       }
@@ -176,14 +191,18 @@
         layer->Register(*this);
         widget->AddLayer(layer.release());
 
+        RenderStyle s;
+
         if (parameters["smooth"].as<bool>())
         {
-          RenderStyle s; 
           s.interpolation_ = ImageInterpolation_Linear;
-          widget->SetLayerStyle(0, s);
         }
+
+        //s.drawGrid_ = true;
+        widget->SetLayerStyle(0, s);
 #else
         // 0178023P**
+        // Extent of the CT layer: (-35.068 -20.368) => (34.932 49.632)
         std::auto_ptr<OrthancFrameLayerSource> ct;
         ct.reset(new OrthancFrameLayerSource(context.GetWebService(), "c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0));
         //ct.reset(new OrthancFrameLayerSource(context.GetWebService(), "4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0));  // BAD SLICE
@@ -196,6 +215,7 @@
 
         {
           RenderStyle s;
+          //s.drawGrid_ = true;
           s.alpha_ = 1;
           widget->SetLayerStyle(0, s);
         }
--- a/Framework/Layers/FrameRenderer.cpp	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Layers/FrameRenderer.cpp	Tue May 30 14:09:11 2017 +0200
@@ -28,54 +28,6 @@
 
 namespace OrthancStone
 {
-  static bool ComputePixelTransform(cairo_matrix_t& target,
-                                    const SliceGeometry& viewportSlice,
-                                    const SliceGeometry& frameSlice,
-                                    double pixelSpacingX,
-                                    double pixelSpacingY)
-  {
-    bool isOpposite;
-    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite,
-                                               viewportSlice.GetNormal(),
-                                               frameSlice.GetNormal()))
-    {
-      return false;
-    }
-    else
-    {
-      double x0, y0, x1, y1, x2, y2;
-      viewportSlice.ProjectPoint(x0, y0, frameSlice.GetOrigin() 
-                                 - 0.5 * pixelSpacingX * frameSlice.GetAxisX()
-                                 - 0.5 * pixelSpacingY * frameSlice.GetAxisY());
-      viewportSlice.ProjectPoint(x1, y1, frameSlice.GetOrigin() 
-                                 + 0.5 * pixelSpacingX * frameSlice.GetAxisX()
-                                 - 0.5 * pixelSpacingY * frameSlice.GetAxisY());
-      viewportSlice.ProjectPoint(x2, y2, frameSlice.GetOrigin() 
-                                 - 0.5 * pixelSpacingX * frameSlice.GetAxisX()
-                                 + 0.5 * pixelSpacingY * frameSlice.GetAxisY());
-
-      /**
-       * Now we solve the system of linear equations Ax + b = x', given:
-       *   A [0 ; 0] + b = [x0 ; y0]
-       *   A [1 ; 0] + b = [x1 ; y1]
-       *   A [0 ; 1] + b = [x2 ; y2]
-       * <=>
-       *   b = [x0 ; y0]
-       *   A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0]
-       *   A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0]
-       * <=>
-       *   b = [x0 ; y0]
-       *   [a11 ; a21] = [x1 - x0 ; y1 - y0]
-       *   [a12 ; a22] = [x2 - x0 ; y2 - y0]
-       **/
-
-      cairo_matrix_init(&target, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0);
-
-      return true;
-    }
-  }
-
-
   FrameRenderer::FrameRenderer(const SliceGeometry& frameSlice,
                                double pixelSpacingX,
                                double pixelSpacingY,
@@ -88,56 +40,8 @@
   }
 
 
-  bool FrameRenderer::ComputeFrameExtent(double& x1,
-                                         double& y1,
-                                         double& x2,
-                                         double& y2,
-                                         const SliceGeometry& viewportSlice,
-                                         const SliceGeometry& frameSlice,
-                                         unsigned int frameWidth,
-                                         unsigned int frameHeight,
-                                         double pixelSpacingX,
-                                         double pixelSpacingY)
-  {
-    bool isOpposite;
-    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal()))
-    {
-      return false;
-    }
-    else
-    {
-      cairo_matrix_t transform;
-      if (!ComputePixelTransform(transform, viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY))
-      {
-        return true;
-      }
-
-      x1 = 0;
-      y1 = 0;
-      cairo_matrix_transform_point(&transform, &x1, &y1);
-      
-      x2 = frameWidth;
-      y2 = frameHeight;
-      cairo_matrix_transform_point(&transform, &x2, &y2);
-      
-      if (x1 > x2)
-      {
-        std::swap(x1, x2);
-      }
-
-      if (y1 > y2)
-      {
-        std::swap(y1, y2);
-      }
-
-      return true;
-    }
-  }
-
-
   bool FrameRenderer::RenderLayer(CairoContext& context,
-                                  const ViewportGeometry& view,
-                                  const SliceGeometry& viewportSlice)
+                                  const ViewportGeometry& view)
   {    
     if (!style_.visible_)
     {
@@ -146,12 +50,6 @@
 
     if (display_.get() == NULL)
     {
-      if (!ComputePixelTransform(transform_, viewportSlice, frameSlice_,
-                                 pixelSpacingX_, pixelSpacingY_))
-      {
-        return true;
-      }
-
       display_.reset(GenerateDisplay(style_));
     }
 
@@ -161,7 +59,12 @@
 
     cairo_save(cr);
 
-    cairo_transform(cr, &transform_);
+    cairo_matrix_t transform;
+    cairo_matrix_init_identity(&transform);
+    cairo_matrix_scale(&transform, pixelSpacingX_, pixelSpacingY_);
+    cairo_matrix_translate(&transform, -0.5, -0.5);
+    cairo_transform(cr, &transform);
+
     //cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
     cairo_set_source_surface(cr, display_->GetObject(), 0, 0);
 
@@ -215,30 +118,6 @@
 
 
   ILayerRenderer* FrameRenderer::CreateRenderer(Orthanc::ImageAccessor* frame,
-                                                const SliceGeometry& frameSlice,
-                                                const OrthancPlugins::IDicomDataset& dicom,
-                                                double pixelSpacingX,
-                                                double pixelSpacingY,
-                                                bool isFullQuality)
-  {  
-    std::auto_ptr<Orthanc::ImageAccessor> protect(frame);
-
-    if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
-    {
-      return new ColorFrameRenderer(protect.release(), frameSlice, 
-                                    pixelSpacingX, pixelSpacingY, isFullQuality);
-    }
-    else
-    {
-      DicomFrameConverter converter;
-      converter.ReadParameters(dicom);
-      return new GrayscaleFrameRenderer(protect.release(), converter, frameSlice, 
-                                        pixelSpacingX, pixelSpacingY, isFullQuality);
-    }
-  }
-
-
-  ILayerRenderer* FrameRenderer::CreateRenderer(Orthanc::ImageAccessor* frame,
                                                 const Slice& frameSlice,
                                                 bool isFullQuality)
   {
@@ -246,13 +125,18 @@
 
     if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
     {
-      return new ColorFrameRenderer(protect.release(), frameSlice.GetGeometry(), 
-                                    frameSlice.GetPixelSpacingX(), frameSlice.GetPixelSpacingY(), isFullQuality);
+      return new ColorFrameRenderer(protect.release(),
+                                    frameSlice.GetGeometry(), 
+                                    frameSlice.GetPixelSpacingX(),
+                                    frameSlice.GetPixelSpacingY(), isFullQuality);
     }
     else
     {
-      return new GrayscaleFrameRenderer(protect.release(), frameSlice.GetConverter(), frameSlice.GetGeometry(), 
-                                        frameSlice.GetPixelSpacingX(), frameSlice.GetPixelSpacingY(), isFullQuality);
+      return new GrayscaleFrameRenderer(protect.release(),
+                                        frameSlice.GetConverter(),
+                                        frameSlice.GetGeometry(), 
+                                        frameSlice.GetPixelSpacingX(),
+                                        frameSlice.GetPixelSpacingY(), isFullQuality);
     }
   }
 }
--- a/Framework/Layers/FrameRenderer.h	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Layers/FrameRenderer.h	Tue May 30 14:09:11 2017 +0200
@@ -35,9 +35,7 @@
     double                        pixelSpacingY_;
     RenderStyle                   style_;
     bool                          isFullQuality_;
-
     std::auto_ptr<CairoSurface>   display_;
-    cairo_matrix_t                transform_;
 
   protected:
     virtual CairoSurface* GenerateDisplay(const RenderStyle& style) = 0;
@@ -48,34 +46,14 @@
                   double pixelSpacingY,
                   bool isFullQuality);
 
-    // TODO Remove this overload
-    static bool ComputeFrameExtent(double& x1,
-                                   double& y1,
-                                   double& x2,
-                                   double& y2,
-                                   const SliceGeometry& viewportSlice,
-                                   const SliceGeometry& frameSlice,
-                                   unsigned int frameWidth,
-                                   unsigned int frameHeight,
-                                   double pixelSpacingX,
-                                   double pixelSpacingY);
+    virtual bool RenderLayer(CairoContext& context,
+                             const ViewportGeometry& view);
 
-    static bool ComputeFrameExtent(double& x1,
-                                   double& y1,
-                                   double& x2,
-                                   double& y2,
-                                   const SliceGeometry& viewportSlice,
-                                   const Slice& frame)
+    virtual const SliceGeometry& GetLayerSlice()
     {
-      return ComputeFrameExtent(x1, y1, x2, y2, viewportSlice,
-                                frame.GetGeometry(), frame.GetWidth(), frame.GetHeight(),
-                                frame.GetPixelSpacingX(), frame.GetPixelSpacingY());
+      return frameSlice_;
     }
 
-    virtual bool RenderLayer(CairoContext& context,
-                             const ViewportGeometry& view,
-                             const SliceGeometry& viewportSlice);
-
     virtual void SetLayerStyle(const RenderStyle& style);
 
     virtual bool IsFullQuality() 
@@ -83,14 +61,6 @@
       return isFullQuality_;
     }
 
-    // TODO Remove this overload
-    static ILayerRenderer* CreateRenderer(Orthanc::ImageAccessor* frame,
-                                          const SliceGeometry& frameSlice,
-                                          const OrthancPlugins::IDicomDataset& dicom,
-                                          double pixelSpacingX,
-                                          double pixelSpacingY,
-                                          bool isFullQuality);
-
     static ILayerRenderer* CreateRenderer(Orthanc::ImageAccessor* frame,
                                           const Slice& frameSlice,
                                           bool isFullQuality);
--- a/Framework/Layers/ILayerRenderer.h	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Layers/ILayerRenderer.h	Tue May 30 14:09:11 2017 +0200
@@ -36,11 +36,12 @@
     }
     
     virtual bool RenderLayer(CairoContext& context,
-                             const ViewportGeometry& view,
-                             const SliceGeometry& viewportSlice) = 0;
+                             const ViewportGeometry& view) = 0;
 
     virtual void SetLayerStyle(const RenderStyle& style) = 0;
 
+    virtual const SliceGeometry& GetLayerSlice() = 0;
+    
     virtual bool IsFullQuality() = 0;
   };
 }
--- a/Framework/Layers/ILayerSource.h	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Layers/ILayerSource.h	Tue May 30 14:09:11 2017 +0200
@@ -65,10 +65,7 @@
 
     virtual void Register(IObserver& observer) = 0;
 
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
+    virtual bool GetExtent(std::vector<Vector>& points,
                            const SliceGeometry& viewportSlice) = 0;
 
     virtual void ScheduleLayerCreation(const SliceGeometry& viewportSlice) = 0;
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue May 30 14:09:11 2017 +0200
@@ -77,43 +77,32 @@
   }
 
 
-  bool OrthancFrameLayerSource::GetExtent(double& x1,
-                                          double& y1,
-                                          double& x2,
-                                          double& y2,
+  bool OrthancFrameLayerSource::GetExtent(std::vector<Vector>& points,
                                           const SliceGeometry& viewportSlice)
   {
-    bool ok = false;
+    size_t index;
+    if (loader_.IsGeometryReady() &&
+        loader_.LookupSlice(index, viewportSlice))
+    {
+      const Slice& slice = loader_.GetSlice(index);
+      const SliceGeometry& plane = slice.GetGeometry();
 
-    if (loader_.IsGeometryReady())
-    {
-      double tx1, ty1, tx2, ty2;
+      double sx = slice.GetPixelSpacingX();
+      double sy = slice.GetPixelSpacingY();
+      double w = static_cast<double>(slice.GetWidth());
+      double h = static_cast<double>(slice.GetHeight());
 
-      for (size_t i = 0; i < loader_.GetSliceCount(); i++)
-      {
-        if (FrameRenderer::ComputeFrameExtent(tx1, ty1, tx2, ty2, viewportSlice, loader_.GetSlice(i)))
-        {
-          if (ok)
-          {
-            x1 = std::min(x1, tx1);
-            y1 = std::min(y1, ty1);
-            x2 = std::min(x2, tx2);
-            y2 = std::min(y2, ty2);
-          }
-          else
-          {
-            // This is the first slice parallel to the viewport
-            x1 = tx1;
-            y1 = ty1;
-            x2 = tx2;
-            y2 = ty2;
-            ok = true;
-          }
-        }
-      }
+      points.clear();
+      points.push_back(plane.MapSliceToWorldCoordinates(-0.5      * sx, -0.5      * sy));
+      points.push_back(plane.MapSliceToWorldCoordinates((w - 0.5) * sx, -0.5      * sy));
+      points.push_back(plane.MapSliceToWorldCoordinates(-0.5      * sx, (h - 0.5) * sy));
+      points.push_back(plane.MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy));
+      return true;
     }
-
-    return ok;
+    else
+    {
+      return false;
+    }
   }
 
   
@@ -125,8 +114,8 @@
     {
       if (loader_.LookupSlice(index, viewportSlice))
       {
-        //loader_.ScheduleLoadSliceImage(index, SliceImageQuality_Full);
-        loader_.ScheduleLoadSliceImage(index, SliceImageQuality_Jpeg50);
+        loader_.ScheduleLoadSliceImage(index, SliceImageQuality_Full);
+        //loader_.ScheduleLoadSliceImage(index, SliceImageQuality_Jpeg50);
       }
       else
       {
--- a/Framework/Layers/OrthancFrameLayerSource.h	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Tue May 30 14:09:11 2017 +0200
@@ -66,11 +66,8 @@
       return loader_.GetSlice(slice);
     }
 
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
-                           const SliceGeometry& viewportSlice /* ignored */);
+    virtual bool GetExtent(std::vector<Vector>& points,
+                           const SliceGeometry& viewportSlice);
 
     virtual void ScheduleLayerCreation(const SliceGeometry& viewportSlice);
   };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Extent.cpp	Tue May 30 14:09:11 2017 +0200
@@ -0,0 +1,86 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "Extent.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace OrthancStone
+{
+  void Extent::Reset()
+  {
+    empty_ = true;
+    x1_ = 0;
+    y1_ = 0;
+    x2_ = 0;
+    y2_ = 0;      
+  }
+
+  void Extent::AddPoint(double x,
+                        double y)
+  {
+    if (empty_)
+    {
+      x1_ = x;
+      y1_ = y;
+      x2_ = x;
+      y2_ = y;
+      empty_ = false;
+    }
+    else
+    {
+      x1_ = std::min(x1_, x);
+      y1_ = std::min(y1_, y);
+      x2_ = std::max(x2_, x);
+      y2_ = std::max(y2_, y);
+    }
+
+    assert(x1_ <= x2_ &&
+           y1_ <= y2_);    // This is the invariant of the structure
+  }
+
+
+  void Extent::Union(const Extent& other)
+  {
+    if (other.IsEmpty())
+    {
+      return;
+    }
+
+    if (IsEmpty())
+    {
+      *this = other;
+      return;
+    }
+
+    assert(!IsEmpty());
+
+    x1_ = std::min(x1_, other.x1_);
+    y1_ = std::min(y1_, other.y1_);
+    x2_ = std::min(x2_, other.x2_);
+    y2_ = std::min(y2_, other.y2_);
+
+    assert(x1_ <= x2_ &&
+           y1_ <= y2_);    // This is the invariant of the structure
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Extent.h	Tue May 30 14:09:11 2017 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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
+
+namespace OrthancStone
+{
+  class Extent
+  {
+  private:
+    bool    empty_;
+    double  x1_;
+    double  y1_;
+    double  x2_;
+    double  y2_;
+
+  public:
+    Extent()
+    {
+      Reset();
+    }
+
+    void Reset();
+
+    void AddPoint(double x,
+                  double y);
+
+    void Union(const Extent& other);
+
+    bool IsEmpty() const
+    {
+      return empty_;
+    }
+
+    double GetX1() const
+    {
+      return x1_;
+    }
+
+    double GetY1() const
+    {
+      return y1_;
+    }
+
+    double GetX2() const
+    {
+      return x2_;
+    }
+
+    double GetY2() const
+    {
+      return y2_;
+    }
+
+    double GetWidth() const
+    {
+      return x2_ - x1_;
+    }
+
+    double GetHeight() const
+    {
+      return y2_ - y1_;
+    }
+  };
+}
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue May 30 14:09:11 2017 +0200
@@ -176,6 +176,7 @@
 
       switch (operation->GetMode())
       {
+        case Mode_InstanceGeometry:
         case Mode_SeriesGeometry:
           that_.userCallback_.NotifyGeometryError(that_);
           that_.state_ = State_Error;
--- a/Framework/Widgets/LayerWidget.cpp	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Tue May 30 14:09:11 2017 +0200
@@ -105,16 +105,51 @@
     }
 
     bool RenderScene(CairoContext& context,
-                     const ViewportGeometry& view)
+                     const ViewportGeometry& view,
+                     const SliceGeometry& viewportSlice)
     {
       bool fullQuality = true;
+      cairo_t *cr = context.GetObject();
 
       for (size_t i = 0; i < renderers_.size(); i++)
       {
-        if (renderers_[i] != NULL &&
-            !renderers_[i]->RenderLayer(context, view, slice_))
+        if (renderers_[i] != NULL)
         {
-          return false;
+          const SliceGeometry& frameSlice = renderers_[i]->GetLayerSlice();
+          
+          double x0, y0, x1, y1, x2, y2;
+          viewportSlice.ProjectPoint(x0, y0, frameSlice.GetOrigin());
+          viewportSlice.ProjectPoint(x1, y1, frameSlice.GetOrigin() + frameSlice.GetAxisX());
+          viewportSlice.ProjectPoint(x2, y2, frameSlice.GetOrigin() + frameSlice.GetAxisY());
+
+          /**
+           * Now we solve the system of linear equations Ax + b = x', given:
+           *   A [0 ; 0] + b = [x0 ; y0]
+           *   A [1 ; 0] + b = [x1 ; y1]
+           *   A [0 ; 1] + b = [x2 ; y2]
+           * <=>
+           *   b = [x0 ; y0]
+           *   A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0]
+           *   A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0]
+           * <=>
+           *   b = [x0 ; y0]
+           *   [a11 ; a21] = [x1 - x0 ; y1 - y0]
+           *   [a12 ; a22] = [x2 - x0 ; y2 - y0]
+           **/
+
+          cairo_matrix_t transform;
+          cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0);
+
+          cairo_save(cr);
+          cairo_transform(cr, &transform);
+          
+          if (!renderers_[i]->RenderLayer(context, view))
+          {
+            cairo_restore(cr);
+            return false;
+          }
+
+          cairo_restore(cr);
         }
 
         if (renderers_[i] != NULL &&
@@ -129,7 +164,6 @@
         double x, y;
         view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10);
 
-        cairo_t *cr = context.GetObject();
         cairo_translate(cr, x, y);
 
 #if 1
@@ -180,29 +214,20 @@
   }
     
 
-  bool LayerWidget::GetAndFixExtent(double& x1,
-                                    double& y1,
-                                    double& x2,
-                                    double& y2,
-                                    ILayerSource& source) const
+  void LayerWidget::GetLayerExtent(Extent& extent,
+                                   ILayerSource& source) const
   {
-    if (source.GetExtent(x1, y1, x2, y2, slice_))
+    extent.Reset();
+    
+    std::vector<Vector> points;
+    if (source.GetExtent(points, slice_))
     {
-      if (x1 > x2)
+      for (size_t i = 0; i < points.size(); i++)
       {
-        std::swap(x1, x2);
+        double x, y;
+        slice_.ProjectPoint(x, y, points[i]);
+        extent.AddPoint(x, y);
       }
-
-      if (y1 > y2)
-      {
-        std::swap(y1, y2);
-      }
-
-      return true;
-    }
-    else
-    {
-      return false;
     }
   }
 
@@ -212,37 +237,20 @@
                                    double& x2,
                                    double& y2)
   {
-    bool first = true;
+    Extent sceneExtent;
 
     for (size_t i = 0; i < layers_.size(); i++)
     {
       double ax, ay, bx, by;
 
       assert(layers_[i] != NULL);
-      if (GetAndFixExtent(ax, ay, bx, by, *layers_[i]))
-      {
-        LOG(INFO) << "Extent of layer " << i << ": (" << ax << "," << ay
-                  << ")->(" << bx << "," << by << ")";
+      Extent layerExtent;
+      GetLayerExtent(layerExtent, *layers_[i]);
 
-        if (first)
-        {
-          x1 = ax;
-          y1 = ay;
-          x2 = bx;
-          y2 = by;
-          first = false;
-        }
-        else
-        {
-          x1 = std::min(x1, ax);
-          y1 = std::min(y1, ay);
-          x2 = std::max(x2, bx);
-          y2 = std::max(y2, by);
-        }
-      }
+      sceneExtent.Union(layerExtent);
     }
 
-    if (first)
+    if (sceneExtent.IsEmpty())
     {
       // Set a default extent of (-1,-1) -> (0,0)
       x1 = -1;
@@ -250,20 +258,27 @@
       x2 = 1;
       y2 = 1;
     }
-
-    // Ensure the extent is non-empty
-    if (x1 >= x2)
+    else
     {
-      double tmp = x1;
-      x1 = tmp - 0.5;
-      x2 = tmp + 0.5;
-    }
+      x1 = sceneExtent.GetX1();
+      y1 = sceneExtent.GetY1();
+      x2 = sceneExtent.GetX2();
+      y2 = sceneExtent.GetY2();
 
-    if (y1 >= y2)
-    {
-      double tmp = y1;
-      y1 = tmp - 0.5;
-      y2 = tmp + 0.5;
+      // Ensure the extent is non-empty
+      if (x1 >= x2)
+      {
+        double tmp = x1;
+        x1 = tmp - 0.5;
+        x2 = tmp + 0.5;
+      }
+
+      if (y1 >= y2)
+      {
+        double tmp = y1;
+        y1 = tmp - 0.5;
+        y2 = tmp + 0.5;
+      }
     }
   }
 
@@ -273,7 +288,7 @@
   {
     if (currentScene_.get() != NULL)
     {
-      return currentScene_->RenderScene(context, view);
+      return currentScene_->RenderScene(context, view, slice_);
     }
     else
     {
@@ -399,7 +414,7 @@
     
     Slice displayedSlice(slice_, THIN_SLICE_THICKNESS);
 
-    if (!displayedSlice.ContainsPlane(slice))
+    //if (!displayedSlice.ContainsPlane(slice))
     {
       if (currentScene_.get() == NULL ||
           (pendingScene_.get() != NULL &&
--- a/Framework/Widgets/LayerWidget.h	Tue May 30 10:30:57 2017 +0200
+++ b/Framework/Widgets/LayerWidget.h	Tue May 30 14:09:11 2017 +0200
@@ -23,6 +23,7 @@
 
 #include "WorldSceneWidget.h"
 #include "../Layers/ILayerSource.h"
+#include "../Toolbox/Extent.h"
 
 #include <map>
 
@@ -49,11 +50,8 @@
     bool LookupLayer(size_t& index /* out */,
                      const ILayerSource& layer) const;
 
-    bool GetAndFixExtent(double& x1,
-                         double& y1,
-                         double& x2,
-                         double& y2,
-                         ILayerSource& source) const;
+    void GetLayerExtent(Extent& extent,
+                        ILayerSource& source) const;
 
     virtual void NotifyGeometryReady(const ILayerSource& source);
 
--- a/Resources/CMake/OrthancStone.cmake	Tue May 30 10:30:57 2017 +0200
+++ b/Resources/CMake/OrthancStone.cmake	Tue May 30 14:09:11 2017 +0200
@@ -188,22 +188,27 @@
   ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlSurface.cpp
   ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlWindow.cpp
 
+  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImage.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImagePolicyBase.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageProgressivePolicy.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageSimplePolicy.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/CircleMeasureTracker.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/ColorFrameRenderer.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/DicomStructureSetRendererFactory.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Layers/DicomStructureSetRendererFactory.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/FrameRenderer.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/GrayscaleFrameRenderer.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/LayerSourceBase.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/LineLayerRenderer.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/LineMeasureTracker.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/OrthancFrameLayerSource.cpp
   ${ORTHANC_STONE_DIR}/Framework/Layers/RenderStyle.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/SeriesFrameRendererFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/SiblingSliceLocationFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/SingleFrameRendererFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/OrthancFrameLayerSource.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Layers/SeriesFrameRendererFactory.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Layers/SiblingSliceLocationFactory.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Layers/SingleFrameRendererFactory.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomFrameConverter.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomStructureSet.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DownloadStack.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/Extent.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/GeometryToolbox.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/MessagingToolbox.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSeriesLoader.cpp
@@ -220,18 +225,14 @@
   ${ORTHANC_STONE_DIR}/Framework/Viewport/WidgetViewport.cpp
   ${ORTHANC_STONE_DIR}/Framework/Volumes/ImageBuffer3D.cpp
   ${ORTHANC_STONE_DIR}/Framework/Volumes/SlicedVolumeBase.cpp
-  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImage.cpp
-  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImagePolicyBase.cpp
-  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageProgressivePolicy.cpp
-  #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageSimplePolicy.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/CairoWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/EmptyWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayeredSceneWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayerWidget.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Widgets/LayeredSceneWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/LayoutWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/TestCairoWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/TestWorldSceneWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/WidgetBase.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayerWidget.cpp
   ${ORTHANC_STONE_DIR}/Framework/Widgets/WorldSceneWidget.cpp
 
   ${PLATFORM_SOURCES}