changeset 66:298f375dcb68 wasm

LayerWidget
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 17 May 2017 22:03:09 +0200
parents 885932a893de
children acb60cbb8301
files Applications/BasicApplicationContext.cpp Applications/IBasicApplication.cpp Applications/Samples/SingleFrameApplication.h Framework/Layers/FrameRenderer.cpp Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/LayerSourceBase.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Toolbox/GeometryToolbox.h Framework/Toolbox/IVolumeSlicesObserver.h Framework/Toolbox/SliceGeometry.cpp Framework/Toolbox/SliceGeometry.h Framework/Viewport/IViewport.h Framework/Viewport/WidgetViewport.cpp Framework/Viewport/WidgetViewport.h Framework/Widgets/EmptyWidget.cpp Framework/Widgets/EmptyWidget.h Framework/Widgets/IWidget.h Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Framework/Widgets/WidgetBase.h Framework/Widgets/WorldSceneWidget.cpp Resources/CMake/OrthancStone.cmake UnitTestsSources/UnitTestsMain.cpp
diffstat 25 files changed, 1090 insertions(+), 141 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/BasicApplicationContext.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Applications/BasicApplicationContext.cpp	Wed May 17 22:03:09 2017 +0200
@@ -141,6 +141,8 @@
       stopped_ = false;
       updateThread_ = boost::thread(UpdateThread, this);
     }
+
+    viewport_.Start();
   }
 
 
--- a/Applications/IBasicApplication.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Applications/IBasicApplication.cpp	Wed May 17 22:03:09 2017 +0200
@@ -210,6 +210,8 @@
        * Initialize the application
        ****************************************************************/
 
+      LOG(WARNING) << "Creating the widgets of the application";
+
       LogStatusBar statusBar;
       BasicApplicationContext context(orthanc);
 
--- a/Applications/Samples/SingleFrameApplication.h	Tue May 16 22:12:41 2017 +0200
+++ b/Applications/Samples/SingleFrameApplication.h	Wed May 17 22:03:09 2017 +0200
@@ -23,17 +23,45 @@
 
 #include "SampleApplicationBase.h"
 
-#include "../../Framework/Layers/SingleFrameRendererFactory.h"
-#include "../../Framework/Widgets/LayeredSceneWidget.h"
+#define OLD 0
+
+#if OLD == 1
+#  include "../../Framework/Layers/SingleFrameRendererFactory.h"
+#  include "../../Framework/Widgets/LayeredSceneWidget.h"
+#else
+#  include "../../Framework/Layers/OrthancFrameLayerSource.h"
+#  include "../../Framework/Widgets/LayerWidget.h"
+#endif
+
 #include "../../Resources/Orthanc/Core/Logging.h"
 
 namespace OrthancStone
 {
   namespace Samples
   {
-    class SingleFrameApplication : public SampleApplicationBase
+    class SingleFrameApplication :
+      public SampleApplicationBase,
+      public IVolumeSlicesObserver
     {
+    private:
+      LayerWidget* widget_;
+      
     public:
+      SingleFrameApplication() : widget_(NULL)
+      {
+      }
+      
+      virtual void NotifySlicesAvailable(const ParallelSlices& slices)
+      {
+        printf("ICI\n");
+        if (widget_ != NULL &&
+            slices.GetSliceCount() > 0)
+        {
+          printf("GO\n");
+          widget_->SetSlice(slices.GetSlice(0), 1.0 /* TODO */);
+        }
+      }
+      
       virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
       {
         boost::program_options::options_description generic("Sample options");
@@ -64,6 +92,7 @@
         std::string instance = parameters["instance"].as<std::string>();
         int frame = parameters["frame"].as<unsigned int>();
 
+#if OLD == 1
         std::auto_ptr<SingleFrameRendererFactory>  renderer;
         renderer.reset
           (new SingleFrameRendererFactory(context.GetWebService().GetConnection(), instance, frame));
@@ -80,6 +109,45 @@
         }
 
         context.SetCentralWidget(widget.release());
+
+#else
+        std::auto_ptr<LayerWidget> widget(new LayerWidget);
+
+#if 0
+        std::auto_ptr<OrthancFrameLayerSource> layer
+          (new OrthancFrameLayerSource(context.GetWebService(), instance, frame));
+        layer->SetObserver(*this);
+        widget->AddLayer(layer.release());
+
+        if (parameters["smooth"].as<bool>())
+        {
+          RenderStyle s; 
+          s.interpolation_ = ImageInterpolation_Linear;
+          widget->SetLayerStyle(0, s);
+        }
+#else
+        // 0178023P**
+        std::auto_ptr<OrthancFrameLayerSource> layer;
+        layer.reset(new OrthancFrameLayerSource(context.GetWebService(), "c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0));
+        //layer.reset(new OrthancFrameLayerSource(context.GetWebService(), "4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0));  // BAD SLICE
+        layer->SetObserver(*this);
+        widget->AddLayer(layer.release());
+
+        widget->AddLayer(new OrthancFrameLayerSource(context.GetWebService(), "a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0));
+
+        RenderStyle s;
+        //s.drawGrid_ = true;
+        s.alpha_ = 1;
+        widget->SetLayerStyle(0, s);
+        s.alpha_ = 0.5;
+        s.applyLut_ = true;
+        s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
+        widget->SetLayerStyle(1, s);
+#endif
+      
+        widget_ = widget.get();
+        context.SetCentralWidget(widget.release());
+#endif
       }
     };
   }
--- a/Framework/Layers/FrameRenderer.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Layers/FrameRenderer.cpp	Wed May 17 22:03:09 2017 +0200
@@ -35,7 +35,9 @@
                                     double pixelSpacingY)
   {
     bool isOpposite;
-    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal()))
+    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                               viewportSlice.GetNormal(),
+                                               frameSlice.GetNormal()))
     {
       return false;
     }
@@ -145,7 +147,8 @@
 
     if (display_.get() == NULL)
     {
-      if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_, pixelSpacingX_, pixelSpacingY_))
+      if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_,
+                                 pixelSpacingX_, pixelSpacingY_))
       {
         return true;
       }
--- a/Framework/Layers/ILayerSource.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Layers/ILayerSource.h	Wed May 17 22:03:09 2017 +0200
@@ -36,6 +36,10 @@
       {
       }
 
+      // Triggered as soon as the source has enough information to
+      // answer to "GetExtent()"
+      virtual void NotifyGeometryReady(ILayerSource& source) = 0;
+      
       // Triggered if the extent or the content of the volume has changed
       virtual void NotifySourceChange(ILayerSource& source) = 0;
 
@@ -67,5 +71,7 @@
                            const SliceGeometry& viewportSlice) = 0;
 
     virtual void ScheduleLayerCreation(const SliceGeometry& viewportSlice) = 0;
+
+    virtual void Start() = 0;
   };
 }
--- a/Framework/Layers/LayerSourceBase.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Wed May 17 22:03:09 2017 +0200
@@ -25,8 +25,26 @@
 
 namespace OrthancStone
 {
+  void LayerSourceBase::NotifyGeometryReady()
+  {
+    if (!started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (observer_ != NULL)
+    {
+      observer_->NotifyGeometryReady(*this);
+    }
+  }  
+    
   void LayerSourceBase::NotifySourceChange()
   {
+    if (!started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
     if (observer_ != NULL)
     {
       observer_->NotifySourceChange(*this);
@@ -35,6 +53,11 @@
 
   void LayerSourceBase::NotifySliceChange(const SliceGeometry& slice)
   {
+    if (!started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
     if (observer_ != NULL)
     {
       observer_->NotifySliceChange(*this, slice);
@@ -42,9 +65,15 @@
   }
 
   void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
-                                         ILayerSource& source,
                                          const SliceGeometry& viewportSlice)
   {
+    std::auto_ptr<ILayerRenderer> tmp(layer);
+    
+    if (!started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
     if (layer == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
@@ -52,17 +81,17 @@
     
     if (observer_ != NULL)
     {
-      observer_->NotifyLayerReady(layer, *this, viewportSlice);
-    }
-    else
-    {
-      delete layer;
+      observer_->NotifyLayerReady(tmp.release(), *this, viewportSlice);
     }
   }
 
-  void LayerSourceBase::NotifyLayerError(ILayerSource& source,
-                                         const SliceGeometry& viewportSlice)
+  void LayerSourceBase::NotifyLayerError(const SliceGeometry& viewportSlice)
   {
+    if (!started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
     if (observer_ != NULL)
     {
       observer_->NotifyLayerError(*this, viewportSlice);
@@ -71,6 +100,32 @@
 
   void LayerSourceBase::SetObserver(IObserver& observer)
   {
-    observer_ = &observer;
+    if (started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (observer_ == NULL)
+    {
+      observer_ = &observer;
+    }
+    else
+    {
+      // Cannot add more than one observer
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  void LayerSourceBase::Start()
+  {
+    if (started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      started_ = true;
+      StartInternal();
+    }
   }
 }
--- a/Framework/Layers/LayerSourceBase.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Wed May 17 22:03:09 2017 +0200
@@ -29,25 +29,37 @@
   {
   private:
     IObserver*  observer_;
+    bool        started_;
 
   protected:
+    bool IsStarted()
+    {
+      return started_;
+    }
+
+    void NotifyGeometryReady();
+    
     void NotifySourceChange();
 
     void NotifySliceChange(const SliceGeometry& slice);
 
     // Takes ownership of "layer" (that cannot be "NULL")
     void NotifyLayerReady(ILayerRenderer* layer,
-                          ILayerSource& source,
                           const SliceGeometry& viewportSlice);
     
-    void NotifyLayerError(ILayerSource& source,
-                          const SliceGeometry& viewportSlice);
+    void NotifyLayerError(const SliceGeometry& viewportSlice);
+
+    virtual void StartInternal() = 0;
 
   public:
-    LayerSourceBase() : observer_(NULL)
+    LayerSourceBase() :
+      observer_(NULL),
+      started_(false)
     {
     }
     
     virtual void SetObserver(IObserver& observer);
+
+    virtual void Start();
   };
 }
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Wed May 17 22:03:09 2017 +0200
@@ -65,14 +65,24 @@
                                                    unsigned int frame) :
     orthanc_(orthanc),
     instanceId_(instanceId),
-    frame_(frame)
+    frame_(frame),
+    frameWidth_(0),
+    frameHeight_(0),
+    pixelSpacingX_(1),
+    pixelSpacingY_(1),
+    observer2_(NULL)
+  {
+  }
+
+
+  void OrthancFrameLayerSource::StartInternal()
   {
     orthanc_.ScheduleGetRequest(*this,
-                                "/instances/" + instanceId + "/tags",
+                                "/instances/" + instanceId_ + "/tags",
                                 new Operation(Content_Tags));
   }
+  
 
-  
   void OrthancFrameLayerSource::SetObserver(IObserver& observer)
   {
     LayerSourceBase::SetObserver(observer);
@@ -83,11 +93,35 @@
     }
   }
 
+
+  void OrthancFrameLayerSource::SetObserver(IVolumeSlicesObserver& observer)
+  {
+    if (IsStarted())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    if (observer2_ == NULL)
+    {
+      observer2_ = &observer;
+    }
+    else
+    {
+      // Cannot add more than one observer
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
   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,
@@ -107,15 +141,36 @@
       DicomFrameConverter converter;
       converter.ReadParameters(*dataset_);
       format_ = converter.GetExpectedPixelFormat();
+      GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, *dataset_);
 
-      NotifySourceChange();
+      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);
-        
-      if (format_ == Orthanc::PixelFormat_SignedGrayscale16)
+
+      bool ok = (image->GetWidth() == frameWidth_ ||
+                 image->GetHeight() == frameHeight_);
+      
+      if (ok &&
+          format_ == Orthanc::PixelFormat_SignedGrayscale16)
       {
         if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
         {
@@ -123,15 +178,23 @@
         }
         else
         {
-          NotifyLayerReady(NULL, *this, operation->GetViewportSlice());
+          ok = false;
         }
       }
-        
-      SliceGeometry frameSlice(*dataset_);
-      NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(),
-                                                     operation->GetViewportSlice(),
-                                                     frameSlice, *dataset_, 1, 1, true),
-                       *this, operation->GetViewportSlice());
+
+      if (ok)
+      {
+        SliceGeometry frameSlice(*dataset_);
+        NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(),
+                                                       operation->GetViewportSlice(),
+                                                       frameSlice, *dataset_,
+                                                       pixelSpacingX_, pixelSpacingY_, true),
+                         operation->GetViewportSlice());
+      }
+      else
+      {
+        NotifyLayerError(operation->GetViewportSlice());
+      }
     }
     else
     {
@@ -146,39 +209,32 @@
                                           double& y2,
                                           const SliceGeometry& viewportSlice /* ignored */)
   {
-    if (dataset_.get() == NULL)
+    if (!IsStarted() ||
+        dataset_.get() == NULL)
     {
       return false;
     }
     else
     {
-      // Assume that PixelSpacingX == PixelSpacingY == 1
-
-      OrthancPlugins::DicomDatasetReader reader(*dataset_);
-    
-      unsigned int width, height;
-
-      if (!reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) ||
-          !reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      x1 = 0;
-      y1 = 0;
-      x2 = static_cast<double>(width);
-      y2 = static_cast<double>(height);
-
-      return true;
+      SliceGeometry frameSlice(*dataset_);
+      return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2,
+                                               viewportSlice, frameSlice,
+                                               frameWidth_, frameHeight_,
+                                               pixelSpacingX_, pixelSpacingY_);
     }
   }
 
   
   void OrthancFrameLayerSource::ScheduleLayerCreation(const SliceGeometry& viewportSlice)
   {
+    if (!IsStarted())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
     if (dataset_.get() == NULL)
     {
-      NotifyLayerReady(NULL, *this, viewportSlice);
+      NotifyLayerError(viewportSlice);
     }
     else
     {
--- a/Framework/Layers/OrthancFrameLayerSource.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Wed May 17 22:03:09 2017 +0200
@@ -23,13 +23,14 @@
 
 #include "LayerSourceBase.h"
 #include "../Toolbox/IWebService.h"
+#include "../Toolbox/IVolumeSlicesObserver.h"
 #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
 
 namespace OrthancStone
-{
+{  
   class OrthancFrameLayerSource :
     public LayerSourceBase,
-    public IWebService::ICallback
+    public IWebService::ICallback   // TODO move this into a PImpl
   {
   private:
     enum Content
@@ -44,7 +45,15 @@
     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_;
+
+  protected:
+    virtual void StartInternal();
 
   public:
     OrthancFrameLayerSource(IWebService& orthanc,
@@ -53,6 +62,8 @@
 
     virtual void SetObserver(IObserver& observer);
 
+    void SetObserver(IVolumeSlicesObserver& observer);
+
     virtual void NotifyError(const std::string& uri,
                              Orthanc::IDynamicObject* payload);
 
--- a/Framework/Toolbox/GeometryToolbox.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Toolbox/GeometryToolbox.h	Wed May 17 22:03:09 2017 +0200
@@ -21,8 +21,9 @@
 
 #pragma once
 
-// Patch for Boost 1.64.0
+// Patch for ublas in Boost 1.64.0
 // https://github.com/dealii/dealii/issues/4302
+#include <boost/version.hpp>
 #if BOOST_VERSION >= 106300  // or 64, need to check
 #  include <boost/serialization/array_wrapper.hpp>
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/IVolumeSlicesObserver.h	Wed May 17 22:03:09 2017 +0200
@@ -0,0 +1,38 @@
+/**
+ * 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
+
+#include "ParallelSlices.h"
+
+namespace OrthancStone
+{
+  class IVolumeSlicesObserver : public boost::noncopyable
+  {
+  public:
+    virtual ~IVolumeSlicesObserver()
+    {
+    }
+
+    virtual void NotifySlicesAvailable(const ParallelSlices& slices) = 0;
+  };
+}
+
--- a/Framework/Toolbox/SliceGeometry.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Toolbox/SliceGeometry.cpp	Wed May 17 22:03:09 2017 +0200
@@ -152,4 +152,14 @@
     offsetX = boost::numeric::ublas::inner_prod(axisX_, projection - origin_);
     offsetY = boost::numeric::ublas::inner_prod(axisY_, projection - origin_);
   }
+
+  
+  bool SliceGeometry::IsSamePlane(const SliceGeometry& other,
+                                  double sliceThickness) const
+  {
+    return (GeometryToolbox::IsParallel(GetNormal(), other.GetNormal()) &&
+            GeometryToolbox::IsNear(ProjectAlongNormal(other.GetOrigin()),
+                                    ProjectAlongNormal(GetOrigin()),
+                                    sliceThickness / 2.0));
+  }
 }
--- a/Framework/Toolbox/SliceGeometry.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Toolbox/SliceGeometry.h	Wed May 17 22:03:09 2017 +0200
@@ -88,5 +88,8 @@
     void ProjectPoint(double& offsetX,
                       double& offsetY,
                       const Vector& point) const;
+
+    bool IsSamePlane(const SliceGeometry& other,
+                     double sliceThickness) const;
   };
 }
--- a/Framework/Viewport/IViewport.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Viewport/IViewport.h	Wed May 17 22:03:09 2017 +0200
@@ -87,5 +87,7 @@
 
     // Should only be called from IWidget
     virtual void NotifyChange(const IWidget& widget) = 0;
+
+    virtual void Start() = 0;
   };
 }
--- a/Framework/Viewport/WidgetViewport.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Viewport/WidgetViewport.cpp	Wed May 17 22:03:09 2017 +0200
@@ -74,6 +74,7 @@
     }
 
     backgroundChanged_ = true;
+    observers_.NotifyChange(this);
 
     return *widget;
   }
@@ -264,4 +265,13 @@
       centralWidget_->UpdateContent();
     }
   }
+
+
+  void WidgetViewport::Start()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->Start();
+    }
+  }
 }
--- a/Framework/Viewport/WidgetViewport.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Viewport/WidgetViewport.h	Wed May 17 22:03:09 2017 +0200
@@ -88,5 +88,7 @@
     virtual bool HasUpdateContent();
 
     virtual void UpdateContent();
+
+    virtual void Start();
   };
 }
--- a/Framework/Widgets/EmptyWidget.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Widgets/EmptyWidget.cpp	Wed May 17 22:03:09 2017 +0200
@@ -26,16 +26,19 @@
 
 namespace OrthancStone
 {
-  bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
+  namespace Samples
   {
-    // Note: This call is slow
-    Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
-    return true;
-  }
+    bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
+    {
+      // Note: This call is slow
+      Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
+      return true;
+    }
 
   
-  void EmptyWidget::UpdateContent()
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    void EmptyWidget::UpdateContent()
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
   }
 }
--- a/Framework/Widgets/EmptyWidget.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Widgets/EmptyWidget.h	Wed May 17 22:03:09 2017 +0200
@@ -25,90 +25,97 @@
 
 namespace OrthancStone
 {
-  /**
-   * This is a test widget that simply fills its surface with an
-   * uniform color.
-   **/
-  class EmptyWidget : public IWidget
+  namespace Samples
   {
-  private:
-    uint8_t  red_;
-    uint8_t  green_;
-    uint8_t  blue_;
-
-  public:
-    EmptyWidget(uint8_t red,
-                uint8_t green,
-                uint8_t blue) :
-      red_(red),
-      green_(green),
-      blue_(blue)
-    {
-    }
-
-    virtual void SetDefaultView()
-    {
-    }
-  
-    virtual void SetParent(OrthancStone::IWidget& widget)
-    {
-    }
-    
-    virtual void SetViewport(IViewport& viewport)
-    {
-    }
-
-    virtual void NotifyChange()
-    {
-    }
-
-    virtual void SetStatusBar(IStatusBar& statusBar)
-    {
-    }
-
-    virtual void SetSize(unsigned int width, 
-                         unsigned int height)
+    /**
+     * This is a test widget that simply fills its surface with an
+     * uniform color.
+     **/
+    class EmptyWidget : public IWidget
     {
-    }
- 
-    virtual bool Render(Orthanc::ImageAccessor& surface);
+    private:
+      uint8_t  red_;
+      uint8_t  green_;
+      uint8_t  blue_;
+
+    public:
+      EmptyWidget(uint8_t red,
+                  uint8_t green,
+                  uint8_t blue) :
+        red_(red),
+        green_(green),
+        blue_(blue)
+      {
+      }
 
-    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
-                                              int x,
-                                              int y,
-                                              KeyboardModifiers modifiers)
-    {
-      return NULL;
-    }
+      virtual void SetDefaultView()
+      {
+      }
+  
+      virtual void SetParent(OrthancStone::IWidget& widget)
+      {
+      }
+    
+      virtual void SetViewport(IViewport& viewport)
+      {
+      }
 
-    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                 int x,
-                                 int y)
-    {
-    }
+      virtual void NotifyChange()
+      {
+      }
+
+      virtual void SetStatusBar(IStatusBar& statusBar)
+      {
+      }
+
+      virtual void SetSize(unsigned int width, 
+                           unsigned int height)
+      {
+      }
+ 
+      virtual bool Render(Orthanc::ImageAccessor& surface);
 
-    virtual void MouseWheel(MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            KeyboardModifiers modifiers)
-    {
-    }
+      virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                                int x,
+                                                int y,
+                                                KeyboardModifiers modifiers)
+      {
+        return NULL;
+      }
 
-    virtual void KeyPressed(char key,
-                            KeyboardModifiers modifiers)
-    {
-    }
+      virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                   int x,
+                                   int y)
+      {
+      }
+
+      virtual void MouseWheel(MouseWheelDirection direction,
+                              int x,
+                              int y,
+                              KeyboardModifiers modifiers)
+      {
+      }
 
-    virtual bool HasUpdateContent() const
-    {
-      return false;
-    }
+      virtual void KeyPressed(char key,
+                              KeyboardModifiers modifiers)
+      {
+      }
 
-    virtual void UpdateContent();
+      virtual bool HasUpdateContent() const
+      {
+        return false;
+      }
+
+      virtual void UpdateContent();
 
-    virtual bool HasRenderMouseOver()
-    {
-      return false;
-    }
-  };
+      virtual bool HasRenderMouseOver()
+      {
+        return false;
+      }
+
+      virtual void Start()
+      {
+      }
+    };
+  }
 }
--- a/Framework/Widgets/IWidget.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Widgets/IWidget.h	Wed May 17 22:03:09 2017 +0200
@@ -74,5 +74,7 @@
     // Subclasses can call this method to signal the display of the
     // widget must be refreshed
     virtual void NotifyChange() = 0;
+
+    virtual void Start() = 0;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayerWidget.cpp	Wed May 17 22:03:09 2017 +0200
@@ -0,0 +1,457 @@
+/**
+ * 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 "LayerWidget.h"
+
+#include "../../Resources/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  class LayerWidget::Scene : public boost::noncopyable
+  {
+  private:
+    SliceGeometry                 slice_;
+    size_t                        countMissing_;
+    std::vector<ILayerRenderer*>  renderers_;
+
+    void DeleteLayer(size_t index)
+    {
+      if (index >= renderers_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      assert(countMissing_ <= renderers_.size());
+
+      if (renderers_[index] != NULL)
+      {
+        assert(countMissing_ < renderers_.size());
+        delete renderers_[index];
+        renderers_[index] = NULL;
+        countMissing_++;
+      }
+    }
+      
+  public:
+    Scene(const SliceGeometry& slice,
+          size_t countLayers) :
+      slice_(slice),
+      countMissing_(countLayers),
+      renderers_(countLayers, NULL)
+    {
+    }
+
+    ~Scene()
+    {
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        DeleteLayer(i);
+      }
+    }
+
+    void SetLayer(size_t index,
+                  ILayerRenderer* renderer)  // Takes ownership
+    {
+      if (renderer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+      DeleteLayer(index);
+
+      renderers_[index] = renderer;
+      countMissing_--;
+    }
+
+    const SliceGeometry& GetSlice() const
+    {
+      return slice_;
+    }
+
+    bool IsComplete() const
+    {
+      return countMissing_ == 0;
+    }
+
+    bool IsSamePlane(const SliceGeometry& slice,
+                     double sliceThickness)
+    {
+      return slice_.IsSamePlane(slice, sliceThickness);
+    }
+
+    bool RenderScene(CairoContext& context,
+                     const ViewportGeometry& view)
+    {
+      bool fullQuality = true;
+
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        if (renderers_[i] != NULL &&
+            !renderers_[i]->RenderLayer(context, view))
+        {
+          return false;
+        }
+
+        if (renderers_[i] != NULL &&
+            !renderers_[i]->IsFullQuality())
+        {
+          fullQuality = false;
+        }
+      }
+
+      if (!fullQuality)
+      {
+        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);
+        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI);
+        cairo_set_line_width(cr, 2.0 / view.GetZoom());
+        cairo_set_source_rgb(cr, 1, 1, 1); 
+        cairo_stroke_preserve(cr);
+        cairo_set_source_rgb(cr, 1, 0, 0); 
+        cairo_fill(cr);
+      }
+
+      return true;
+    }
+
+    void SetLayerStyle(size_t index,
+                       const RenderStyle& style)
+    {
+      if (renderers_[index] != NULL)
+      {
+        renderers_[index]->SetLayerStyle(style);
+      }
+    }
+  };
+
+  
+  bool LayerWidget::LookupLayer(size_t& index /* out */,
+                                ILayerSource& layer) const
+  {
+    LayersIndex::const_iterator found = layersIndex_.find(&layer);
+
+    if (found == layersIndex_.end())
+    {
+      return false;
+    }
+    else
+    {
+      index = found->second;
+      assert(index < layers_.size() &&
+             layers_[index] == &layer);
+      return true;
+    }
+  }
+    
+        
+  void LayerWidget::GetSceneExtent(double& x1,
+                                   double& y1,
+                                   double& x2,
+                                   double& y2)
+  {
+    bool first = true;
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      double ax, ay, bx, by;
+
+      assert(layers_[i] != NULL);
+      if (layers_[i]->GetExtent(ax, ay, bx, by, slice_))
+      {
+        if (ax > bx)
+        {
+          std::swap(ax, bx);
+        }
+
+        if (ay > by)
+        {
+          std::swap(ay, by);
+        }
+
+        //LOG(INFO) << "Extent of layer " << i << ": (" << ax << "," << ay << ")->(" << bx << "," << by << ")";
+        printf("Extent %d: (%f,%f) -> (%f,%f)\n", (int) i, ax, ay, bx, by);
+
+        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);
+        }
+      }
+    }
+
+    if (first)
+    {
+      // Set a default extent of (-1,-1) -> (0,0)
+      x1 = -1;
+      y1 = -1;
+      x2 = 1;
+      y2 = 1;
+    }
+
+    // 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;
+    }
+  }
+
+  
+  bool LayerWidget::RenderScene(CairoContext& context,
+                                const ViewportGeometry& view)
+  {
+    if (currentScene_.get() != NULL)
+    {
+      return currentScene_->RenderScene(context, view);
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  
+  void LayerWidget::ResetPendingScene()
+  {
+    pendingScene_.reset(new Scene(slice_, layers_.size()));
+  }
+  
+
+  void LayerWidget::UpdateLayer(size_t index,
+                                ILayerRenderer* renderer,
+                                const SliceGeometry& slice)
+  {
+    printf("Updating layer %d\n", (int) index);
+    
+    std::auto_ptr<ILayerRenderer> tmp(renderer);
+
+    if (renderer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    renderer->SetLayerStyle(styles_[index]);
+
+    if (currentScene_.get() != NULL &&
+        currentScene_->IsSamePlane(slice, sliceThickness_))
+    {
+      currentScene_->SetLayer(index, tmp.release());
+      NotifyChange();
+    }
+    else if (pendingScene_.get() != NULL &&
+             pendingScene_->IsSamePlane(slice, sliceThickness_))
+    {
+      pendingScene_->SetLayer(index, tmp.release());
+
+      if (currentScene_.get() == NULL ||
+          pendingScene_->IsComplete())
+      {
+        currentScene_ = pendingScene_;
+        NotifyChange();
+      }
+    }
+  }
+
+  
+  LayerWidget::LayerWidget() :
+    started_(false),
+    sliceThickness_(1)
+  {
+    SetBackgroundCleared(true);
+  }
+  
+  
+  LayerWidget::~LayerWidget()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      delete layers_[i];
+    }
+  }
+  
+
+  size_t LayerWidget::AddLayer(ILayerSource* layer)  // Takes ownership
+  {
+    if (layer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    size_t index = layers_.size();
+    layers_.push_back(layer);
+    styles_.push_back(RenderStyle());
+    layersIndex_[layer] = index;
+
+    ResetPendingScene();
+    layer->SetObserver(*this);
+
+    return index;
+  }
+
+  
+  void LayerWidget::SetLayerStyle(size_t layer,
+                                  const RenderStyle& style)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    styles_[layer] = style;
+
+    if (currentScene_.get() != NULL)
+    {
+      currentScene_->SetLayerStyle(layer, style);
+    }
+
+    if (pendingScene_.get() != NULL)
+    {
+      pendingScene_->SetLayerStyle(layer, style);
+    }
+
+    NotifyChange();
+  }
+  
+
+  void LayerWidget::SetSlice(const SliceGeometry& slice,
+                             double sliceThickness)
+  {
+    if (!slice_.IsSamePlane(slice, 100.0 * std::numeric_limits<double>::epsilon()))
+    {
+      if (currentScene_.get() == NULL ||
+          (pendingScene_.get() != NULL &&
+           pendingScene_->IsComplete()))
+      {
+        currentScene_ = pendingScene_;
+      }
+        
+      slice_ = slice;
+      sliceThickness_ = sliceThickness;
+      ResetPendingScene();
+
+      if (started_)
+      {
+        for (size_t i = 0; i < layers_.size(); i++)
+        {
+          assert(layers_[i] != NULL);
+          layers_[i]->ScheduleLayerCreation(slice_);
+        }
+      }
+    }
+  }
+
+  
+  void LayerWidget::NotifyGeometryReady(ILayerSource& source)
+  {
+    size_t i;
+    if (LookupLayer(i, source))
+      printf("Geometry ready for layer %d\n", (int) i);
+
+    SetDefaultView();
+    layers_[i]->ScheduleLayerCreation(slice_);
+  }
+  
+
+  void LayerWidget::NotifySourceChange(ILayerSource& source)
+  {
+    source.ScheduleLayerCreation(slice_);
+  }
+  
+
+  void LayerWidget::NotifySliceChange(ILayerSource& source,
+                                      const SliceGeometry& slice)
+  {
+    if (slice_.IsSamePlane(slice, sliceThickness_))
+    {
+      source.ScheduleLayerCreation(slice_);
+    }
+  }
+  
+
+  void LayerWidget::NotifyLayerReady(ILayerRenderer* renderer,
+                                     ILayerSource& source,
+                                     const SliceGeometry& viewportSlice)
+  {
+    std::auto_ptr<ILayerRenderer> tmp(renderer);
+
+    size_t i;
+    if (LookupLayer(i, source))
+      printf("Renderer ready for layer %d\n", (int) i);
+
+    size_t index;
+    if (LookupLayer(index, source))
+    {
+      UpdateLayer(index, tmp.release(), viewportSlice);
+    }
+  }
+
+  
+  void LayerWidget::NotifyLayerError(ILayerSource& source,
+                                     const SliceGeometry& viewportSlice)
+  {
+    size_t i;
+    if (LookupLayer(i, source))
+      LOG(ERROR) << "Error on layer " << i;
+  }    
+
+
+  void LayerWidget::Start()
+  {
+    if (started_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);      
+    }
+    
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      layers_[i]->Start();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayerWidget.h	Wed May 17 22:03:09 2017 +0200
@@ -0,0 +1,98 @@
+/**
+ * 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
+
+#include "WorldSceneWidget.h"
+#include "../Layers/ILayerSource.h"
+
+#include <map>
+
+namespace OrthancStone
+{
+  class LayerWidget :
+    public WorldSceneWidget,
+    public ILayerSource::IObserver   // TODO move this as PImpl
+  {
+  private:
+    class Scene;
+    
+    typedef std::map<ILayerSource*, size_t>  LayersIndex;
+
+    bool                        started_;
+    LayersIndex                 layersIndex_;
+    std::vector<ILayerSource*>  layers_;
+    std::vector<RenderStyle>    styles_;
+    SliceGeometry               slice_;
+    double                      sliceThickness_;
+    std::auto_ptr<Scene>        currentScene_;
+    std::auto_ptr<Scene>        pendingScene_;
+
+
+    bool LookupLayer(size_t& index /* out */,
+                     ILayerSource& layer) const;
+    
+        
+  protected:
+    virtual void GetSceneExtent(double& x1,
+                                double& y1,
+                                double& x2,
+                                double& y2);
+ 
+    virtual bool RenderScene(CairoContext& context,
+                             const ViewportGeometry& view);
+
+    void ResetPendingScene();
+
+    void UpdateLayer(size_t index,
+                     ILayerRenderer* renderer,
+                     const SliceGeometry& slice);
+    
+  public:
+    LayerWidget();
+
+    virtual ~LayerWidget();
+
+    size_t AddLayer(ILayerSource* layer);  // Takes ownership
+
+    void SetLayerStyle(size_t layer,
+                       const RenderStyle& style);
+
+    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/WidgetBase.h	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Widgets/WidgetBase.h	Wed May 17 22:03:09 2017 +0200
@@ -99,5 +99,9 @@
     }
 
     virtual void NotifyChange();
+
+    virtual void Start()
+    {
+    }
   };
 }
--- a/Framework/Widgets/WorldSceneWidget.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Wed May 17 22:03:09 2017 +0200
@@ -268,8 +268,8 @@
 
     if (observers_.IsEmpty())
     {
-      // Without a size observer, use the default view
-      view_.SetDefaultView();
+      // Without a size observer, reset to the default view
+      // view_.SetDefaultView();
     }
     else
     {
--- a/Resources/CMake/OrthancStone.cmake	Tue May 16 22:12:41 2017 +0200
+++ b/Resources/CMake/OrthancStone.cmake	Wed May 17 22:03:09 2017 +0200
@@ -222,6 +222,7 @@
   ${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
 
   ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue May 16 22:12:41 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed May 17 22:03:09 2017 +0200
@@ -24,6 +24,7 @@
 #include "../Resources/Orthanc/Core/Logging.h"
 #include "../Framework/Toolbox/OrthancWebService.h"
 #include "../Framework/Layers/OrthancFrameLayerSource.h"
+#include "../Framework/Widgets/LayerWidget.h"
 
 
 namespace OrthancStone
@@ -60,6 +61,97 @@
     {
     }
   };
+
+
+
+  /*class OrthancInstanceLoader : public IWebService::ICallback
+  {
+  public:
+    class ICallback : public boost::noncopyable
+    {
+    public:
+      virtual ~ICallback()
+      {
+      }
+      
+      virtual void NotifyInstanceLoaded(const std::string& instanceId,
+                                        const OrthancPlugins::FullOrthancDataset& dicom) = 0;
+      
+      virtual void NotifyInstanceError(const std::string& instanceId) = 0;
+    };
+
+  private:
+    class Operation : public Orthanc::IDynamicObject
+    {
+    private:
+      ICallback&   callback_;
+      std::string  instanceId_;
+
+    public:
+      Operation(ICallback& callback,
+                const std::string& instanceId) :
+        callback_(callback),
+        instanceId_(instanceId)
+      {
+      }
+
+      ICallback& GetCallback()
+      {
+        return callback_;
+      }
+
+      const std::string& GetInstanceId() const
+      {
+        return instanceId_;
+      }
+    };
+
+    IWebService&  orthanc_;
+
+  public:
+    OrthancInstanceLoader(IWebService&  orthanc) :
+      orthanc_(orthanc)
+    {
+    }
+
+    void ScheduleLoadInstance(ICallback& callback,
+                              const std::string& instanceId)
+    {
+      orthanc_.ScheduleGetRequest(*this,
+                                  "/instances/" + instanceId + "/tags",
+                                  new Operation(callback, instanceId));
+    }
+    
+    void NotifySuccess(const std::string& uri,
+                       const void* answer,
+                       size_t answerSize,
+                       Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload));
+
+      try
+      {
+        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
+        operation->GetCallback().NotifyInstanceLoaded(operation->GetInstanceId(), dataset);
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        operation->GetCallback().NotifyInstanceError(operation->GetInstanceId());
+      }
+    }
+      
+    void NotifyError(const std::string& uri,
+                     Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload));
+
+      LOG(ERROR) << "Cannot download " << uri;
+      operation->GetCallback().NotifyInstanceError(operation->GetInstanceId());
+    }
+    };*/
+
+  
+
 }
 
 
@@ -75,6 +167,10 @@
 
   OrthancStone::SliceGeometry slice;
   source.ScheduleLayerCreation(slice);
+
+
+  OrthancStone::LayerWidget widget;
+  printf(">> %d\n", widget.AddLayer(new OrthancStone::OrthancFrameLayerSource(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0)));
 }