changeset 388:20f149669c1f

renamed LayerWidget as SliceViewerWidget
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 09 Nov 2018 17:26:39 +0100
parents a8b5cf760473
children 3e6e10a5a6c8
files Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp Applications/Samples/SimpleViewer/SimpleViewerApplication.h Applications/Samples/SimpleViewerApplicationSingleFile.h Applications/Samples/SingleFrameApplication.h Framework/SmartLoader.cpp Framework/SmartLoader.h Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Framework/Widgets/SliceViewerWidget.cpp Framework/Widgets/SliceViewerWidget.h Framework/dev.h Resources/CMake/OrthancStoneConfiguration.cmake Resources/OrthancStone.doxygen
diffstat 14 files changed, 859 insertions(+), 836 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp	Fri Nov 09 17:11:35 2018 +0100
+++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp	Fri Nov 09 17:26:39 2018 +0100
@@ -38,12 +38,12 @@
     {
       if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_LineMeasure)
       {
-        return new LineMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(),
+        return new LineMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                       x, y, 255, 0, 0, application_.GetFont());
       }
       else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_CircleMeasure)
       {
-        return new CircleMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(),
+        return new CircleMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                         x, y, 255, 0, 0, application_.GetFont());
       }
       else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Crop)
@@ -76,7 +76,7 @@
   {
     if (statusBar != NULL)
     {
-      Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+      Vector p = dynamic_cast<SliceViewerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
 
       char buf[64];
       sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp	Fri Nov 09 17:11:35 2018 +0100
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp	Fri Nov 09 17:26:39 2018 +0100
@@ -54,7 +54,7 @@
       thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
       thumbnailsLayout_->SetVertical();
 
-      mainWidget_ = new LayerWidget(IObserver::broker_, "main-viewport");
+      mainWidget_ = new SliceViewerWidget(IObserver::broker_, "main-viewport");
       //mainWidget_->RegisterObserver(*this);
 
       // hierarchy
@@ -152,10 +152,13 @@
   void SimpleViewerApplication::LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
   {
     LOG(INFO) << "Loading thumbnail for series " << seriesId;
-    LayerWidget* thumbnailWidget = new LayerWidget(IObserver::broker_, "thumbnail-series-" + seriesId);
+    SliceViewerWidget* thumbnailWidget = 
+      new SliceViewerWidget(IObserver::broker_, "thumbnail-series-" + seriesId);
     thumbnails_.push_back(thumbnailWidget);
     thumbnailsLayout_->AddWidget(thumbnailWidget);
-    thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
+    thumbnailWidget->RegisterObserverCallback(
+      new Callable<SimpleViewerApplication, SliceViewerWidget::GeometryChangedMessage>
+      (*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
     smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0);
     thumbnailWidget->SetInteractor(*thumbnailInteractor_);
   }
@@ -165,7 +168,7 @@
     orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
   }
 
-  void SimpleViewerApplication::OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
+  void SimpleViewerApplication::OnWidgetGeometryChanged(const SliceViewerWidget::GeometryChangedMessage& message)
   {
     message.GetOrigin().FitContent();
   }
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Fri Nov 09 17:11:35 2018 +0100
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Fri Nov 09 17:26:39 2018 +0100
@@ -26,7 +26,7 @@
 #include "Framework/Layers/OrthancFrameLayerSource.h"
 #include "Framework/Layers/CircleMeasureTracker.h"
 #include "Framework/Layers/LineMeasureTracker.h"
-#include "Framework/Widgets/LayerWidget.h"
+#include "Framework/Widgets/SliceViewerWidget.h"
 #include "Framework/Widgets/LayoutWidget.h"
 #include "Framework/Messages/IObserver.h"
 #include "Framework/SmartLoader.h"
@@ -88,26 +88,26 @@
     };
 
   private:
-    Tools                           currentTool_;
+    Tools                               currentTool_;
     std::auto_ptr<MainWidgetInteractor> mainWidgetInteractor_;
     std::auto_ptr<ThumbnailInteractor>  thumbnailInteractor_;
-    LayoutWidget*                   mainLayout_;
-    LayoutWidget*                   thumbnailsLayout_;
-    LayerWidget*                    mainWidget_;
-    std::vector<LayerWidget*>       thumbnails_;
+    LayoutWidget*                       mainLayout_;
+    LayoutWidget*                       thumbnailsLayout_;
+    SliceViewerWidget*                  mainWidget_;
+    std::vector<SliceViewerWidget*>     thumbnails_;
     std::map<std::string, std::vector<std::string> > instancesIdsPerSeriesId_;
-    std::map<std::string, Json::Value> seriesTags_;
-    BaseCommandBuilder              commandBuilder_;
+    std::map<std::string, Json::Value>  seriesTags_;
+    BaseCommandBuilder                  commandBuilder_;
 
-    unsigned int                    currentInstanceIndex_;
-    OrthancStone::WidgetViewport*   wasmViewport1_;
-    OrthancStone::WidgetViewport*   wasmViewport2_;
+    unsigned int                        currentInstanceIndex_;
+    OrthancStone::WidgetViewport*       wasmViewport1_;
+    OrthancStone::WidgetViewport*       wasmViewport2_;
 
-    IStatusBar*                     statusBar_;
-    std::auto_ptr<SmartLoader>    smartLoader_;
-    std::auto_ptr<OrthancApiClient>      orthancApiClient_;
+    IStatusBar*                         statusBar_;
+    std::auto_ptr<SmartLoader>          smartLoader_;
+    std::auto_ptr<OrthancApiClient>     orthancApiClient_;
 
-    Orthanc::Font                   font_;
+    Orthanc::Font                       font_;
 
   public:
     SimpleViewerApplication(MessageBroker& broker) :
@@ -140,7 +140,7 @@
 
     void SelectStudy(const std::string& studyId);
 
-    void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message);
+    void OnWidgetGeometryChanged(const SliceViewerWidget::GeometryChangedMessage& message);
 
     void SelectSeriesInMainViewport(const std::string& seriesId);
 
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h	Fri Nov 09 17:11:35 2018 +0100
+++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h	Fri Nov 09 17:26:39 2018 +0100
@@ -26,7 +26,7 @@
 #include "../../Framework/Layers/OrthancFrameLayerSource.h"
 #include "../../Framework/Layers/CircleMeasureTracker.h"
 #include "../../Framework/Layers/LineMeasureTracker.h"
-#include "../../Framework/Widgets/LayerWidget.h"
+#include "../../Framework/Widgets/SliceViewerWidget.h"
 #include "../../Framework/Widgets/LayoutWidget.h"
 #include "../../Framework/Messages/IObserver.h"
 #include "../../Framework/SmartLoader.h"
@@ -52,6 +52,7 @@
       {
       private:
         SimpleViewerApplication&  application_;
+
       public:
         ThumbnailInteractor(SimpleViewerApplication&  application) :
           application_(application)
@@ -76,27 +77,30 @@
           }
           return NULL;
         }
+
         virtual void MouseOver(CairoContext& context,
                                WorldSceneWidget& widget,
                                const ViewportGeometry& view,
                                double x,
                                double y,
                                IStatusBar* statusBar)
-        {}
+        {
+        }
 
         virtual void MouseWheel(WorldSceneWidget& widget,
                                 MouseWheelDirection direction,
                                 KeyboardModifiers modifiers,
                                 IStatusBar* statusBar)
-        {}
+        {
+        }
 
         virtual void KeyPressed(WorldSceneWidget& widget,
                                 KeyboardKeys key,
                                 char keyChar,
                                 KeyboardModifiers modifiers,
                                 IStatusBar* statusBar)
-        {}
-
+        {
+        }
       };
 
       class MainWidgetInteractor : public IWorldSceneInteractor
@@ -124,12 +128,12 @@
           {
             if (application_.currentTool_ == Tools_LineMeasure)
             {
-              return new LineMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(),
+              return new LineMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                             x, y, 255, 0, 0, application_.GetFont());
             }
             else if (application_.currentTool_ == Tools_CircleMeasure)
             {
-              return new CircleMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(),
+              return new CircleMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(),
                                               x, y, 255, 0, 0, application_.GetFont());
             }
           }
@@ -145,7 +149,7 @@
         {
           if (statusBar != NULL)
           {
-            Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+            Vector p = dynamic_cast<SliceViewerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
             
             char buf[64];
             sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
@@ -198,10 +202,10 @@
           : WasmPlatformApplicationAdapter(broker, application),
             viewerApplication_(application)
         {
-
         }
 
-        virtual void HandleMessageFromWeb(std::string& output, const std::string& input) {
+        virtual void HandleMessageFromWeb(std::string& output, const std::string& input) 
+        {
           if (input == "select-tool:line-measure")
           {
             viewerApplication_.currentTool_ = Tools_LineMeasure;
@@ -216,7 +220,8 @@
           output = "ok";
         }
 
-        virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
+        virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) 
+        {
           UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
         }
 
@@ -232,7 +237,7 @@
       std::auto_ptr<ThumbnailInteractor>   thumbnailInteractor_;
       LayoutWidget*                        mainLayout_;
       LayoutWidget*                        thumbnailsLayout_;
-      std::vector<LayerWidget*>            thumbnails_;
+      std::vector<SliceViewerWidget*>      thumbnails_;
 
       std::map<std::string, std::vector<std::string> > instancesIdsPerSeriesId_;
       std::map<std::string, Json::Value> seriesTags_;
@@ -293,7 +298,7 @@
           thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
           thumbnailsLayout_->SetVertical();
 
-          mainWidget_ = new LayerWidget(broker_, "main-viewport");
+          mainWidget_ = new SliceViewerWidget(broker_, "main-viewport");
           //mainWidget_->RegisterObserver(*this);
 
           // hierarchy
@@ -319,9 +324,10 @@
         if (parameters.count("studyId") < 1)
         {
           LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
-          orthancApiClient_->GetJsonAsync("/studies",
-                                          new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>
-                                          (*this, &SimpleViewerApplication::OnStudyListReceived));
+          orthancApiClient_->GetJsonAsync(
+            "/studies",
+            new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>
+            (*this, &SimpleViewerApplication::OnStudyListReceived));
         }
         else
         {
@@ -346,9 +352,10 @@
         {
           for (size_t i=0; i < response["Series"].size(); i++)
           {
-            orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(),
-                                            new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>
-                                            (*this, &SimpleViewerApplication::OnSeriesReceived));
+            orthancApiClient_->GetJsonAsync(
+              "/series/" + response["Series"][(int)i].asString(),
+              new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>
+              (*this, &SimpleViewerApplication::OnSeriesReceived));
           }
         }
       }
@@ -375,7 +382,7 @@
           LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]);
 
           // if this is the first thumbnail loaded, load the first instance in the mainWidget
-          LayerWidget& widget = *dynamic_cast<LayerWidget*>(mainWidget_);
+          SliceViewerWidget& widget = *dynamic_cast<SliceViewerWidget*>(mainWidget_);
           if (widget.GetLayerCount() == 0)
           {
             smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
@@ -386,10 +393,10 @@
       void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
       {
         LOG(INFO) << "Loading thumbnail for series " << seriesId;
-        LayerWidget* thumbnailWidget = new LayerWidget(IObserver::broker_, "thumbnail-series-" + seriesId);
+        SliceViewerWidget* thumbnailWidget = new SliceViewerWidget(IObserver::broker_, "thumbnail-series-" + seriesId);
         thumbnails_.push_back(thumbnailWidget);
         thumbnailsLayout_->AddWidget(thumbnailWidget);
-        thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
+        thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, SliceViewerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
         smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0);
         thumbnailWidget->SetInteractor(*thumbnailInteractor_);
       }
@@ -399,14 +406,14 @@
         orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
       }
 
-      void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
+      void OnWidgetGeometryChanged(const SliceViewerWidget::GeometryChangedMessage& message)
       {
         message.GetOrigin().FitContent();
       }
 
       void SelectSeriesInMainViewport(const std::string& seriesId)
       {
-        LayerWidget& widget = *dynamic_cast<LayerWidget*>(mainWidget_);
+        SliceViewerWidget& widget = *dynamic_cast<SliceViewerWidget*>(mainWidget_);
         smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
       }
 
@@ -440,7 +447,5 @@
 #endif
 
     };
-
-
   }
 }
--- a/Applications/Samples/SingleFrameApplication.h	Fri Nov 09 17:11:35 2018 +0100
+++ b/Applications/Samples/SingleFrameApplication.h	Fri Nov 09 17:26:39 2018 +0100
@@ -24,7 +24,7 @@
 #include "SampleApplicationBase.h"
 
 #include "../../Framework/Layers/OrthancFrameLayerSource.h"
-#include "../../Framework/Widgets/LayerWidget.h"
+#include "../../Framework/Widgets/SliceViewerWidget.h"
 
 #include <Core/Logging.h>
 
@@ -73,7 +73,7 @@
         {
           if (statusBar != NULL)
           {
-            Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+            Vector p = dynamic_cast<SliceViewerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
             
             char buf[64];
             sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", 
@@ -147,9 +147,9 @@
       }
 
 
-      LayerWidget& GetMainWidget()
+      SliceViewerWidget& GetMainWidget()
       {
-        return *dynamic_cast<LayerWidget*>(mainWidget_);
+        return *dynamic_cast<SliceViewerWidget*>(mainWidget_);
       }
       
 
@@ -243,7 +243,7 @@
         int frame = parameters["frame"].as<unsigned int>();
 
         orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService()));
-        mainWidget_ = new LayerWidget(broker_, "main-widget");
+        mainWidget_ = new SliceViewerWidget(broker_, "main-widget");
 
         std::auto_ptr<OrthancFrameLayerSource> layer(new OrthancFrameLayerSource(broker_, *orthancApiClient_));
         source_ = layer.get();
--- a/Framework/SmartLoader.cpp	Fri Nov 09 17:11:35 2018 +0100
+++ b/Framework/SmartLoader.cpp	Fri Nov 09 17:26:39 2018 +0100
@@ -23,7 +23,7 @@
 #include "Layers/OrthancFrameLayerSource.h"
 #include "Messages/MessageForwarder.h"
 #include "Core/Images/Image.h"
-#include "Framework/Widgets/LayerWidget.h"
+#include "Framework/Widgets/SliceViewerWidget.h"
 #include "Framework/StoneException.h"
 #include "Framework/Layers/FrameRenderer.h"
 #include "Core/Logging.h"
@@ -127,7 +127,7 @@
   {
   }
 
-  void SmartLoader::SetFrameInWidget(LayerWidget& layerWidget, 
+  void SmartLoader::SetFrameInWidget(SliceViewerWidget& sliceViewer, 
                                      size_t layerIndex, 
                                      const std::string& instanceId, 
                                      unsigned int frame)
@@ -159,13 +159,13 @@
     }
 
     // make sure that the widget registers the events before we trigger them
-    if (layerWidget.GetLayerCount() == layerIndex)
+    if (sliceViewer.GetLayerCount() == layerIndex)
     {
-      layerWidget.AddLayer(layerSource.release());
+      sliceViewer.AddLayer(layerSource.release());
     }
-    else if (layerWidget.GetLayerCount() > layerIndex)
+    else if (sliceViewer.GetLayerCount() > layerIndex)
     {
-      layerWidget.ReplaceLayer(layerIndex, layerSource.release());
+      sliceViewer.ReplaceLayer(layerIndex, layerSource.release());
     }
     else
     {
--- a/Framework/SmartLoader.h	Fri Nov 09 17:11:35 2018 +0100
+++ b/Framework/SmartLoader.h	Fri Nov 09 17:26:39 2018 +0100
@@ -28,7 +28,7 @@
 
 namespace OrthancStone
 {
-  class LayerWidget;
+  class SliceViewerWidget;
 
   class SmartLoader : public IObservable, public IObserver
   {
@@ -53,7 +53,7 @@
 
     void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
 
-    void SetFrameInWidget(LayerWidget& layerWidget, size_t layerIndex, const std::string& instanceId, unsigned int frame);
+    void SetFrameInWidget(SliceViewerWidget& sliceViewer, size_t layerIndex, const std::string& instanceId, unsigned int frame);
 
     void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId);
 
--- a/Framework/Widgets/LayerWidget.cpp	Fri Nov 09 17:11:35 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,639 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "LayerWidget.h"
-
-#include "../Layers/SliceOutlineRenderer.h"
-#include "../Toolbox/GeometryToolbox.h"
-#include "Framework/Layers/FrameRenderer.h"
-
-#include <Core/Logging.h>
-
-#include <boost/math/constants/constants.hpp>
-
-
-static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon();
-
-namespace OrthancStone
-{
-  class LayerWidget::Scene : public boost::noncopyable
-  {
-  private:
-    CoordinateSystem3D            slice_;
-    double                        thickness_;
-    size_t                        countMissing_;
-    std::vector<ILayerRenderer*>  renderers_;
-public:
-    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_++;
-      }
-    }
-
-    Scene(const CoordinateSystem3D& slice,
-          double thickness,
-          size_t countLayers) :
-      slice_(slice),
-      thickness_(thickness),
-      countMissing_(countLayers),
-      renderers_(countLayers, NULL)
-    {
-      if (thickness <= 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    ~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 CoordinateSystem3D& GetSlice() const
-    {
-      return slice_;
-    }
-
-    bool HasRenderer(size_t index)
-    {
-      return renderers_[index] != NULL;
-    }
-
-    bool IsComplete() const
-    {
-      return countMissing_ == 0;
-    }
-
-    unsigned int GetCountMissing() const
-    {
-      return countMissing_;
-    }
-
-    bool RenderScene(CairoContext& context,
-                     const ViewportGeometry& view,
-                     const CoordinateSystem3D& viewportSlice)
-    {
-      bool fullQuality = true;
-      cairo_t *cr = context.GetObject();
-
-      for (size_t i = 0; i < renderers_.size(); i++)
-      {
-        if (renderers_[i] != NULL)
-        {
-          const CoordinateSystem3D& 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 &&
-            !renderers_[i]->IsFullQuality())
-        {
-          fullQuality = false;
-        }
-      }
-
-      if (!fullQuality)
-      {
-        double x, y;
-        view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10);
-
-        cairo_translate(cr, x, y);
-
-#if 1
-        double s = 5.0 / view.GetZoom();
-        cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s);
-#else
-        // TODO Drawing filled circles makes WebAssembly crash!
-        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>());
-#endif
-        
-        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 ContainsPlane(const CoordinateSystem3D& slice) const
-    {
-      bool isOpposite;
-      if (!GeometryToolbox::IsParallelOrOpposite(isOpposite,
-                                                 slice.GetNormal(),
-                                                 slice_.GetNormal()))
-      {
-        return false;
-      }
-      else
-      {
-        double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) -
-                    slice_.ProjectAlongNormal(slice_.GetOrigin()));
-
-        if (z < 0)
-        {
-          z = -z;
-        }
-
-        return z <= thickness_;
-      }
-    }
-
-    double GetThickness() const
-    {
-      return thickness_;
-    }
-  };
-
-  
-  bool LayerWidget::LookupLayer(size_t& index /* out */,
-                                const 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::GetLayerExtent(Extent2D& extent,
-                                   ILayerSource& source) const
-  {
-    extent.Reset();
-
-    std::vector<Vector> points;
-    if (source.GetExtent(points, slice_))
-    {
-      for (size_t i = 0; i < points.size(); i++)
-      {
-        double x, y;
-        slice_.ProjectPoint(x, y, points[i]);
-        extent.AddPoint(x, y);
-      }
-    }
-  }
-
-
-  Extent2D LayerWidget::GetSceneExtent()
-  {
-    Extent2D sceneExtent;
-
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      assert(layers_[i] != NULL);
-      Extent2D layerExtent;
-      GetLayerExtent(layerExtent, *layers_[i]);
-
-      sceneExtent.Union(layerExtent);
-    }
-
-    return sceneExtent;
-  }
-
-  
-  bool LayerWidget::RenderScene(CairoContext& context,
-                                const ViewportGeometry& view)
-  {
-    if (currentScene_.get() != NULL)
-    {
-      return currentScene_->RenderScene(context, view, slice_);
-    }
-    else
-    {
-      return true;
-    }
-  }
-
-  
-  void LayerWidget::ResetPendingScene()
-  {
-    double thickness;
-    if (pendingScene_.get() == NULL)
-    {
-      thickness = 1.0;
-    }
-    else
-    {
-      thickness = pendingScene_->GetThickness();
-    }
-    
-    pendingScene_.reset(new Scene(slice_, thickness, layers_.size()));
-  }
-  
-
-  void LayerWidget::UpdateLayer(size_t index,
-                                ILayerRenderer* renderer,
-                                const CoordinateSystem3D& slice)
-  {
-    LOG(INFO) << "Updating layer " << 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_->ContainsPlane(slice))
-    {
-      currentScene_->SetLayer(index, tmp.release());
-      NotifyContentChanged();
-    }
-    else if (pendingScene_.get() != NULL &&
-             pendingScene_->ContainsPlane(slice))
-    {
-      pendingScene_->SetLayer(index, tmp.release());
-
-      if (currentScene_.get() == NULL ||
-          !currentScene_->IsComplete() ||
-          pendingScene_->IsComplete())
-      {
-        currentScene_ = pendingScene_;
-        NotifyContentChanged();
-      }
-    }
-  }
-
-  
-  LayerWidget::LayerWidget(MessageBroker& broker, const std::string& name) :
-    WorldSceneWidget(name),
-    IObserver(broker),
-    IObservable(broker),
-    started_(false)
-  {
-    SetBackgroundCleared(true);
-  }
-  
-  
-  LayerWidget::~LayerWidget()
-  {
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      delete layers_[i];
-    }
-  }
-  
-  void LayerWidget::ObserveLayer(ILayerSource& layer)
-  {
-    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::GeometryReadyMessage>(*this, &LayerWidget::OnGeometryReady));
-    // currently ignore errors layer->RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::GeometryErrorMessage>(*this, &LayerWidget::...));
-    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::SliceChangedMessage>(*this, &LayerWidget::OnSliceChanged));
-    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::ContentChangedMessage>(*this, &LayerWidget::OnContentChanged));
-    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::LayerReadyMessage>(*this, &LayerWidget::OnLayerReady));
-    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::LayerErrorMessage>(*this, &LayerWidget::OnLayerError));
-  }
-
-
-  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();
-
-    ObserveLayer(*layer);
-
-    ResetChangedLayers();
-
-    return index;
-  }
-
-  void LayerWidget::ReplaceLayer(size_t index, ILayerSource* layer)  // Takes ownership
-  {
-    if (layer == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    if (index >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    delete layers_[index];
-    layers_[index] = layer;
-    layersIndex_[layer] = index;
-
-    ResetPendingScene();
-
-    ObserveLayer(*layer);
-
-    InvalidateLayer(index);
-  }
-
-  void LayerWidget::RemoveLayer(size_t index)
-  {
-    if (index >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    ILayerSource* previousLayer = layers_[index];
-    layersIndex_.erase(layersIndex_.find(previousLayer));
-    layers_.erase(layers_.begin() + index);
-    changedLayers_.erase(changedLayers_.begin() + index);
-    styles_.erase(styles_.begin() + index);
-
-    delete layers_[index];
-
-    currentScene_->DeleteLayer(index);
-    ResetPendingScene();
-
-    NotifyContentChanged();
-  }
-
-  const RenderStyle& LayerWidget::GetLayerStyle(size_t layer) const
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(layers_.size() == styles_.size());
-    return styles_[layer];
-  }
-  
-
-  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);
-    }
-
-    NotifyContentChanged();
-  }
-  
-
-  void LayerWidget::SetSlice(const CoordinateSystem3D& slice)
-  {
-    LOG(INFO) << "Setting slice origin: (" << slice.GetOrigin()[0]
-              << "," << slice.GetOrigin()[1]
-              << "," << slice.GetOrigin()[2] << ")";
-    
-    Slice displayedSlice(slice_, THIN_SLICE_THICKNESS);
-
-    //if (!displayedSlice.ContainsPlane(slice))
-    {
-      if (currentScene_.get() == NULL ||
-          (pendingScene_.get() != NULL &&
-           pendingScene_->IsComplete()))
-      {
-        currentScene_ = pendingScene_;
-      }
-
-      slice_ = slice;
-      ResetPendingScene();
-
-      InvalidateAllLayers();   // TODO Removing this line avoid loading twice the image in WASM
-    }
-  }
-
-  void LayerWidget::OnGeometryReady(const ILayerSource::GeometryReadyMessage& message)
-  {
-    size_t i;
-    if (LookupLayer(i, message.GetOrigin()))
-    {
-      LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
-
-      changedLayers_[i] = true;
-      //layers_[i]->ScheduleLayerCreation(slice_);
-    }
-    EmitMessage(GeometryChangedMessage(*this));
-  }
-  
-  void LayerWidget::InvalidateAllLayers()
-  {
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      assert(layers_[i] != NULL);
-      changedLayers_[i] = true;
-      
-      //layers_[i]->ScheduleLayerCreation(slice_);
-    }
-  }
-
-
-  void LayerWidget::InvalidateLayer(size_t layer)
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(layers_[layer] != NULL);
-    changedLayers_[layer] = true;
-
-    //layers_[layer]->ScheduleLayerCreation(slice_);
-  }
-
-
-  void LayerWidget::OnContentChanged(const ILayerSource::ContentChangedMessage& message)
-  {
-    size_t index;
-    if (LookupLayer(index, message.GetOrigin()))
-    {
-      InvalidateLayer(index);
-    }
-    
-    EmitMessage(LayerWidget::ContentChangedMessage(*this));
-  }
-  
-
-  void LayerWidget::OnSliceChanged(const ILayerSource::SliceChangedMessage& message)
-  {
-    if (message.GetSlice().ContainsPlane(slice_))
-    {
-      size_t index;
-      if (LookupLayer(index, message.GetOrigin()))
-      {
-        InvalidateLayer(index);
-      }
-    }
-    
-    EmitMessage(LayerWidget::ContentChangedMessage(*this));
-  }
-  
-  
-  void LayerWidget::OnLayerReady(const ILayerSource::LayerReadyMessage& message)
-  {
-    size_t index;
-    if (LookupLayer(index, message.GetOrigin()))
-    {
-      LOG(INFO) << "Renderer ready for layer " << index;
-      UpdateLayer(index, message.CreateRenderer(), message.GetSlice());
-    }
-    
-    EmitMessage(LayerWidget::ContentChangedMessage(*this));
-  }
-
-
-  void LayerWidget::OnLayerError(const ILayerSource::LayerErrorMessage& message)
-  {
-    size_t index;
-    if (LookupLayer(index, message.GetOrigin()))
-    {
-      LOG(ERROR) << "Using error renderer on layer " << index;
-
-      // TODO
-      //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
-
-      EmitMessage(LayerWidget::ContentChangedMessage(*this));
-    }
-  }
-
-
-  void LayerWidget::ResetChangedLayers()
-  {
-    changedLayers_.resize(layers_.size());
-
-    for (size_t i = 0; i < changedLayers_.size(); i++)
-    {
-      changedLayers_[i] = false;
-    }
-  }
-
-
-  void LayerWidget::DoAnimation()
-  {
-    assert(changedLayers_.size() <= layers_.size());
-    
-    for (size_t i = 0; i < changedLayers_.size(); i++)
-    {
-      if (changedLayers_[i])
-      {
-        layers_[i]->ScheduleLayerCreation(slice_);
-      }
-    }
-    
-    ResetChangedLayers();
-  }
-}
--- a/Framework/Widgets/LayerWidget.h	Fri Nov 09 17:11:35 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WorldSceneWidget.h"
-#include "../Layers/ILayerSource.h"
-#include "../Toolbox/Extent2D.h"
-#include "../../Framework/Messages/IObserver.h"
-
-#include <map>
-
-namespace OrthancStone
-{
-  class LayerWidget :
-      public WorldSceneWidget,
-      public IObserver,
-      public IObservable
-  {
-  public:
-    typedef OriginMessage<MessageType_Widget_GeometryChanged, LayerWidget> GeometryChangedMessage;
-    typedef OriginMessage<MessageType_Widget_ContentChanged, LayerWidget> ContentChangedMessage;
-
-  private:
-    class Scene;
-    
-    typedef std::map<const ILayerSource*, size_t>  LayersIndex;
-
-    bool                        started_;
-    LayersIndex                 layersIndex_;
-    std::vector<ILayerSource*>  layers_;
-    std::vector<RenderStyle>    styles_;
-    CoordinateSystem3D          slice_;
-    std::auto_ptr<Scene>        currentScene_;
-    std::auto_ptr<Scene>        pendingScene_;
-    std::vector<bool>           changedLayers_;
-
-    bool LookupLayer(size_t& index /* out */,
-                     const ILayerSource& layer) const;
-
-    void GetLayerExtent(Extent2D& extent,
-                        ILayerSource& source) const;
-
-    void OnGeometryReady(const ILayerSource::GeometryReadyMessage& message);
-
-    virtual void OnContentChanged(const ILayerSource::ContentChangedMessage& message);
-
-    virtual void OnSliceChanged(const ILayerSource::SliceChangedMessage& message);
-
-    virtual void OnLayerReady(const ILayerSource::LayerReadyMessage& message);
-
-    virtual void OnLayerError(const ILayerSource::LayerErrorMessage& message);
-
-    void ObserveLayer(ILayerSource& source);
-
-    void ResetChangedLayers();
-
-  public:
-    LayerWidget(MessageBroker& broker, const std::string& name);
-
-    virtual Extent2D GetSceneExtent();
-
-  protected:
-    virtual bool RenderScene(CairoContext& context,
-                             const ViewportGeometry& view);
-
-    void ResetPendingScene();
-
-    void UpdateLayer(size_t index,
-                     ILayerRenderer* renderer,
-                     const CoordinateSystem3D& slice);
-
-    void InvalidateAllLayers();
-
-    void InvalidateLayer(size_t layer);
-    
-  public:
-    virtual ~LayerWidget();
-
-    size_t AddLayer(ILayerSource* layer);  // Takes ownership
-
-    void ReplaceLayer(size_t layerIndex, ILayerSource* layer); // Takes ownership
-
-    void RemoveLayer(size_t layerIndex);
-
-    size_t GetLayerCount() const
-    {
-      return layers_.size();
-    }
-
-    const RenderStyle& GetLayerStyle(size_t layer) const;
-
-    void SetLayerStyle(size_t layer,
-                       const RenderStyle& style);
-
-    void SetSlice(const CoordinateSystem3D& slice);
-
-    const CoordinateSystem3D& GetSlice() const
-    {
-      return slice_;
-    }
-
-    virtual bool HasAnimation() const
-    {
-      return true;
-    }
-
-    virtual void DoAnimation();
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/SliceViewerWidget.cpp	Fri Nov 09 17:26:39 2018 +0100
@@ -0,0 +1,651 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SliceViewerWidget.h"
+
+#include "../Layers/SliceOutlineRenderer.h"
+#include "../Toolbox/GeometryToolbox.h"
+#include "Framework/Layers/FrameRenderer.h"
+
+#include <Core/Logging.h>
+
+#include <boost/math/constants/constants.hpp>
+
+
+static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon();
+
+namespace OrthancStone
+{
+  class SliceViewerWidget::Scene : public boost::noncopyable
+  {
+  private:
+    CoordinateSystem3D            slice_;
+    double                        thickness_;
+    size_t                        countMissing_;
+    std::vector<ILayerRenderer*>  renderers_;
+
+  public:
+    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_++;
+      }
+    }
+
+    Scene(const CoordinateSystem3D& slice,
+          double thickness,
+          size_t countLayers) :
+      slice_(slice),
+      thickness_(thickness),
+      countMissing_(countLayers),
+      renderers_(countLayers, NULL)
+    {
+      if (thickness <= 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    ~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 CoordinateSystem3D& GetSlice() const
+    {
+      return slice_;
+    }
+
+    bool HasRenderer(size_t index)
+    {
+      return renderers_[index] != NULL;
+    }
+
+    bool IsComplete() const
+    {
+      return countMissing_ == 0;
+    }
+
+    unsigned int GetCountMissing() const
+    {
+      return countMissing_;
+    }
+
+    bool RenderScene(CairoContext& context,
+                     const ViewportGeometry& view,
+                     const CoordinateSystem3D& viewportSlice)
+    {
+      bool fullQuality = true;
+      cairo_t *cr = context.GetObject();
+
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        if (renderers_[i] != NULL)
+        {
+          const CoordinateSystem3D& 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 &&
+            !renderers_[i]->IsFullQuality())
+        {
+          fullQuality = false;
+        }
+      }
+
+      if (!fullQuality)
+      {
+        double x, y;
+        view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10);
+
+        cairo_translate(cr, x, y);
+
+#if 1
+        double s = 5.0 / view.GetZoom();
+        cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s);
+#else
+        // TODO Drawing filled circles makes WebAssembly crash!
+        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>());
+#endif
+        
+        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 ContainsPlane(const CoordinateSystem3D& slice) const
+    {
+      bool isOpposite;
+      if (!GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                 slice.GetNormal(),
+                                                 slice_.GetNormal()))
+      {
+        return false;
+      }
+      else
+      {
+        double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) -
+                    slice_.ProjectAlongNormal(slice_.GetOrigin()));
+
+        if (z < 0)
+        {
+          z = -z;
+        }
+
+        return z <= thickness_;
+      }
+    }
+
+    double GetThickness() const
+    {
+      return thickness_;
+    }
+  };
+
+  
+  bool SliceViewerWidget::LookupLayer(size_t& index /* out */,
+                                      const 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 SliceViewerWidget::GetLayerExtent(Extent2D& extent,
+                                         ILayerSource& source) const
+  {
+    extent.Reset();
+
+    std::vector<Vector> points;
+    if (source.GetExtent(points, slice_))
+    {
+      for (size_t i = 0; i < points.size(); i++)
+      {
+        double x, y;
+        slice_.ProjectPoint(x, y, points[i]);
+        extent.AddPoint(x, y);
+      }
+    }
+  }
+
+
+  Extent2D SliceViewerWidget::GetSceneExtent()
+  {
+    Extent2D sceneExtent;
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      Extent2D layerExtent;
+      GetLayerExtent(layerExtent, *layers_[i]);
+
+      sceneExtent.Union(layerExtent);
+    }
+
+    return sceneExtent;
+  }
+
+  
+  bool SliceViewerWidget::RenderScene(CairoContext& context,
+                                      const ViewportGeometry& view)
+  {
+    if (currentScene_.get() != NULL)
+    {
+      return currentScene_->RenderScene(context, view, slice_);
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  
+  void SliceViewerWidget::ResetPendingScene()
+  {
+    double thickness;
+    if (pendingScene_.get() == NULL)
+    {
+      thickness = 1.0;
+    }
+    else
+    {
+      thickness = pendingScene_->GetThickness();
+    }
+    
+    pendingScene_.reset(new Scene(slice_, thickness, layers_.size()));
+  }
+  
+
+  void SliceViewerWidget::UpdateLayer(size_t index,
+                                      ILayerRenderer* renderer,
+                                      const CoordinateSystem3D& slice)
+  {
+    LOG(INFO) << "Updating layer " << 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_->ContainsPlane(slice))
+    {
+      currentScene_->SetLayer(index, tmp.release());
+      NotifyContentChanged();
+    }
+    else if (pendingScene_.get() != NULL &&
+             pendingScene_->ContainsPlane(slice))
+    {
+      pendingScene_->SetLayer(index, tmp.release());
+
+      if (currentScene_.get() == NULL ||
+          !currentScene_->IsComplete() ||
+          pendingScene_->IsComplete())
+      {
+        currentScene_ = pendingScene_;
+        NotifyContentChanged();
+      }
+    }
+  }
+
+  
+  SliceViewerWidget::SliceViewerWidget(MessageBroker& broker, 
+                                       const std::string& name) :
+    WorldSceneWidget(name),
+    IObserver(broker),
+    IObservable(broker),
+    started_(false)
+  {
+    SetBackgroundCleared(true);
+  }
+  
+  
+  SliceViewerWidget::~SliceViewerWidget()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      delete layers_[i];
+    }
+  }
+  
+  void SliceViewerWidget::ObserveLayer(ILayerSource& layer)
+  {
+    layer.RegisterObserverCallback(new Callable<SliceViewerWidget, ILayerSource::GeometryReadyMessage>
+                                   (*this, &SliceViewerWidget::OnGeometryReady));
+    // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, ILayerSource::GeometryErrorMessage>(*this, &SliceViewerWidget::...));
+    layer.RegisterObserverCallback(new Callable<SliceViewerWidget, ILayerSource::SliceChangedMessage>
+                                   (*this, &SliceViewerWidget::OnSliceChanged));
+    layer.RegisterObserverCallback(new Callable<SliceViewerWidget, ILayerSource::ContentChangedMessage>
+                                   (*this, &SliceViewerWidget::OnContentChanged));
+    layer.RegisterObserverCallback(new Callable<SliceViewerWidget, ILayerSource::LayerReadyMessage>
+                                   (*this, &SliceViewerWidget::OnLayerReady));
+    layer.RegisterObserverCallback(new Callable<SliceViewerWidget, ILayerSource::LayerErrorMessage>
+                                   (*this, &SliceViewerWidget::OnLayerError));
+  }
+
+
+  size_t SliceViewerWidget::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();
+
+    ObserveLayer(*layer);
+
+    ResetChangedLayers();
+
+    return index;
+  }
+
+
+  void SliceViewerWidget::ReplaceLayer(size_t index, ILayerSource* layer)  // Takes ownership
+  {
+    if (layer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    delete layers_[index];
+    layers_[index] = layer;
+    layersIndex_[layer] = index;
+
+    ResetPendingScene();
+
+    ObserveLayer(*layer);
+
+    InvalidateLayer(index);
+  }
+
+
+  void SliceViewerWidget::RemoveLayer(size_t index)
+  {
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    ILayerSource* previousLayer = layers_[index];
+    layersIndex_.erase(layersIndex_.find(previousLayer));
+    layers_.erase(layers_.begin() + index);
+    changedLayers_.erase(changedLayers_.begin() + index);
+    styles_.erase(styles_.begin() + index);
+
+    delete layers_[index];
+
+    currentScene_->DeleteLayer(index);
+    ResetPendingScene();
+
+    NotifyContentChanged();
+  }
+
+
+  const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    return styles_[layer];
+  }
+  
+
+  void SliceViewerWidget::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);
+    }
+
+    NotifyContentChanged();
+  }
+  
+
+  void SliceViewerWidget::SetSlice(const CoordinateSystem3D& slice)
+  {
+    LOG(INFO) << "Setting slice origin: (" << slice.GetOrigin()[0]
+              << "," << slice.GetOrigin()[1]
+              << "," << slice.GetOrigin()[2] << ")";
+    
+    Slice displayedSlice(slice_, THIN_SLICE_THICKNESS);
+
+    //if (!displayedSlice.ContainsPlane(slice))
+    {
+      if (currentScene_.get() == NULL ||
+          (pendingScene_.get() != NULL &&
+           pendingScene_->IsComplete()))
+      {
+        currentScene_ = pendingScene_;
+      }
+
+      slice_ = slice;
+      ResetPendingScene();
+
+      InvalidateAllLayers();   // TODO Removing this line avoid loading twice the image in WASM
+    }
+  }
+
+
+  void SliceViewerWidget::OnGeometryReady(const ILayerSource::GeometryReadyMessage& message)
+  {
+    size_t i;
+    if (LookupLayer(i, message.GetOrigin()))
+    {
+      LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
+
+      changedLayers_[i] = true;
+      //layers_[i]->ScheduleLayerCreation(slice_);
+    }
+    EmitMessage(GeometryChangedMessage(*this));
+  }
+  
+
+  void SliceViewerWidget::InvalidateAllLayers()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      changedLayers_[i] = true;
+      
+      //layers_[i]->ScheduleLayerCreation(slice_);
+    }
+  }
+
+
+  void SliceViewerWidget::InvalidateLayer(size_t layer)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_[layer] != NULL);
+    changedLayers_[layer] = true;
+
+    //layers_[layer]->ScheduleLayerCreation(slice_);
+  }
+
+
+  void SliceViewerWidget::OnContentChanged(const ILayerSource::ContentChangedMessage& message)
+  {
+    size_t index;
+    if (LookupLayer(index, message.GetOrigin()))
+    {
+      InvalidateLayer(index);
+    }
+    
+    EmitMessage(SliceViewerWidget::ContentChangedMessage(*this));
+  }
+  
+
+  void SliceViewerWidget::OnSliceChanged(const ILayerSource::SliceChangedMessage& message)
+  {
+    if (message.GetSlice().ContainsPlane(slice_))
+    {
+      size_t index;
+      if (LookupLayer(index, message.GetOrigin()))
+      {
+        InvalidateLayer(index);
+      }
+    }
+    
+    EmitMessage(SliceViewerWidget::ContentChangedMessage(*this));
+  }
+  
+  
+  void SliceViewerWidget::OnLayerReady(const ILayerSource::LayerReadyMessage& message)
+  {
+    size_t index;
+    if (LookupLayer(index, message.GetOrigin()))
+    {
+      LOG(INFO) << "Renderer ready for layer " << index;
+      UpdateLayer(index, message.CreateRenderer(), message.GetSlice());
+    }
+    
+    EmitMessage(SliceViewerWidget::ContentChangedMessage(*this));
+  }
+
+
+  void SliceViewerWidget::OnLayerError(const ILayerSource::LayerErrorMessage& message)
+  {
+    size_t index;
+    if (LookupLayer(index, message.GetOrigin()))
+    {
+      LOG(ERROR) << "Using error renderer on layer " << index;
+
+      // TODO
+      //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
+
+      EmitMessage(SliceViewerWidget::ContentChangedMessage(*this));
+    }
+  }
+
+
+  void SliceViewerWidget::ResetChangedLayers()
+  {
+    changedLayers_.resize(layers_.size());
+
+    for (size_t i = 0; i < changedLayers_.size(); i++)
+    {
+      changedLayers_[i] = false;
+    }
+  }
+
+
+  void SliceViewerWidget::DoAnimation()
+  {
+    assert(changedLayers_.size() <= layers_.size());
+    
+    for (size_t i = 0; i < changedLayers_.size(); i++)
+    {
+      if (changedLayers_[i])
+      {
+        layers_[i]->ScheduleLayerCreation(slice_);
+      }
+    }
+    
+    ResetChangedLayers();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/SliceViewerWidget.h	Fri Nov 09 17:26:39 2018 +0100
@@ -0,0 +1,129 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+#include "../Layers/ILayerSource.h"
+#include "../Toolbox/Extent2D.h"
+#include "../../Framework/Messages/IObserver.h"
+
+#include <map>
+
+namespace OrthancStone
+{
+  class SliceViewerWidget :
+    public WorldSceneWidget,
+    public IObserver,
+    public IObservable
+  {
+  public:
+    typedef OriginMessage<MessageType_Widget_GeometryChanged, SliceViewerWidget> GeometryChangedMessage;
+    typedef OriginMessage<MessageType_Widget_ContentChanged, SliceViewerWidget> ContentChangedMessage;
+
+  private:
+    class Scene;
+    
+    typedef std::map<const ILayerSource*, size_t>  LayersIndex;
+
+    bool                        started_;
+    LayersIndex                 layersIndex_;
+    std::vector<ILayerSource*>  layers_;
+    std::vector<RenderStyle>    styles_;
+    CoordinateSystem3D          slice_;
+    std::auto_ptr<Scene>        currentScene_;
+    std::auto_ptr<Scene>        pendingScene_;
+    std::vector<bool>           changedLayers_;
+
+    bool LookupLayer(size_t& index /* out */,
+                     const ILayerSource& layer) const;
+
+    void GetLayerExtent(Extent2D& extent,
+                        ILayerSource& source) const;
+
+    void OnGeometryReady(const ILayerSource::GeometryReadyMessage& message);
+
+    virtual void OnContentChanged(const ILayerSource::ContentChangedMessage& message);
+
+    virtual void OnSliceChanged(const ILayerSource::SliceChangedMessage& message);
+
+    virtual void OnLayerReady(const ILayerSource::LayerReadyMessage& message);
+
+    virtual void OnLayerError(const ILayerSource::LayerErrorMessage& message);
+
+    void ObserveLayer(ILayerSource& source);
+
+    void ResetChangedLayers();
+
+  public:
+    SliceViewerWidget(MessageBroker& broker, 
+                      const std::string& name);
+
+    virtual Extent2D GetSceneExtent();
+
+  protected:
+    virtual bool RenderScene(CairoContext& context,
+                             const ViewportGeometry& view);
+
+    void ResetPendingScene();
+
+    void UpdateLayer(size_t index,
+                     ILayerRenderer* renderer,
+                     const CoordinateSystem3D& slice);
+
+    void InvalidateAllLayers();
+
+    void InvalidateLayer(size_t layer);
+    
+  public:
+    virtual ~SliceViewerWidget();
+
+    size_t AddLayer(ILayerSource* layer);  // Takes ownership
+
+    void ReplaceLayer(size_t layerIndex, ILayerSource* layer); // Takes ownership
+
+    void RemoveLayer(size_t layerIndex);
+
+    size_t GetLayerCount() const
+    {
+      return layers_.size();
+    }
+
+    const RenderStyle& GetLayerStyle(size_t layer) const;
+
+    void SetLayerStyle(size_t layer,
+                       const RenderStyle& style);
+
+    void SetSlice(const CoordinateSystem3D& slice);
+
+    const CoordinateSystem3D& GetSlice() const
+    {
+      return slice_;
+    }
+
+    virtual bool HasAnimation() const
+    {
+      return true;
+    }
+
+    virtual void DoAnimation();
+  };
+}
--- a/Framework/dev.h	Fri Nov 09 17:11:35 2018 +0100
+++ b/Framework/dev.h	Fri Nov 09 17:26:39 2018 +0100
@@ -25,7 +25,7 @@
 #include "Layers/LayerSourceBase.h"
 #include "Layers/SliceOutlineRenderer.h"
 #include "Layers/LineLayerRenderer.h"
-#include "Widgets/LayerWidget.h"
+#include "Widgets/SliceViewerWidget.h"
 #include "Toolbox/DownloadStack.h"
 #include "Toolbox/GeometryToolbox.h"
 #include "Toolbox/OrthancSlicesLoader.h"
@@ -702,7 +702,7 @@
       protected ISlicedVolume::IObserver
   {
   private:
-    LayerWidget&                        widget_;
+    SliceViewerWidget&                        widget_;
     VolumeProjection                    projection_;
     std::auto_ptr<VolumeImageGeometry>  slices_;
     size_t                              slice_;
@@ -799,7 +799,7 @@
 
   public:
     VolumeImageInteractor(OrthancVolumeImage& volume,
-                          LayerWidget& widget,
+                          SliceViewerWidget& widget,
                           VolumeProjection projection) :
       widget_(widget),
       projection_(projection)
@@ -894,10 +894,10 @@
       }
     };
 
-    LayerWidget&  otherPlane_;
+    SliceViewerWidget&  otherPlane_;
 
   public:
-    SliceLocationSource(MessageBroker& broker, LayerWidget&  otherPlane) :
+    SliceLocationSource(MessageBroker& broker, SliceViewerWidget&  otherPlane) :
       LayerSourceBase(broker),
       otherPlane_(otherPlane)
     {
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Nov 09 17:11:35 2018 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Nov 09 17:26:39 2018 +0100
@@ -287,9 +287,9 @@
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWidget.h
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneInteractor.h
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayerWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanMouseTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Widgets/SliceViewerWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestWorldSceneWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp
--- a/Resources/OrthancStone.doxygen	Fri Nov 09 17:11:35 2018 +0100
+++ b/Resources/OrthancStone.doxygen	Fri Nov 09 17:26:39 2018 +0100
@@ -655,7 +655,9 @@
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = @ORTHANC_STONE_ROOT@/Framework @ORTHANC_STONE_ROOT@/Platforms
+INPUT                  = @ORTHANC_STONE_ROOT@/Applications \
+                         @ORTHANC_STONE_ROOT@/Framework \
+                         @ORTHANC_STONE_ROOT@/Platforms
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is