changeset 77:f5f54ed8d307 wasm

refactoring
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 24 May 2017 21:13:29 +0200
parents 0aef120d7e1c
children 93b917b02fee
files Framework/Layers/ColorFrameRenderer.cpp Framework/Layers/ColorFrameRenderer.h Framework/Layers/DicomStructureSetRendererFactory.cpp Framework/Layers/FrameRenderer.cpp Framework/Layers/FrameRenderer.h Framework/Layers/GrayscaleFrameRenderer.cpp Framework/Layers/GrayscaleFrameRenderer.h Framework/Layers/ILayerRenderer.h Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/LayerSourceBase.h Framework/Layers/LineLayerRenderer.cpp Framework/Layers/LineLayerRenderer.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Layers/SeriesFrameRendererFactory.cpp Framework/Layers/SingleFrameRendererFactory.cpp Framework/Toolbox/OrthancAsynchronousWebService.cpp Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Toolbox/Slice.h Framework/Toolbox/SliceGeometry.h Framework/Toolbox/SlicesSorter.cpp Framework/Toolbox/SlicesSorter.h Framework/Volumes/VolumeImage.cpp Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Framework/Widgets/LayeredSceneWidget.cpp UnitTestsSources/UnitTestsMain.cpp
diffstat 29 files changed, 291 insertions(+), 280 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Layers/ColorFrameRenderer.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/ColorFrameRenderer.cpp	Wed May 24 21:13:29 2017 +0200
@@ -38,12 +38,11 @@
 
 
   ColorFrameRenderer::ColorFrameRenderer(Orthanc::ImageAccessor* frame,
-                                         const SliceGeometry& viewportSlice,
                                          const SliceGeometry& frameSlice,
                                          double pixelSpacingX,
                                          double pixelSpacingY,
                                          bool isFullQuality) :
-    FrameRenderer(viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
+    FrameRenderer(frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
     frame_(frame)
   {
     if (frame == NULL)
--- a/Framework/Layers/ColorFrameRenderer.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/ColorFrameRenderer.h	Wed May 24 21:13:29 2017 +0200
@@ -35,7 +35,6 @@
 
   public:
     ColorFrameRenderer(Orthanc::ImageAccessor* frame,    // Takes ownership
-                       const SliceGeometry& viewportSlice,
                        const SliceGeometry& frameSlice,
                        double pixelSpacingX,
                        double pixelSpacingY,
--- a/Framework/Layers/DicomStructureSetRendererFactory.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/DicomStructureSetRendererFactory.cpp	Wed May 24 21:13:29 2017 +0200
@@ -42,7 +42,8 @@
     }
 
     virtual bool RenderLayer(CairoContext& context,
-                             const ViewportGeometry& view)
+                             const ViewportGeometry& view,
+                             const SliceGeometry& viewportSlice)
     {
       if (visible_)
       {
--- a/Framework/Layers/FrameRenderer.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/FrameRenderer.cpp	Wed May 24 21:13:29 2017 +0200
@@ -76,12 +76,10 @@
   }
 
 
-  FrameRenderer::FrameRenderer(const SliceGeometry& viewportSlice,
-                               const SliceGeometry& frameSlice,
+  FrameRenderer::FrameRenderer(const SliceGeometry& frameSlice,
                                double pixelSpacingX,
                                double pixelSpacingY,
                                bool isFullQuality) :
-    viewportSlice_(viewportSlice),
     frameSlice_(frameSlice),
     pixelSpacingX_(pixelSpacingX),
     pixelSpacingY_(pixelSpacingY),
@@ -138,7 +136,8 @@
 
 
   bool FrameRenderer::RenderLayer(CairoContext& context,
-                                  const ViewportGeometry& view)
+                                  const ViewportGeometry& view,
+                                  const SliceGeometry& viewportSlice)
   {
     if (!style_.visible_)
     {
@@ -147,7 +146,7 @@
 
     if (display_.get() == NULL)
     {
-      if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_,
+      if (!ComputePixelTransform(transform_, viewportSlice, frameSlice_,
                                  pixelSpacingX_, pixelSpacingY_))
       {
         return true;
@@ -216,7 +215,6 @@
 
 
   ILayerRenderer* FrameRenderer::CreateRenderer(Orthanc::ImageAccessor* frame,
-                                                const SliceGeometry& viewportSlice,
                                                 const SliceGeometry& frameSlice,
                                                 const OrthancPlugins::IDicomDataset& dicom,
                                                 double pixelSpacingX,
@@ -227,15 +225,34 @@
 
     if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
     {
-      return new ColorFrameRenderer(protect.release(), viewportSlice, frameSlice, 
+      return new ColorFrameRenderer(protect.release(), frameSlice, 
                                     pixelSpacingX, pixelSpacingY, isFullQuality);
     }
     else
     {
       DicomFrameConverter converter;
       converter.ReadParameters(dicom);
-      return new GrayscaleFrameRenderer(protect.release(), converter, viewportSlice, frameSlice, 
+      return new GrayscaleFrameRenderer(protect.release(), converter, frameSlice, 
                                         pixelSpacingX, pixelSpacingY, isFullQuality);
     }
   }
+
+
+  ILayerRenderer* FrameRenderer::CreateRenderer(Orthanc::ImageAccessor* frame,
+                                                const Slice& slice,
+                                                bool isFullQuality)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> protect(frame);
+
+    if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      return new ColorFrameRenderer(protect.release(), slice.GetGeometry(), 
+                                    slice.GetPixelSpacingX(), slice.GetPixelSpacingY(), isFullQuality);
+    }
+    else
+    {
+      return new GrayscaleFrameRenderer(protect.release(), slice.GetConverter(), slice.GetGeometry(), 
+                                        slice.GetPixelSpacingX(), slice.GetPixelSpacingY(), isFullQuality);
+    }
+  }
 }
--- a/Framework/Layers/FrameRenderer.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/FrameRenderer.h	Wed May 24 21:13:29 2017 +0200
@@ -23,14 +23,13 @@
 
 #include "ILayerRenderer.h"
 
-#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/Slice.h"
 
 namespace OrthancStone
 {
   class FrameRenderer : public ILayerRenderer
   {
   private:
-    SliceGeometry                 viewportSlice_;
     SliceGeometry                 frameSlice_;
     double                        pixelSpacingX_;
     double                        pixelSpacingY_;
@@ -44,12 +43,12 @@
     virtual CairoSurface* GenerateDisplay(const RenderStyle& style) = 0;
 
   public:
-    FrameRenderer(const SliceGeometry& viewportSlice,
-                  const SliceGeometry& frameSlice,
+    FrameRenderer(const SliceGeometry& frameSlice,
                   double pixelSpacingX,
                   double pixelSpacingY,
                   bool isFullQuality);
 
+    // TODO Remove this overload
     static bool ComputeFrameExtent(double& x1,
                                    double& y1,
                                    double& x2,
@@ -61,8 +60,21 @@
                                    double pixelSpacingX,
                                    double pixelSpacingY);
 
+    static bool ComputeFrameExtent(double& x1,
+                                   double& y1,
+                                   double& x2,
+                                   double& y2,
+                                   const SliceGeometry& viewportSlice,
+                                   const Slice& slice)
+    {
+      return ComputeFrameExtent(x1, y1, x2, y2, viewportSlice,
+                                slice.GetGeometry(), slice.GetWidth(), slice.GetHeight(),
+                                slice.GetPixelSpacingX(), slice.GetPixelSpacingY());
+    }
+
     virtual bool RenderLayer(CairoContext& context,
-                             const ViewportGeometry& view);
+                             const ViewportGeometry& view,
+                             const SliceGeometry& viewportSlice);
 
     virtual void SetLayerStyle(const RenderStyle& style);
 
@@ -71,12 +83,16 @@
       return isFullQuality_;
     }
 
+    // TODO Remove this overload
     static ILayerRenderer* CreateRenderer(Orthanc::ImageAccessor* frame,
-                                          const SliceGeometry& viewportSlice,
                                           const SliceGeometry& frameSlice,
                                           const OrthancPlugins::IDicomDataset& dicom,
                                           double pixelSpacingX,
                                           double pixelSpacingY,
                                           bool isFullQuality);
+
+    static ILayerRenderer* CreateRenderer(Orthanc::ImageAccessor* frame,
+                                          const Slice& slice,
+                                          bool isFullQuality);
   };
 }
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/GrayscaleFrameRenderer.cpp	Wed May 24 21:13:29 2017 +0200
@@ -107,12 +107,11 @@
 
   GrayscaleFrameRenderer::GrayscaleFrameRenderer(Orthanc::ImageAccessor* frame,
                                                  const DicomFrameConverter& converter,
-                                                 const SliceGeometry& viewportSlice,
                                                  const SliceGeometry& frameSlice,
                                                  double pixelSpacingX,
                                                  double pixelSpacingY,
                                                  bool isFullQuality) :
-    FrameRenderer(viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
+    FrameRenderer(frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
     frame_(frame),
     defaultWindowCenter_(converter.GetDefaultWindowCenter()),
     defaultWindowWidth_(converter.GetDefaultWindowWidth())
--- a/Framework/Layers/GrayscaleFrameRenderer.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/GrayscaleFrameRenderer.h	Wed May 24 21:13:29 2017 +0200
@@ -39,7 +39,6 @@
   public:
     GrayscaleFrameRenderer(Orthanc::ImageAccessor* frame,    // Takes ownership
                            const DicomFrameConverter& converter,
-                           const SliceGeometry& viewportSlice,
                            const SliceGeometry& frameSlice,
                            double pixelSpacingX,
                            double pixelSpacingY,
--- a/Framework/Layers/ILayerRenderer.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/ILayerRenderer.h	Wed May 24 21:13:29 2017 +0200
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "../Viewport/CairoContext.h"
+#include "../Toolbox/SliceGeometry.h"
 #include "../Toolbox/ViewportGeometry.h"
 #include "RenderStyle.h"
 
@@ -35,7 +36,8 @@
     }
     
     virtual bool RenderLayer(CairoContext& context,
-                             const ViewportGeometry& view) = 0;
+                             const ViewportGeometry& view,
+                             const SliceGeometry& viewportSlice) = 0;
 
     virtual void SetLayerStyle(const RenderStyle& style) = 0;
 
--- a/Framework/Layers/ILayerSource.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/ILayerSource.h	Wed May 24 21:13:29 2017 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "ILayerRenderer.h"
-#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/Slice.h"
 
 namespace OrthancStone
 {
@@ -40,22 +40,24 @@
       // answer to "GetExtent()"
       virtual void NotifyGeometryReady(ILayerSource& source) = 0;
       
+      virtual void NotifyGeometryError(ILayerSource& source) = 0;
+      
       // Triggered if the extent or the content of the volume has changed
       virtual void NotifySourceChange(ILayerSource& source) = 0;
 
-      // Triggered if some slice in the source volume has changed
+      // Triggered if the content of some slice in the source volume has changed
       virtual void NotifySliceChange(ILayerSource& source,
-                                     const SliceGeometry& slice) = 0;
+                                     const Slice& slice) = 0;
 
       // The layer must be deleted by the observer. "layer" will never
       // be "NULL", otherwise "NotifyLayerError()" would have been
       // called.
       virtual void NotifyLayerReady(ILayerRenderer *layer,
                                     ILayerSource& source,
-                                    const SliceGeometry& viewportSlice) = 0;
+                                    const Slice& slice) = 0;
 
       virtual void NotifyLayerError(ILayerSource& source,
-                                    const SliceGeometry& viewportSlice) = 0;
+                                    const SliceGeometry& slice) = 0;
     };
     
     virtual ~ILayerSource()
--- a/Framework/Layers/LayerSourceBase.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Wed May 24 21:13:29 2017 +0200
@@ -38,6 +38,19 @@
     }
   }  
     
+  void LayerSourceBase::NotifyGeometryError()
+  {
+    if (!started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (observer_ != NULL)
+    {
+      observer_->NotifyGeometryError(*this);
+    }
+  }  
+    
   void LayerSourceBase::NotifySourceChange()
   {
     if (!started_)
@@ -51,7 +64,7 @@
     }
   }
 
-  void LayerSourceBase::NotifySliceChange(const SliceGeometry& slice)
+  void LayerSourceBase::NotifySliceChange(const Slice& slice)
   {
     if (!started_)
     {
@@ -65,7 +78,7 @@
   }
 
   void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
-                                         const SliceGeometry& viewportSlice)
+                                         const Slice& slice)
   {
     std::auto_ptr<ILayerRenderer> tmp(layer);
     
@@ -81,11 +94,11 @@
     
     if (observer_ != NULL)
     {
-      observer_->NotifyLayerReady(tmp.release(), *this, viewportSlice);
+      observer_->NotifyLayerReady(tmp.release(), *this, slice);
     }
   }
 
-  void LayerSourceBase::NotifyLayerError(const SliceGeometry& viewportSlice)
+  void LayerSourceBase::NotifyLayerError(const SliceGeometry& slice)
   {
     if (!started_)
     {
@@ -94,7 +107,7 @@
 
     if (observer_ != NULL)
     {
-      observer_->NotifyLayerError(*this, viewportSlice);
+      observer_->NotifyLayerError(*this, slice);
     }
   }
 
--- a/Framework/Layers/LayerSourceBase.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Wed May 24 21:13:29 2017 +0200
@@ -39,15 +39,17 @@
 
     void NotifyGeometryReady();
     
+    void NotifyGeometryError();
+    
     void NotifySourceChange();
 
-    void NotifySliceChange(const SliceGeometry& slice);
+    void NotifySliceChange(const Slice& slice);
 
     // Takes ownership of "layer" (that cannot be "NULL")
     void NotifyLayerReady(ILayerRenderer* layer,
-                          const SliceGeometry& viewportSlice);
+                          const Slice& slice);
     
-    void NotifyLayerError(const SliceGeometry& viewportSlice);
+    void NotifyLayerError(const SliceGeometry& slice);
 
     virtual void StartInternal() = 0;
 
--- a/Framework/Layers/LineLayerRenderer.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/LineLayerRenderer.cpp	Wed May 24 21:13:29 2017 +0200
@@ -38,7 +38,8 @@
 
 
   bool LineLayerRenderer::RenderLayer(CairoContext& context,
-                                      const ViewportGeometry& view)
+                                      const ViewportGeometry& view,
+                                      const SliceGeometry& slice)
   {
     if (visible_)
     {
--- a/Framework/Layers/LineLayerRenderer.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/LineLayerRenderer.h	Wed May 24 21:13:29 2017 +0200
@@ -42,7 +42,8 @@
                       double y2);
 
     virtual bool RenderLayer(CairoContext& context,
-                             const ViewportGeometry& view);
+                             const ViewportGeometry& view,
+                             const SliceGeometry& slice);
 
     virtual void SetLayerStyle(const RenderStyle& style);
 
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Wed May 24 21:13:29 2017 +0200
@@ -32,44 +32,63 @@
 
 namespace OrthancStone
 {
-  class OrthancFrameLayerSource::Operation : public Orthanc::IDynamicObject
+  void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader)
   {
-  private:
-    Content        content_;
-    SliceGeometry  viewportSlice_;
-
-  public:
-    Operation(Content content) : content_(content)
+    if (loader.GetSliceCount() > 0)
     {
+      // Make sure all the slices are parallel. TODO Alleviate this constraint
+      for (size_t i = 1; i < loader.GetSliceCount(); i++)
+      {
+        if (!GeometryToolbox::IsParallel(loader.GetSlice(i).GetGeometry().GetNormal(),
+                                         loader.GetSlice(0).GetGeometry().GetNormal()))
+        {
+          LayerSourceBase::NotifyGeometryError();
+          return;
+        }
+      }
     }
 
-    void SetViewportSlice(const SliceGeometry& slice)
-    {
-      viewportSlice_ = slice;
-    }
-
-    const SliceGeometry& GetViewportSlice() const
+    if (observer2_ != NULL)
     {
-      return viewportSlice_;
-    } 
+      ParallelSlices slices;
+
+      for (size_t i = 0; i < loader.GetSliceCount(); i++)
+      {
+        slices.AddSlice(loader.GetSlice(i).GetGeometry());
+      }
+
+      observer2_->NotifySlicesAvailable(slices);
+    }
+      
+    LayerSourceBase::NotifyGeometryReady();
+  }
 
-    Content GetContent() const
-    {
-      return content_;
-    }
-  };
-    
+  void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader)
+  {
+    LayerSourceBase::NotifyGeometryError();
+  }
+
+  void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                                      unsigned int sliceIndex,
+                                                      const Slice& slice,
+                                                      Orthanc::ImageAccessor* image)
+  {
+    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image, slice, true), slice);
+  }
+
+  void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                                      unsigned int sliceIndex,
+                                                      const Slice& slice)
+  {
+    LayerSourceBase::NotifyLayerError(slice.GetGeometry());
+  }
 
   OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc,
                                                    const std::string& instanceId,
                                                    unsigned int frame) :
-    orthanc_(orthanc),
     instanceId_(instanceId),
     frame_(frame),
-    frameWidth_(0),
-    frameHeight_(0),
-    pixelSpacingX_(1),
-    pixelSpacingY_(1),
+    loader_(*this, orthanc),
     observer2_(NULL)
   {
   }
@@ -77,23 +96,10 @@
 
   void OrthancFrameLayerSource::StartInternal()
   {
-    orthanc_.ScheduleGetRequest(*this,
-                                "/instances/" + instanceId_ + "/tags",
-                                new Operation(Content_Tags));
+    loader_.ScheduleLoadInstance(instanceId_, frame_);
   }
   
 
-  void OrthancFrameLayerSource::SetObserver(IObserver& observer)
-  {
-    LayerSourceBase::SetObserver(observer);
-
-    if (dataset_.get() != NULL)
-    {
-      NotifySourceChange();
-    }
-  }
-
-
   void OrthancFrameLayerSource::SetObserver(IVolumeSlicesObserver& observer)
   {
     if (IsStarted())
@@ -112,96 +118,6 @@
     }
   }
 
-
-  void OrthancFrameLayerSource::NotifyError(const std::string& uri,
-                                            Orthanc::IDynamicObject* payload)
-  {
-    std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload));
-
-    LOG(ERROR) << "Cannot download " << uri;
-    NotifyLayerError(operation->GetViewportSlice());
-  }
-  
-
-  void OrthancFrameLayerSource::NotifySuccess(const std::string& uri,
-                                              const void* answer,
-                                              size_t answerSize,
-                                              Orthanc::IDynamicObject* payload)
-  {
-    std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload));
-
-    if (operation.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else if (operation->GetContent() == Content_Tags)
-    {
-      dataset_.reset(new OrthancPlugins::FullOrthancDataset(answer, answerSize));
-
-      DicomFrameConverter converter;
-      converter.ReadParameters(*dataset_);
-      format_ = converter.GetExpectedPixelFormat();
-      GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, *dataset_);
-
-      OrthancPlugins::DicomDatasetReader reader(*dataset_);
-      if (!reader.GetUnsignedIntegerValue(frameWidth_, OrthancPlugins::DICOM_TAG_COLUMNS) ||
-          !reader.GetUnsignedIntegerValue(frameHeight_, OrthancPlugins::DICOM_TAG_ROWS))
-      {
-        frameWidth_ = 0;
-        frameHeight_ = 0;
-        LOG(WARNING) << "Missing tags in a DICOM image: Columns or Rows";
-      }
-
-      if (observer2_ != NULL)
-      {
-        ParallelSlices slices;
-        slices.AddSlice(SliceGeometry(*dataset_));
-        observer2_->NotifySlicesAvailable(slices);
-      }
-      
-      NotifyGeometryReady();
-    }
-    else if (operation->GetContent() == Content_Frame)
-    {
-      std::auto_ptr<Orthanc::PngReader>  image(new Orthanc::PngReader);
-      image->ReadFromMemory(answer, answerSize);
-
-      bool ok = (image->GetWidth() == frameWidth_ ||
-                 image->GetHeight() == frameHeight_);
-      
-      if (ok &&
-          format_ == Orthanc::PixelFormat_SignedGrayscale16)
-      {
-        if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-        {
-          image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-        }
-        else
-        {
-          ok = false;
-        }
-      }
-
-      if (ok)
-      {
-        SliceGeometry frameSlice(*dataset_);
-        NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(),
-                                                       operation->GetViewportSlice(),
-                                                       frameSlice, *dataset_,
-                                                       pixelSpacingX_, pixelSpacingY_, true),
-                         operation->GetViewportSlice());
-      }
-      else
-      {
-        NotifyLayerError(operation->GetViewportSlice());
-      }
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
   
   bool OrthancFrameLayerSource::GetExtent(double& x1,
                                           double& y1,
@@ -209,61 +125,57 @@
                                           double& y2,
                                           const SliceGeometry& viewportSlice /* ignored */)
   {
-    if (!IsStarted() ||
-        dataset_.get() == NULL)
-    {
-      return false;
-    }
-    else
+    bool ok = false;
+
+    if (IsStarted() &&
+        loader_.IsGeometryReady())
     {
-      SliceGeometry frameSlice(*dataset_);
-      return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2,
-                                               viewportSlice, frameSlice,
-                                               frameWidth_, frameHeight_,
-                                               pixelSpacingX_, pixelSpacingY_);
+      double tx1, ty1, tx2, ty2;
+
+      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;
+          }
+        }
+      }
     }
+
+    return ok;
   }
 
   
   void OrthancFrameLayerSource::ScheduleLayerCreation(const SliceGeometry& viewportSlice)
   {
-    if (!IsStarted())
+    size_t index;
+
+    if (!IsStarted() ||
+        !loader_.IsGeometryReady())
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-    
-    if (dataset_.get() == NULL)
+    else if (loader_.LookupSlice(index, viewportSlice))
     {
-      NotifyLayerError(viewportSlice);
+      loader_.ScheduleLoadSliceImage(index);
     }
     else
     {
-      std::string uri = ("/instances/" + instanceId_ + "/frames/" + 
-                         boost::lexical_cast<std::string>(frame_));
-
-      std::string compressed;
-
-      switch (format_)
-      {
-        case Orthanc::PixelFormat_RGB24:
-          uri += "/preview";
-          break;
-
-        case Orthanc::PixelFormat_Grayscale16:
-          uri += "/image-uint16";
-          break;
-
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          uri += "/image-int16";
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      std::auto_ptr<Operation> operation(new Operation(Content_Frame));
-      operation->SetViewportSlice(viewportSlice);
-      orthanc_.ScheduleGetRequest(*this, uri, operation.release());
+      LayerSourceBase::NotifyLayerError(viewportSlice);
     }
   }
 }
--- a/Framework/Layers/OrthancFrameLayerSource.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Wed May 24 21:13:29 2017 +0200
@@ -24,54 +24,45 @@
 #include "LayerSourceBase.h"
 #include "../Toolbox/IWebService.h"
 #include "../Toolbox/IVolumeSlicesObserver.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
+#include "../Toolbox/OrthancSlicesLoader.h"
 
 namespace OrthancStone
 {  
   class OrthancFrameLayerSource :
     public LayerSourceBase,
-    public IWebService::ICallback   // TODO move this into a PImpl
+    private OrthancSlicesLoader::ICallback
   {
   private:
-    enum Content
-    {
-      Content_Tags,
-      Content_Frame
-    };
+    std::string             instanceId_;
+    unsigned int            frame_;
+    OrthancSlicesLoader     loader_;
+    IVolumeSlicesObserver*  observer2_;
+
+    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader);
+
+    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader);
 
-    class Operation;
-    
-    IWebService&                                      orthanc_;
-    std::string                                       instanceId_;
-    unsigned int                                      frame_;
-    std::auto_ptr<OrthancPlugins::FullOrthancDataset> dataset_;
-    unsigned int                                      frameWidth_;
-    unsigned int                                      frameHeight_;
-    Orthanc::PixelFormat                              format_;
-    double                                            pixelSpacingX_;
-    double                                            pixelSpacingY_;
-    IVolumeSlicesObserver*                            observer2_;
+    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       Orthanc::ImageAccessor* image);
+
+    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice);
 
   protected:
     virtual void StartInternal();
 
   public:
+    using LayerSourceBase::SetObserver;
+
     OrthancFrameLayerSource(IWebService& orthanc,
                             const std::string& instanceId,
                             unsigned int frame);
 
-    virtual void SetObserver(IObserver& observer);
-
     void SetObserver(IVolumeSlicesObserver& observer);
 
-    virtual void NotifyError(const std::string& uri,
-                             Orthanc::IDynamicObject* payload);
-
-    virtual void NotifySuccess(const std::string& uri,
-                               const void* answer,
-                               size_t answerSize,
-                               Orthanc::IDynamicObject* payload);
-
     virtual bool GetExtent(double& x1,
                            double& y1,
                            double& x2,
--- a/Framework/Layers/SeriesFrameRendererFactory.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/SeriesFrameRendererFactory.cpp	Wed May 24 21:13:29 2017 +0200
@@ -155,7 +155,6 @@
     {
       SliceGeometry frameSlice(*currentDataset_);
       return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), 
-                                           viewportSlice, 
                                            frameSlice,
                                            *currentDataset_, 
                                            spacingX, spacingY,
--- a/Framework/Layers/SingleFrameRendererFactory.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Layers/SingleFrameRendererFactory.cpp	Wed May 24 21:13:29 2017 +0200
@@ -76,7 +76,7 @@
   {
     SliceGeometry frameSlice(*dicom_);
     return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), 
-                                         viewportSlice, frameSlice, *dicom_, 1, 1, true);
+                                         frameSlice, *dicom_, 1, 1, true);
   }
 
 
--- a/Framework/Toolbox/OrthancAsynchronousWebService.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/OrthancAsynchronousWebService.cpp	Wed May 24 21:13:29 2017 +0200
@@ -90,6 +90,7 @@
       catch (Orthanc::OrthancException&)
       {
         callback_.NotifyError(uri_, payload_.release());
+        return;
       }
 
       callback_.NotifySuccess(uri_, answer.c_str(), answer.size(), payload_.release());
@@ -220,14 +221,15 @@
       
       for (size_t i = 0; i < threads_.size(); i++)
       {
-        assert(threads_[i] != NULL);
-
-        if (threads_[i]->joinable())
+        if (threads_[i] != NULL)
         {
-          threads_[i]->join();
+          if (threads_[i]->joinable())
+          {
+            threads_[i]->join();
+          }
+
+          delete threads_[i];
         }
-
-        delete threads_[i];
       }
     }
   };
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Wed May 24 21:13:29 2017 +0200
@@ -335,6 +335,12 @@
   }
   
 
+  bool OrthancSlicesLoader::IsGeometryReady() const
+  {
+    return state_ == State_GeometryReady;
+  }
+
+
   size_t OrthancSlicesLoader::GetSliceCount() const
   {
     if (state_ != State_GeometryReady)
@@ -357,6 +363,18 @@
   }
   
 
+  bool OrthancSlicesLoader::LookupSlice(size_t& index,
+                                        const SliceGeometry& plane) const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return slices_.LookupSlice(index, plane);
+  }
+  
+
   void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index)
   {
     if (state_ != State_GeometryReady)
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Wed May 24 21:13:29 2017 +0200
@@ -101,10 +101,15 @@
     void ScheduleLoadInstance(const std::string& instanceId,
                               unsigned int frame);
 
+    bool IsGeometryReady() const;
+
     size_t GetSliceCount() const;
 
     const Slice& GetSlice(size_t index) const;
 
+    bool LookupSlice(size_t& index,
+                     const SliceGeometry& plane) const;
+
     void ScheduleLoadSliceImage(size_t index);
   };
 }
--- a/Framework/Toolbox/Slice.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/Slice.h	Wed May 24 21:13:29 2017 +0200
@@ -78,5 +78,10 @@
     unsigned int GetHeight() const;
 
     const DicomFrameConverter& GetConverter() const;
+
+    bool ContainsPlane(const SliceGeometry& plane) const
+    {
+      return geometry_.IsSamePlane(plane, thickness_);
+    }
   };
 }
--- a/Framework/Toolbox/SliceGeometry.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/SliceGeometry.h	Wed May 24 21:13:29 2017 +0200
@@ -89,7 +89,7 @@
                       double& offsetY,
                       const Vector& point) const;
 
-    bool IsSamePlane(const SliceGeometry& other,
+    bool IsSamePlane(const SliceGeometry& slice,
                      double sliceThickness) const;
   };
 }
--- a/Framework/Toolbox/SlicesSorter.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/SlicesSorter.cpp	Wed May 24 21:13:29 2017 +0200
@@ -192,4 +192,23 @@
 
     return found;
   }
+
+
+  bool SlicesSorter::LookupSlice(size_t& index,
+                                 const SliceGeometry& slice) const
+  {
+    // TODO Turn this linear-time lookup into a log-time lookup,
+    // keeping track of whether the slices are sorted along the normal
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      if (slices_[i]->GetSlice().ContainsPlane(slice))
+      {
+        index = i;
+        return true;
+      }
+    }
+
+    return false;
+  }
 }
--- a/Framework/Toolbox/SlicesSorter.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Toolbox/SlicesSorter.h	Wed May 24 21:13:29 2017 +0200
@@ -64,5 +64,8 @@
     void FilterNormal(const Vector& normal);
     
     bool SelectNormal(Vector& normal) const;
+
+    bool LookupSlice(size_t& index,
+                     const SliceGeometry& slice) const;
   };
 }
--- a/Framework/Volumes/VolumeImage.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Volumes/VolumeImage.cpp	Wed May 24 21:13:29 2017 +0200
@@ -387,7 +387,6 @@
       }
 
       return FrameRenderer::CreateRenderer(frame.release(), 
-                                           viewportSlice, 
                                            frameSlice, 
                                            *that_.referenceDataset_, 
                                            spacing[0], spacing[1],
--- a/Framework/Widgets/LayerWidget.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Wed May 24 21:13:29 2017 +0200
@@ -105,7 +105,7 @@
       for (size_t i = 0; i < renderers_.size(); i++)
       {
         if (renderers_[i] != NULL &&
-            !renderers_[i]->RenderLayer(context, view))
+            !renderers_[i]->RenderLayer(context, view, slice_))
         {
           return false;
         }
@@ -257,7 +257,7 @@
 
   void LayerWidget::UpdateLayer(size_t index,
                                 ILayerRenderer* renderer,
-                                const SliceGeometry& slice)
+                                const Slice& slice)
   {
     LOG(INFO) << "Updating layer " << index;
     
@@ -277,13 +277,13 @@
     renderer->SetLayerStyle(styles_[index]);
 
     if (currentScene_.get() != NULL &&
-        currentScene_->IsSamePlane(slice, sliceThickness_))
+        currentScene_->IsSamePlane(slice.GetGeometry(), sliceThickness_))
     {
       currentScene_->SetLayer(index, tmp.release());
       NotifyChange();
     }
     else if (pendingScene_.get() != NULL &&
-             pendingScene_->IsSamePlane(slice, sliceThickness_))
+             pendingScene_->IsSamePlane(slice.GetGeometry(), sliceThickness_))
     {
       pendingScene_->SetLayer(index, tmp.release());
 
@@ -397,6 +397,12 @@
   }
   
 
+  void LayerWidget::NotifyGeometryError(ILayerSource& source)
+  {
+    LOG(ERROR) << "Cannot get geometry";
+  }
+  
+
   void LayerWidget::NotifySourceChange(ILayerSource& source)
   {
     source.ScheduleLayerCreation(slice_);
@@ -404,9 +410,9 @@
   
 
   void LayerWidget::NotifySliceChange(ILayerSource& source,
-                                      const SliceGeometry& slice)
+                                      const Slice& slice)
   {
-    if (slice_.IsSamePlane(slice, sliceThickness_))
+    if (slice.ContainsPlane(slice_))
     {
       source.ScheduleLayerCreation(slice_);
     }
@@ -415,18 +421,15 @@
 
   void LayerWidget::NotifyLayerReady(ILayerRenderer* renderer,
                                      ILayerSource& source,
-                                     const SliceGeometry& viewportSlice)
+                                     const Slice& slice)
   {
     std::auto_ptr<ILayerRenderer> tmp(renderer);
 
-    size_t i;
-    if (LookupLayer(i, source))
-      LOG(INFO) << "Renderer ready for layer " << i;
-
     size_t index;
     if (LookupLayer(index, source))
     {
-      UpdateLayer(index, tmp.release(), viewportSlice);
+      LOG(INFO) << "Renderer ready for layer " << index;
+      UpdateLayer(index, tmp.release(), slice);
     }
   }
 
--- a/Framework/Widgets/LayerWidget.h	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Widgets/LayerWidget.h	Wed May 24 21:13:29 2017 +0200
@@ -30,7 +30,7 @@
 {
   class LayerWidget :
     public WorldSceneWidget,
-    public ILayerSource::IObserver   // TODO move this as PImpl
+    private ILayerSource::IObserver
   {
   private:
     class Scene;
@@ -50,6 +50,23 @@
     bool LookupLayer(size_t& index /* out */,
                      ILayerSource& layer) const;
     
+
+    virtual void NotifyGeometryReady(ILayerSource& source);
+
+    virtual void NotifyGeometryError(ILayerSource& source);
+
+    virtual void NotifySourceChange(ILayerSource& source);
+
+    virtual void NotifySliceChange(ILayerSource& source,
+                                   const Slice& slice);
+
+    virtual void NotifyLayerReady(ILayerRenderer* renderer,
+                                  ILayerSource& source,
+                                  const Slice& slice);
+
+    virtual void NotifyLayerError(ILayerSource& source,
+                                  const SliceGeometry& slice);
+
         
   protected:
     virtual void GetSceneExtent(double& x1,
@@ -64,7 +81,7 @@
 
     void UpdateLayer(size_t index,
                      ILayerRenderer* renderer,
-                     const SliceGeometry& slice);
+                     const Slice& slice);
     
   public:
     LayerWidget();
@@ -79,20 +96,6 @@
     void SetSlice(const SliceGeometry& slice,
                   double sliceThickness);
 
-    virtual void NotifyGeometryReady(ILayerSource& source);
-
-    virtual void NotifySourceChange(ILayerSource& source);
-
-    virtual void NotifySliceChange(ILayerSource& source,
-                                   const SliceGeometry& slice);
-
-    virtual void NotifyLayerReady(ILayerRenderer* renderer,
-                                  ILayerSource& source,
-                                  const SliceGeometry& viewportSlice);
-
-    virtual void NotifyLayerError(ILayerSource& source,
-                                  const SliceGeometry& viewportSlice);
-
     virtual void Start();
   };
 }
--- a/Framework/Widgets/LayeredSceneWidget.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/Framework/Widgets/LayeredSceneWidget.cpp	Wed May 24 21:13:29 2017 +0200
@@ -100,7 +100,8 @@
     }
 
     bool RenderScene(CairoContext& context,
-                     const ViewportGeometry& view)
+                     const ViewportGeometry& view,
+                     const SliceGeometry& slice)
     {
       boost::mutex::scoped_lock lock(mutex_);
 
@@ -109,7 +110,7 @@
       for (size_t i = 0; i < renderers_.size(); i++)
       {
         if (renderers_[i] != NULL &&
-            !renderers_[i]->RenderLayer(context, view))
+            !renderers_[i]->RenderLayer(context, view, slice))
         {
           return false;
         }
@@ -395,7 +396,7 @@
   bool LayeredSceneWidget::RenderScene(CairoContext& context,
                                        const ViewportGeometry& view) 
   {
-    return renderers_->RenderScene(context, view);
+    return renderers_->RenderScene(context, view, slice_);
   }
 
 
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed May 24 12:42:08 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed May 24 21:13:29 2017 +0200
@@ -77,9 +77,9 @@
   OrthancStone::Tata tata;
   OrthancStone::OrthancSlicesLoader loader(tata, orthanc);
   //loader.ScheduleLoadSeries("c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5");
-  loader.ScheduleLoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");
+  //loader.ScheduleLoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");
 
-  //loader.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
+  loader.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
 
   /*printf(">> %d\n", loader.GetSliceCount());
     loader.ScheduleLoadSliceImage(31);*/