changeset 1200:54cbffabdc45 broker

integration mainline->broker
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 29 Nov 2019 11:03:41 +0100
parents 4cc997207d8a (diff) 922d2e61aa5d (current diff)
children f3bb9a6dd949
files Applications/Samples/SingleFrameEditorApplication.h Applications/Sdl/SdlEngine.cpp Framework/Radiography/RadiographyAlphaLayer.h Framework/Radiography/RadiographyDicomLayer.cpp Framework/Radiography/RadiographyDicomLayer.h Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyMaskLayer.h Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/Radiography/RadiographyTextLayer.h Framework/Radiography/RadiographyWidget.cpp Framework/Radiography/RadiographyWidget.h Framework/StoneEnumerations.h
diffstat 193 files changed, 4030 insertions(+), 2648 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Generic/GuiAdapter.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Generic/GuiAdapter.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -792,7 +792,7 @@
     while (!stop)
     {
       {
-        LockingEmitter::WriterLock lock(lockingEmitter_);
+        Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
         if(func != NULL)
           (*func)(cookie);
         OnAnimationFrame(); // in SDL we must call it
@@ -802,7 +802,7 @@
 
       while (!stop && SDL_PollEvent(&event))
       {
-        LockingEmitter::WriterLock lock(lockingEmitter_);
+        Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
 
         if (event.type == SDL_QUIT)
         {
--- a/Applications/Generic/GuiAdapter.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Generic/GuiAdapter.h	Fri Nov 29 11:03:41 2019 +0100
@@ -95,7 +95,10 @@
   struct GuiAdapterWheelEvent;
   struct GuiAdapterKeyboardEvent;
 
-  class LockingEmitter;
+  namespace Deprecated
+  {
+    class LockingEmitter;
+  }
     
 #if 1
   typedef bool (*OnMouseEventFunc)(std::string canvasId, const GuiAdapterMouseEvent* mouseEvent, void* userData);
@@ -225,7 +228,7 @@
   {
   public:
 #if ORTHANC_ENABLE_THREADS == 1
-    GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter)
+    GuiAdapter(Deprecated::LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter)
 #else
     GuiAdapter()
 #endif
@@ -301,7 +304,7 @@
     This object is used by the multithreaded Oracle to serialize access to
     shared data. We need to use it as soon as we access the state.
     */
-    LockingEmitter& lockingEmitter_;
+    Deprecated::LockingEmitter& lockingEmitter_;
 #endif
 
     /**
--- a/Applications/Generic/NativeStoneApplicationContext.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Generic/NativeStoneApplicationContext.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -24,10 +24,10 @@
 
 namespace OrthancStone
 {
-  Deprecated::IWidget& NativeStoneApplicationContext::GlobalMutexLocker::SetCentralWidget(Deprecated::IWidget* widget)
+  void NativeStoneApplicationContext::GlobalMutexLocker::SetCentralWidget(
+    boost::shared_ptr<Deprecated::IWidget> widget)
   {
     that_.centralViewport_.SetCentralWidget(widget);
-    return *widget;
   }
 
 
@@ -45,9 +45,7 @@
   }
   
 
-  NativeStoneApplicationContext::NativeStoneApplicationContext(MessageBroker& broker) :
-    StoneApplicationContext(broker),
-    centralViewport_(broker),
+  NativeStoneApplicationContext::NativeStoneApplicationContext() :
     stopped_(true),
     updateDelayInMs_(100)   // By default, 100ms between each refresh of the content
   {
--- a/Applications/Generic/NativeStoneApplicationContext.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Generic/NativeStoneApplicationContext.h	Fri Nov 29 11:03:41 2019 +0100
@@ -56,7 +56,7 @@
       {
       }
 
-      Deprecated::IWidget& SetCentralWidget(Deprecated::IWidget* widget);   // Takes ownership
+      void SetCentralWidget(boost::shared_ptr<Deprecated::IWidget> widget);
 
       Deprecated::IViewport& GetCentralViewport() 
       {
@@ -67,14 +67,9 @@
       {
         that_.updateDelayInMs_ = delayInMs;
       }
-
-      MessageBroker& GetMessageBroker()
-      {
-        return that_.GetMessageBroker();
-      }
     };
 
-    NativeStoneApplicationContext(MessageBroker& broker);
+    NativeStoneApplicationContext();
 
     void Start();
 
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Generic/NativeStoneApplicationRunner.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -97,7 +97,7 @@
     DeclareCommandLineOptions(options);
     
     // application specific options
-    application_.DeclareStartupOptions(options);
+    application_->DeclareStartupOptions(options);
 
     boost::program_options::variables_map parameters;
     bool error = false;
@@ -197,7 +197,7 @@
 
       LogStatusBar statusBar;
 
-      NativeStoneApplicationContext context(broker_);
+      NativeStoneApplicationContext context;
 
       {
         // use multiple threads to execute asynchronous tasks like 
@@ -206,24 +206,23 @@
         oracle.Start();
 
         {
-          Deprecated::OracleWebService webService(
-            broker_, oracle, webServiceParameters, context);
-          
+          boost::shared_ptr<Deprecated::OracleWebService> webService
+            (new Deprecated::OracleWebService(oracle, webServiceParameters, context));
           context.SetWebService(webService);
           context.SetOrthancBaseUrl(webServiceParameters.GetUrl());
 
-          Deprecated::OracleDelayedCallExecutor delayedExecutor(broker_, oracle, context);
+          Deprecated::OracleDelayedCallExecutor delayedExecutor(oracle, context);
           context.SetDelayedCallExecutor(delayedExecutor);
 
-          application_.Initialize(&context, statusBar, parameters);
+          application_->Initialize(&context, statusBar, parameters);
 
           {
             NativeStoneApplicationContext::GlobalMutexLocker locker(context);
-            locker.SetCentralWidget(application_.GetCentralWidget());
+            locker.SetCentralWidget(application_->GetCentralWidget());
             locker.GetCentralViewport().SetStatusBar(statusBar);
           }
 
-          std::string title = application_.GetTitle();
+          std::string title = application_->GetTitle();
           if (title.empty())
           {
             title = "Stone of Orthanc";
@@ -244,7 +243,7 @@
       }
 
       LOG(WARNING) << "The application is stopping";
-      application_.Finalize();
+      application_->Finalize();
     }
     catch (Orthanc::OrthancException& e)
     {
--- a/Applications/Generic/NativeStoneApplicationRunner.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Generic/NativeStoneApplicationRunner.h	Fri Nov 29 11:03:41 2019 +0100
@@ -34,14 +34,11 @@
   class NativeStoneApplicationRunner
   {
   protected:
-    MessageBroker&      broker_;
-    IStoneApplication&  application_;
+    boost::shared_ptr<IStoneApplication>  application_;
+    
   public:
-
-    NativeStoneApplicationRunner(MessageBroker& broker,
-                                 IStoneApplication& application)
-      : broker_(broker),
-        application_(application)
+    NativeStoneApplicationRunner(boost::shared_ptr<IStoneApplication> application)
+      : application_(application)
     {
     }
     int Execute(int argc,
--- a/Applications/IStoneApplication.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/IStoneApplication.h	Fri Nov 29 11:03:41 2019 +0100
@@ -64,7 +64,11 @@
 #endif
 
     virtual std::string GetTitle() const = 0;
-    virtual Deprecated::IWidget* GetCentralWidget() = 0;
+    
+    virtual void SetCentralWidget(boost::shared_ptr<Deprecated::IWidget> widget) = 0;
+    
+    virtual boost::shared_ptr<Deprecated::IWidget> GetCentralWidget() = 0;
+    
     virtual void Finalize() = 0;
   };
 }
--- a/Applications/Samples/SampleApplicationBase.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Samples/SampleApplicationBase.h	Fri Nov 29 11:03:41 2019 +0100
@@ -40,9 +40,8 @@
   {
     class SampleApplicationBase : public IStoneApplication
     {
-    protected:
-      // ownership is transferred to the application context
-      Deprecated::WorldSceneWidget*  mainWidget_;
+    private:
+      boost::shared_ptr<Deprecated::IWidget>  mainWidget_;
 
     public:
       virtual void Initialize(StoneApplicationContext* context,
@@ -64,7 +63,16 @@
 
 
       virtual void Finalize() ORTHANC_OVERRIDE {}
-      virtual Deprecated::IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;}
+
+      virtual void SetCentralWidget(boost::shared_ptr<Deprecated::IWidget> widget) ORTHANC_OVERRIDE
+      {
+        mainWidget_ = widget;
+      }
+
+      virtual boost::shared_ptr<Deprecated::IWidget> GetCentralWidget() ORTHANC_OVERRIDE
+      {
+        return mainWidget_;
+      }
 
 #if ORTHANC_ENABLE_WASM==1
       // default implementations for a single canvas named "canvas" in the HTML and an emtpy WasmApplicationAdapter
--- a/Applications/Samples/SampleMainNative.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Samples/SampleMainNative.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,19 +26,18 @@
 #if ORTHANC_ENABLE_QT==1
 #include "Qt/SampleQtApplicationRunner.h"
 #endif
-#include "../../Framework/Messages/MessageBroker.h"
 
 int main(int argc, char* argv[]) 
 {
-  OrthancStone::MessageBroker broker;
-  SampleApplication sampleStoneApplication(broker);
+  boost::shared_ptr<SampleApplication> sampleStoneApplication(new SampleApplication);
 
 #if ORTHANC_ENABLE_SDL==1
-  OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication);
+  OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(sampleStoneApplication);
   return sdlApplicationRunner.Execute(argc, argv);
 #endif
+  
 #if ORTHANC_ENABLE_QT==1
-  OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication);
+  OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(sampleStoneApplication);
   return qtAppRunner.Execute(argc, argv);
 #endif
 }
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h	Fri Nov 29 11:03:41 2019 +0100
@@ -44,7 +44,7 @@
   {
     class SimpleViewerApplication :
       public SampleSingleCanvasWithButtonsApplicationBase,
-      public IObserver
+      public ObserverBase<SimpleViewerApplication>
     {
     private:
       class ThumbnailInteractor : public Deprecated::IWorldSceneInteractor
@@ -199,8 +199,8 @@
         SimpleViewerApplication&  viewerApplication_;
 
       public:
-        SimpleViewerApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application)
-          : WasmPlatformApplicationAdapter(broker, application),
+        SimpleViewerApplicationAdapter(SimpleViewerApplication& application)
+          : WasmPlatformApplicationAdapter(application),
             viewerApplication_(application)
         {
         }
@@ -243,7 +243,7 @@
       std::auto_ptr<ThumbnailInteractor>   thumbnailInteractor_;
       Deprecated::LayoutWidget*                        mainLayout_;
       Deprecated::LayoutWidget*                        thumbnailsLayout_;
-      std::vector<Deprecated::SliceViewerWidget*>      thumbnails_;
+      std::vector<boost::shared_ptr<Deprecated::SliceViewerWidget> >      thumbnails_;
 
       std::map<std::string, std::vector<std::string> > instancesIdsPerSeriesId_;
       std::map<std::string, Json::Value> seriesTags_;
@@ -258,8 +258,7 @@
       Orthanc::Font                        font_;
 
     public:
-      SimpleViewerApplication(MessageBroker& broker) :
-        IObserver(broker),
+      SimpleViewerApplication() :
         currentTool_(Tool_LineMeasure),
         mainLayout_(NULL),
         currentInstanceIndex_(0),
@@ -297,26 +296,28 @@
           mainLayout_->SetBackgroundColor(0, 0, 0);
           mainLayout_->SetHorizontal();
 
-          thumbnailsLayout_ = new Deprecated::LayoutWidget("thumbnail-layout");
+          boost::shared_ptr<Deprecated::LayoutWidget> thumbnailsLayout_(new Deprecated::LayoutWidget("thumbnail-layout"));
           thumbnailsLayout_->SetPadding(10);
           thumbnailsLayout_->SetBackgroundCleared(true);
           thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
           thumbnailsLayout_->SetVertical();
 
-          mainWidget_ = new Deprecated::SliceViewerWidget(GetBroker(), "main-viewport");
+          boost::shared_ptr<Deprecated::SliceViewerWidget> widget
+            (new Deprecated::SliceViewerWidget("main-viewport"));
+          SetCentralWidget(widget);
           //mainWidget_->RegisterObserver(*this);
 
           // hierarchy
           mainLayout_->AddWidget(thumbnailsLayout_);
-          mainLayout_->AddWidget(mainWidget_);
+          mainLayout_->AddWidget(widget);
 
           // sources
-          smartLoader_.reset(new Deprecated::SmartLoader(GetBroker(), context->GetOrthancApiClient()));
+          smartLoader_.reset(new Deprecated::SmartLoader(context->GetOrthancApiClient()));
           smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam);
 
           mainLayout_->SetTransmitMouseOver(true);
           mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
-          mainWidget_->SetInteractor(*mainWidgetInteractor_);
+          widget->SetInteractor(*mainWidgetInteractor_);
           thumbnailInteractor_.reset(new ThumbnailInteractor(*this));
         }
 
@@ -327,10 +328,10 @@
         if (parameters.count("studyId") < 1)
         {
           LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
-          context->GetOrthancApiClient().GetJsonAsync(
+          context->GetOrthancApiClient()->GetJsonAsync(
             "/studies",
             new Callable<SimpleViewerApplication, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
-            (*this, &SimpleViewerApplication::OnStudyListReceived));
+            (GetSharedObserver(), &SimpleViewerApplication::OnStudyListReceived));
         }
         else
         {
@@ -357,10 +358,10 @@
         {
           for (size_t i=0; i < response["Series"].size(); i++)
           {
-            context_->GetOrthancApiClient().GetJsonAsync(
+            context_->GetOrthancApiClient()->GetJsonAsync(
               "/series/" + response["Series"][(int)i].asString(),
               new Callable<SimpleViewerApplication, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
-              (*this, &SimpleViewerApplication::OnSeriesReceived));
+              (GetSharedObserver(), &SimpleViewerApplication::OnSeriesReceived));
           }
         }
       }
@@ -387,7 +388,7 @@
           LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]);
 
           // if this is the first thumbnail loaded, load the first instance in the mainWidget
-          Deprecated::SliceViewerWidget& widget = *dynamic_cast<Deprecated::SliceViewerWidget*>(mainWidget_);
+          Deprecated::SliceViewerWidget& widget = dynamic_cast<Deprecated::SliceViewerWidget&>(*GetCentralWidget());
           if (widget.GetLayerCount() == 0)
           {
             smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
@@ -398,10 +399,10 @@
       void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
       {
         LOG(INFO) << "Loading thumbnail for series " << seriesId;
-        Deprecated::SliceViewerWidget* thumbnailWidget = new Deprecated::SliceViewerWidget(GetBroker(), "thumbnail-series-" + seriesId);
+        boost::shared_ptr<Deprecated::SliceViewerWidget> thumbnailWidget(new Deprecated::SliceViewerWidget("thumbnail-series-" + seriesId));
         thumbnails_.push_back(thumbnailWidget);
         thumbnailsLayout_->AddWidget(thumbnailWidget);
-        thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, Deprecated::SliceViewerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
+        Register<Deprecated::SliceViewerWidget::GeometryChangedMessage>(*thumbnailWidget, &SimpleViewerApplication::OnWidgetGeometryChanged);
         smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0);
         thumbnailWidget->SetInteractor(*thumbnailInteractor_);
       }
@@ -409,9 +410,9 @@
       void SelectStudy(const std::string& studyId)
       {
         LOG(INFO) << "Selecting study: " << studyId;
-        context_->GetOrthancApiClient().GetJsonAsync(
+        context_->GetOrthancApiClient()->GetJsonAsync(
           "/studies/" + studyId, new Callable<SimpleViewerApplication, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
-          (*this, &SimpleViewerApplication::OnStudyReceived));
+          (GetSharedObserver(), &SimpleViewerApplication::OnStudyReceived));
       }
 
       void OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message)
@@ -422,7 +423,7 @@
 
       void SelectSeriesInMainViewport(const std::string& seriesId)
       {
-        Deprecated::SliceViewerWidget& widget = *dynamic_cast<Deprecated::SliceViewerWidget*>(mainWidget_);
+        Deprecated::SliceViewerWidget& widget = dynamic_cast<Deprecated::SliceViewerWidget&>(*GetCentralWidget());
         smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
       }
 
@@ -451,7 +452,7 @@
       virtual void InitializeWasm()
       {
         AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
-        AttachWidgetToWasmViewport("canvas2", mainWidget_);
+        AttachWidgetToWasmViewport("canvas2", widget);
       }
 #endif
 
--- a/Applications/Samples/SingleFrameApplication.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Samples/SingleFrameApplication.h	Fri Nov 29 11:03:41 2019 +0100
@@ -38,7 +38,7 @@
   {
     class SingleFrameApplication :
       public SampleSingleCanvasApplicationBase,
-      public IObserver
+      public ObserverBase<SingleFrameApplication>
     {
     private:
       class Interactor : public Deprecated::IWorldSceneInteractor
@@ -127,7 +127,7 @@
 
       void OffsetSlice(int offset)
       {
-        if (source_ != NULL)
+        if (source_)
         {
           int slice = static_cast<int>(slice_) + offset;
 
@@ -149,21 +149,15 @@
       }
 
 
-      Deprecated::SliceViewerWidget& GetMainWidget()
-      {
-        return *dynamic_cast<Deprecated::SliceViewerWidget*>(mainWidget_);
-      }
-      
-
       void SetSlice(size_t index)
       {
-        if (source_ != NULL &&
+        if (source_ &&
             index < source_->GetSlicesCount())
         {
           slice_ = static_cast<unsigned int>(index);
           
 #if 1
-          GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry());
+          widget_->SetSlice(source_->GetSlice(slice_).GetGeometry());
 #else
           // TEST for scene extents - Rotate the axes
           double a = 15.0 / 180.0 * boost::math::constants::pi<double>();
@@ -189,22 +183,22 @@
         // Once the geometry of the series is downloaded from Orthanc,
         // display its middle slice, and adapt the viewport to fit this
         // slice
-        if (source_ == &message.GetOrigin())
+        if (source_ &&
+            source_.get() == &message.GetOrigin())
         {
           SetSlice(source_->GetSlicesCount() / 2);
         }
 
-        GetMainWidget().FitContent();
+        widget_->FitContent();
       }
-      
+
+      boost::shared_ptr<Deprecated::SliceViewerWidget>  widget_;
       std::auto_ptr<Interactor>         mainWidgetInteractor_;
-      const Deprecated::DicomSeriesVolumeSlicer*    source_;
+      boost::shared_ptr<Deprecated::DicomSeriesVolumeSlicer> source_;
       unsigned int                      slice_;
 
     public:
-      SingleFrameApplication(MessageBroker& broker) :
-        IObserver(broker),
-        source_(NULL),
+      SingleFrameApplication() :
         slice_(0)
       {
       }
@@ -243,13 +237,15 @@
         std::string instance = parameters["instance"].as<std::string>();
         int frame = parameters["frame"].as<unsigned int>();
 
-        mainWidget_ = new Deprecated::SliceViewerWidget(GetBroker(), "main-widget");
+        widget_.reset(new Deprecated::SliceViewerWidget("main-widget"));
+        SetCentralWidget(widget_);
 
-        std::auto_ptr<Deprecated::DicomSeriesVolumeSlicer> layer(new Deprecated::DicomSeriesVolumeSlicer(GetBroker(), context->GetOrthancApiClient()));
-        source_ = layer.get();
+        boost::shared_ptr<Deprecated::DicomSeriesVolumeSlicer> layer(new Deprecated::DicomSeriesVolumeSlicer);
+        layer->Connect(context->GetOrthancApiClient());
+        source_ = layer;
         layer->LoadFrame(instance, frame);
-        layer->RegisterObserverCallback(new Callable<SingleFrameApplication, Deprecated::IVolumeSlicer::GeometryReadyMessage>(*this, &SingleFrameApplication::OnMainWidgetGeometryReady));
-        GetMainWidget().AddLayer(layer.release());
+        Register<Deprecated::IVolumeSlicer::GeometryReadyMessage>(*layer, &SingleFrameApplication::OnMainWidgetGeometryReady);
+        widget_->AddLayer(layer);
 
         Deprecated::RenderStyle s;
 
@@ -258,11 +254,11 @@
           s.interpolation_ = ImageInterpolation_Bilinear;
         }
 
-        GetMainWidget().SetLayerStyle(0, s);
-        GetMainWidget().SetTransmitMouseOver(true);
+        widget_->SetLayerStyle(0, s);
+        widget_->SetTransmitMouseOver(true);
 
         mainWidgetInteractor_.reset(new Interactor(*this));
-        GetMainWidget().SetInteractor(*mainWidgetInteractor_);
+        widget_->SetInteractor(*mainWidgetInteractor_);
       }
     };
 
--- a/Applications/Samples/SingleFrameEditorApplication.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,13 +28,13 @@
 #include "../../Framework/Radiography/RadiographyLayerMoveTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerResizeTracker.h"
 #include "../../Framework/Radiography/RadiographyLayerRotateTracker.h"
+#include "../../Framework/Radiography/RadiographyMaskLayer.h"
 #include "../../Framework/Radiography/RadiographyScene.h"
 #include "../../Framework/Radiography/RadiographySceneCommand.h"
+#include "../../Framework/Radiography/RadiographySceneReader.h"
+#include "../../Framework/Radiography/RadiographySceneWriter.h"
 #include "../../Framework/Radiography/RadiographyWidget.h"
 #include "../../Framework/Radiography/RadiographyWindowingTracker.h"
-#include "../../Framework/Radiography/RadiographySceneWriter.h"
-#include "../../Framework/Radiography/RadiographySceneReader.h"
-#include "../../Framework/Radiography/RadiographyMaskLayer.h"
 #include "../../Framework/Toolbox/TextRenderer.h"
 
 #include <Core/HttpClient.h>
@@ -55,7 +55,7 @@
   {
     class RadiographyEditorInteractor :
         public Deprecated::IWorldSceneInteractor,
-        public IObserver
+        public ObserverBase<RadiographyEditorInteractor>
     {
     private:
       enum Tool
@@ -82,8 +82,7 @@
 
 
     public:
-      RadiographyEditorInteractor(MessageBroker& broker) :
-        IObserver(broker),
+      RadiographyEditorInteractor() :
         context_(NULL),
         tool_(Tool_Move),
         maskLayer_(NULL)
@@ -315,7 +314,7 @@
           LOG(INFO) << "JSON export was successful: "
                     << snapshot.toStyledString();
 
-          boost::shared_ptr<RadiographyScene> scene(new RadiographyScene(GetBroker()));
+          boost::shared_ptr<RadiographyScene> scene(new RadiographyScene);
           RadiographySceneReader reader(*scene, context_->GetOrthancApiClient());
           reader.Read(snapshot);
 
@@ -346,7 +345,7 @@
 
           if (context_ != NULL)
           {
-            widget.GetScene().ExportDicom(context_->GetOrthancApiClient(),
+            widget.GetScene().ExportDicom(*context_->GetOrthancApiClient(),
                                           tags, std::string(), 0.1, 0.1, widget.IsInverted(),
                                           widget.GetInterpolation(), EXPORT_USING_PAM);
           }
@@ -426,16 +425,9 @@
     private:
       boost::shared_ptr<RadiographyScene>   scene_;
       RadiographyEditorInteractor           interactor_;
-      Orthanc::FontRegistry                 fontRegistry_;
       RadiographyMaskLayer*                 maskLayer_;
 
     public:
-      SingleFrameEditorApplication(MessageBroker& broker) :
-        IObserver(broker),
-        interactor_(broker)
-      {
-      }
-
       virtual ~SingleFrameEditorApplication()
       {
         LOG(WARNING) << "Destroying the application";
@@ -487,9 +479,9 @@
         std::string instance = parameters["instance"].as<std::string>();
         //int frame = parameters["frame"].as<unsigned int>();
 
-        scene_.reset(new RadiographyScene(GetBroker()));
+        scene_.reset(new RadiographyScene);
         
-        RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL);
+        RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(*context->GetOrthancApiClient(), instance, 0, false, NULL);
         //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0);
         // = scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL);
 
@@ -527,10 +519,10 @@
           layer.SetPan(0, 200);
         }
         
-        
-        mainWidget_ = new RadiographyWidget(GetBroker(), scene_, "main-widget");
-        mainWidget_->SetTransmitMouseOver(true);
-        mainWidget_->SetInteractor(interactor_);
+        boost::shared_ptr<RadiographyWidget> widget(new RadiographyWidget(scene_, "main-widget"));
+        widget->SetTransmitMouseOver(true);
+        widget->SetInteractor(interactor_);
+        SetCentralWidget(widget);
 
         //scene_->SetWindowing(128, 256);
       }
--- a/Applications/Sdl/SdlCairoSurface.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Sdl/SdlCairoSurface.h	Fri Nov 29 11:03:41 2019 +0100
@@ -27,6 +27,8 @@
 #include "../../Framework/Wrappers/CairoSurface.h"
 #include "../../Framework/Deprecated/Viewport/IViewport.h"
 
+#include <SDL_render.h>
+
 #include <boost/thread/mutex.hpp>
 
 namespace OrthancStone
--- a/Applications/Sdl/SdlEngine.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Sdl/SdlEngine.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -99,9 +99,7 @@
 
 
   SdlEngine::SdlEngine(SdlWindow& window,
-                       NativeStoneApplicationContext& context,
-                       MessageBroker& broker) :
-    IObserver(broker),
+                       NativeStoneApplicationContext& context) :
     window_(window),
     context_(context),
     surface_(window),
--- a/Applications/Sdl/SdlEngine.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Sdl/SdlEngine.h	Fri Nov 29 11:03:41 2019 +0100
@@ -23,12 +23,13 @@
 
 #if ORTHANC_ENABLE_SDL == 1
 
+#include "../../Framework/Messages/ObserverBase.h"
+#include "../Generic/NativeStoneApplicationContext.h"
 #include "SdlCairoSurface.h"
-#include "../Generic/NativeStoneApplicationContext.h"
 
 namespace OrthancStone
 {
-  class SdlEngine : public IObserver
+  class SdlEngine : public ObserverBase<SdlEngine>
   {
   private:
     SdlWindow&                window_;
@@ -46,8 +47,7 @@
 
   public:
     SdlEngine(SdlWindow& window,
-              NativeStoneApplicationContext& context,
-              MessageBroker& broker);
+              NativeStoneApplicationContext& context);
   
     void OnViewportChanged(const Deprecated::IViewport::ViewportChangedMessage& message)
     {
--- a/Applications/Sdl/SdlOrthancSurface.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Sdl/SdlOrthancSurface.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -27,6 +27,8 @@
 #include <Core/OrthancException.h>
 #include <Core/Images/Image.h>
 
+#include <SDL_render.h>
+
 namespace OrthancStone
 {
   SdlOrthancSurface::SdlOrthancSurface(SdlWindow& window) :
--- a/Applications/Sdl/SdlStoneApplicationRunner.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -103,20 +103,19 @@
     LOG(WARNING) << "Starting the application";
 
     SdlWindow window(title.c_str(), width_, height_, enableOpenGl_);
-    SdlEngine sdl(window, context, broker_);
+    boost::shared_ptr<SdlEngine> sdl(new SdlEngine(window, context));
 
     {
       NativeStoneApplicationContext::GlobalMutexLocker locker(context);
 
-      locker.GetCentralViewport().RegisterObserverCallback(
-        new Callable<SdlEngine, Deprecated::IViewport::ViewportChangedMessage>
-        (sdl, &SdlEngine::OnViewportChanged));
+      sdl->Register<Deprecated::IViewport::ViewportChangedMessage>
+        (locker.GetCentralViewport(), &SdlEngine::OnViewportChanged);
 
       //context.GetCentralViewport().Register(sdl);  // (*)
     }
 
     context.Start();
-    sdl.Run();
+    sdl->Run();
 
     LOG(WARNING) << "Stopping the application";
 
--- a/Applications/Sdl/SdlStoneApplicationRunner.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/Sdl/SdlStoneApplicationRunner.h	Fri Nov 29 11:03:41 2019 +0100
@@ -39,9 +39,8 @@
     bool          enableOpenGl_;
     
   public:
-    SdlStoneApplicationRunner(MessageBroker& broker,
-                              IStoneApplication& application) :
-      NativeStoneApplicationRunner(broker, application)
+    SdlStoneApplicationRunner(boost::shared_ptr<IStoneApplication> application) :
+      NativeStoneApplicationRunner(application)
     {
     }
 
--- a/Applications/StoneApplicationContext.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/StoneApplicationContext.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -32,35 +32,35 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
 
-    orthanc_.reset(new Deprecated::OrthancApiClient(broker_, *webService_, orthancBaseUrl_));
+    orthanc_.reset(new Deprecated::OrthancApiClient(*webService_, orthancBaseUrl_));
   }
 
 
-  Deprecated::IWebService& StoneApplicationContext::GetWebService()
+  boost::shared_ptr<Deprecated::IWebService> StoneApplicationContext::GetWebService()
   {
     if (webService_ == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
     
-    return *webService_;
+    return webService_;
   }
 
   
-  Deprecated::OrthancApiClient& StoneApplicationContext::GetOrthancApiClient()
+  boost::shared_ptr<Deprecated::OrthancApiClient> StoneApplicationContext::GetOrthancApiClient()
   {
     if (orthanc_.get() == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
     
-    return *orthanc_;
+    return orthanc_;
   }
 
   
-  void StoneApplicationContext::SetWebService(Deprecated::IWebService& webService)
+  void StoneApplicationContext::SetWebService(boost::shared_ptr<Deprecated::IWebService> webService)
   {
-    webService_ = &webService;
+    webService_ = webService;
     InitializeOrthanc();
   }
 
--- a/Applications/StoneApplicationContext.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Applications/StoneApplicationContext.h	Fri Nov 29 11:03:41 2019 +0100
@@ -59,18 +59,15 @@
   class StoneApplicationContext : public boost::noncopyable
   {
   private:
-    MessageBroker&                   broker_;
-    Deprecated::IWebService*         webService_;
-    Deprecated::IDelayedCallExecutor*            delayedCallExecutor_;
-    std::auto_ptr<Deprecated::OrthancApiClient>  orthanc_;
+    boost::shared_ptr<Deprecated::IWebService>     webService_;
+    Deprecated::IDelayedCallExecutor*  delayedCallExecutor_;   // TODO => shared_ptr ??
+    boost::shared_ptr<Deprecated::OrthancApiClient>  orthanc_;
     std::string                      orthancBaseUrl_;
 
     void InitializeOrthanc();
 
   public:
-    StoneApplicationContext(MessageBroker& broker) :
-      broker_(broker),
-      webService_(NULL),
+    StoneApplicationContext() :
       delayedCallExecutor_(NULL)
     {
     }
@@ -79,21 +76,11 @@
     {
     }
 
-    MessageBroker& GetMessageBroker()
-    {
-      return broker_;
-    }
+    boost::shared_ptr<Deprecated::IWebService> GetWebService();
 
-    bool HasWebService() const
-    {
-      return webService_ != NULL;
-    }
+    boost::shared_ptr<Deprecated::OrthancApiClient> GetOrthancApiClient();
 
-    Deprecated::IWebService& GetWebService();
-
-    Deprecated::OrthancApiClient& GetOrthancApiClient();
-
-    void SetWebService(Deprecated::IWebService& webService);
+    void SetWebService(boost::shared_ptr<Deprecated::IWebService> webService);
 
     void SetOrthancBaseUrl(const std::string& baseUrl);
 
--- a/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -87,59 +87,73 @@
   }
 
 
-  DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker,
-                                                   OrthancApiClient& orthanc) :
-    IVolumeSlicer(broker),
-    IObserver(broker),
-    loader_(broker, orthanc),
+  DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer() :
     quality_(SliceImageQuality_FullPng)
   {
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage>
-        (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady));
-
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage>
-      (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError));
+  }
 
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage>
-        (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady));
-
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage>
-      (*this, &DicomSeriesVolumeSlicer::OnSliceImageError));
+  void DicomSeriesVolumeSlicer::Connect(boost::shared_ptr<OrthancApiClient> orthanc)
+  {
+    loader_.reset(new OrthancSlicesLoader(orthanc));
+    Register<OrthancSlicesLoader::SliceGeometryReadyMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceGeometryReady);
+    Register<OrthancSlicesLoader::SliceGeometryErrorMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceGeometryError);
+    Register<OrthancSlicesLoader::SliceImageReadyMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceImageReady);
+    Register<OrthancSlicesLoader::SliceImageErrorMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceImageError);
   }
 
   
   void DicomSeriesVolumeSlicer::LoadSeries(const std::string& seriesId)
   {
-    loader_.ScheduleLoadSeries(seriesId);
+    if (loader_.get() == NULL)
+    {
+      // Should have called "Connect()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    loader_->ScheduleLoadSeries(seriesId);
   }
 
 
   void DicomSeriesVolumeSlicer::LoadInstance(const std::string& instanceId)
   {
-    loader_.ScheduleLoadInstance(instanceId);
+    if (loader_.get() == NULL)
+    {
+      // Should have called "Connect()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    loader_->ScheduleLoadInstance(instanceId);
   }
 
 
   void DicomSeriesVolumeSlicer::LoadFrame(const std::string& instanceId,
                                           unsigned int frame)
   {
-    loader_.ScheduleLoadFrame(instanceId, frame);
+    if (loader_.get() == NULL)
+    {
+      // Should have called "Connect()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    loader_->ScheduleLoadFrame(instanceId, frame);
   }
 
 
   bool DicomSeriesVolumeSlicer::GetExtent(std::vector<OrthancStone::Vector>& points,
                                           const OrthancStone::CoordinateSystem3D& viewportSlice)
   {
+    if (loader_.get() == NULL)
+    {
+      // Should have called "Connect()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
     size_t index;
 
-    if (loader_.IsGeometryReady() &&
-        loader_.LookupSlice(index, viewportSlice))
+    if (loader_->IsGeometryReady() &&
+        loader_->LookupSlice(index, viewportSlice))
     {
-      loader_.GetSlice(index).GetExtent(points);
+      loader_->GetSlice(index).GetExtent(points);
       return true;
     }
     else
@@ -151,12 +165,18 @@
   
   void DicomSeriesVolumeSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
   {
+    if (loader_.get() == NULL)
+    {
+      // Should have called "Connect()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
     size_t index;
 
-    if (loader_.IsGeometryReady() &&
-        loader_.LookupSlice(index, viewportSlice))
+    if (loader_->IsGeometryReady() &&
+        loader_->LookupSlice(index, viewportSlice))
     {
-      loader_.ScheduleLoadSliceImage(index, quality_);
+      loader_->ScheduleLoadSliceImage(index, quality_);
     }
   }
 }
--- a/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "IVolumeSlicer.h"
+#include "../../Messages/ObserverBase.h"
 #include "../Toolbox/IWebService.h"
 #include "../Toolbox/OrthancSlicesLoader.h"
 #include "../Toolbox/OrthancApiClient.h"
@@ -33,7 +34,7 @@
   // messages are sent to observers so they can use it
   class DicomSeriesVolumeSlicer :
     public IVolumeSlicer,
-    public OrthancStone::IObserver
+    public OrthancStone::ObserverBase<DicomSeriesVolumeSlicer>
     //private OrthancSlicesLoader::ISliceLoaderObserver
   {
   public:
@@ -79,13 +80,14 @@
   private:
     class RendererFactory;
     
-    OrthancSlicesLoader  loader_;
+    boost::shared_ptr<OrthancSlicesLoader> loader_;
     SliceImageQuality    quality_;
 
   public:
-    DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker,
-                            OrthancApiClient& orthanc);
+    DicomSeriesVolumeSlicer();
 
+    void Connect(boost::shared_ptr<OrthancApiClient> orthanc);
+    
     void LoadSeries(const std::string& seriesId);
 
     void LoadInstance(const std::string& instanceId);
@@ -105,12 +107,12 @@
 
     size_t GetSlicesCount() const
     {
-      return loader_.GetSlicesCount();
+      return loader_->GetSlicesCount();
     }
 
     const Slice& GetSlice(size_t slice) const 
     {
-      return loader_.GetSlice(slice);
+      return loader_->GetSlice(slice);
     }
 
     virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
--- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -20,6 +20,8 @@
 
 #include "DicomStructureSetSlicer.h"
 
+#include "../../Toolbox/DicomStructureSet.h"
+
 namespace Deprecated
 {
   class DicomStructureSetSlicer::Renderer : public ILayerRenderer
@@ -28,13 +30,18 @@
     class Structure
     {
     private:
-      bool                                                                   visible_;
-      uint8_t                                                                red_;
-      uint8_t                                                                green_;
-      uint8_t                                                                blue_;
-      std::string                                                            name_;
+      bool         visible_;
+      uint8_t      red_;
+      uint8_t      green_;
+      uint8_t      blue_;
+      std::string  name_;
+
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
+      std::vector< std::vector<OrthancStone::Point2D> > polygons_;
+#else
       std::vector< std::pair<OrthancStone::Point2D, OrthancStone::Point2D> > segments_;
-
+#endif
+      
     public:
       Structure(OrthancStone::DicomStructureSet& structureSet,
                 const OrthancStone::CoordinateSystem3D& plane,
@@ -42,7 +49,12 @@
         name_(structureSet.GetStructureName(index))
       {
         structureSet.GetStructureColor(red_, green_, blue_, index);
+
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
+        visible_ = structureSet.ProjectStructure(polygons_, index, plane);
+#else
         visible_ = structureSet.ProjectStructure(segments_, index, plane);
+#endif
       }
 
       void Render(OrthancStone::CairoContext& context)
@@ -53,12 +65,25 @@
         
           context.SetSourceColor(red_, green_, blue_);
 
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
+          for (size_t i = 0; i < polygons_.size(); i++)
+          {
+            cairo_move_to(cr, polygons_[i][0].x, polygons_[i][0].y);
+            for (size_t j = 0; j < polygons_[i].size(); j++)
+            {
+              cairo_line_to(cr, polygons_[i][j].x, polygons_[i][j].y);
+            }
+            cairo_line_to(cr, polygons_[i][0].x, polygons_[i][0].y);
+            cairo_stroke(cr);
+          }
+#else
           for (size_t i = 0; i < segments_.size(); i++)
           {
             cairo_move_to(cr, segments_[i].first.x, segments_[i].first.y);
-            cairo_move_to(cr, segments_[i].second.x, segments_[i].second.y);
+            cairo_line_to(cr, segments_[i].second.x, segments_[i].second.y);
             cairo_stroke(cr);
           }
+#endif
         }
       }
     };
@@ -140,15 +165,10 @@
   };
   
 
-  DicomStructureSetSlicer::DicomStructureSetSlicer(OrthancStone::MessageBroker& broker,
-                                                   StructureSetLoader& loader) :
-    IVolumeSlicer(broker),
-    IObserver(broker),
+  DicomStructureSetSlicer::DicomStructureSetSlicer(StructureSetLoader& loader) :
     loader_(loader)
   {
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomStructureSetSlicer, StructureSetLoader::ContentChangedMessage>
-      (*this, &DicomStructureSetSlicer::OnStructureSetLoaded));
+    Register<StructureSetLoader::ContentChangedMessage>(loader_, &DicomStructureSetSlicer::OnStructureSetLoaded);
   }
 
 
--- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,7 +28,7 @@
 {
   class DicomStructureSetSlicer :
     public IVolumeSlicer,
-    public OrthancStone::IObserver
+    public OrthancStone::ObserverBase<DicomStructureSetSlicer>
   {
   private:
     class Renderer;
@@ -42,8 +42,7 @@
     }
 
   public:
-    DicomStructureSetSlicer(OrthancStone::MessageBroker& broker,
-                            StructureSetLoader& loader);
+    DicomStructureSetSlicer(StructureSetLoader& loader);
 
     virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
                            const OrthancStone::CoordinateSystem3D& viewportPlane)
--- a/Framework/Deprecated/Layers/IVolumeSlicer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Layers/IVolumeSlicer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -122,11 +122,6 @@
     };
 
 
-    IVolumeSlicer(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-    
     virtual ~IVolumeSlicer()
     {
     }
--- a/Framework/Deprecated/SmartLoader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/SmartLoader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -21,7 +21,6 @@
 
 #include "SmartLoader.h"
 
-#include "../Messages/MessageForwarder.h"
 #include "../StoneException.h"
 #include "Core/Images/Image.h"
 #include "Core/Logging.h"
@@ -68,11 +67,6 @@
     CachedSliceStatus               status_;
 
   public:
-    CachedSlice(OrthancStone::MessageBroker& broker) :
-    IVolumeSlicer(broker)
-    {
-    }
-
     virtual ~CachedSlice()
     {
     }
@@ -106,7 +100,7 @@
 
     CachedSlice* Clone() const
     {
-      CachedSlice* output = new CachedSlice(GetBroker());
+      CachedSlice* output = new CachedSlice;
       output->sliceIndex_ = sliceIndex_;
       output->slice_.reset(slice_->Clone());
       output->image_ = image_;
@@ -119,10 +113,7 @@
   };
 
 
-  SmartLoader::SmartLoader(OrthancStone::MessageBroker& broker,  
-                           OrthancApiClient& orthancApiClient) :
-    IObservable(broker),
-    IObserver(broker),
+  SmartLoader::SmartLoader(boost::shared_ptr<OrthancApiClient> orthancApiClient) :
     imageQuality_(SliceImageQuality_FullPam),
     orthancApiClient_(orthancApiClient)
   {
@@ -140,7 +131,7 @@
     //   the messages to its observables
     // in both cases, we must be carefull about objects lifecycle !!!
 
-    std::auto_ptr<IVolumeSlicer> layerSource;
+    boost::shared_ptr<IVolumeSlicer> layerSource;
     std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
     SmartLoader::CachedSlice* cachedSlice = NULL;
 
@@ -151,22 +142,23 @@
     }
     else
     {
-      layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_));
+      layerSource.reset(new DicomSeriesVolumeSlicer);
+      dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->Connect(orthancApiClient_);
       dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_);
-      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady));
-      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady));
-      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady));
+      Register<IVolumeSlicer::GeometryReadyMessage>(*layerSource, &SmartLoader::OnLayerGeometryReady);
+      Register<DicomSeriesVolumeSlicer::FrameReadyMessage>(*layerSource, &SmartLoader::OnFrameReady);
+      Register<IVolumeSlicer::LayerReadyMessage>(*layerSource, &SmartLoader::OnLayerReady);
       dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame);
     }
 
     // make sure that the widget registers the events before we trigger them
     if (sliceViewer.GetLayerCount() == layerIndex)
     {
-      sliceViewer.AddLayer(layerSource.release());
+      sliceViewer.AddLayer(layerSource);
     }
     else if (sliceViewer.GetLayerCount() > layerIndex)
     {
-      sliceViewer.ReplaceLayer(layerIndex, layerSource.release());
+      sliceViewer.ReplaceLayer(layerIndex, layerSource);
     }
     else
     {
@@ -190,7 +182,7 @@
 
 
     // create the slice in the cache with "empty" data
-    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
+    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice);
     cachedSlice->slice_.reset(new Slice(instanceId, frame));
     cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad;
     std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
@@ -199,12 +191,12 @@
 
     cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice);
 
-    std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_));
-
+    std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer);
+    dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->Connect(orthancApiClient_);
     dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_);
-    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady));
-    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady));
-    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady));
+    Register<IVolumeSlicer::GeometryReadyMessage>(*layerSource, &SmartLoader::OnLayerGeometryReady);
+    Register<DicomSeriesVolumeSlicer::FrameReadyMessage>(*layerSource, &SmartLoader::OnFrameReady);
+    Register<IVolumeSlicer::LayerReadyMessage>(*layerSource, &SmartLoader::OnLayerReady);
     dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame);
 
     // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache
@@ -235,7 +227,7 @@
 
     LOG(WARNING) << "Geometry ready: " << sliceKeyId;
 
-    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
+    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice);
     cachedSlice->slice_.reset(slice.Clone());
     cachedSlice->effectiveQuality_ = source.GetImageQuality();
     cachedSlice->status_ = CachedSliceStatus_GeometryLoaded;
@@ -256,7 +248,7 @@
 
     LOG(WARNING) << "Image ready: " << sliceKeyId;
 
-    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
+    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice);
     cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame()));
     cachedSlice->effectiveQuality_ = message.GetImageQuality();
     cachedSlice->slice_.reset(message.GetSlice().Clone());
--- a/Framework/Deprecated/SmartLoader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/SmartLoader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -30,7 +30,7 @@
 {
   class SliceViewerWidget;
 
-  class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver
+  class SmartLoader : public OrthancStone::IObservable, public OrthancStone::ObserverBase<SmartLoader>
   {
     class CachedSlice;
 
@@ -42,10 +42,10 @@
     PreloadingInstances preloadingInstances_;
 
     SliceImageQuality     imageQuality_;
-    OrthancApiClient&     orthancApiClient_;
+    boost::shared_ptr<OrthancApiClient>  orthancApiClient_;
 
   public:
-    SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient);  // TODO: add maxPreloadStorageSizeInBytes
+    SmartLoader(boost::shared_ptr<OrthancApiClient> orthancApiClient);  // TODO: add maxPreloadStorageSizeInBytes
 
 //    void PreloadStudy(const std::string studyId);
 //    void PreloadSeries(const std::string seriesId);
--- a/Framework/Deprecated/Toolbox/BaseWebService.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -97,9 +97,9 @@
       GetAsyncInternal(uri, headers,
                        new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered
                        new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestSuccessMessage>
-                       (*this, &BaseWebService::CacheAndNotifyHttpSuccess),
+                       (GetSharedObserver(), &BaseWebService::CacheAndNotifyHttpSuccess),
                        new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestErrorMessage>
-                       (*this, &BaseWebService::NotifyHttpError),
+                       (GetSharedObserver(), &BaseWebService::NotifyHttpError),
                        timeoutInSeconds);
     }
     else
--- a/Framework/Deprecated/Toolbox/BaseWebService.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/BaseWebService.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "IWebService.h"
+#include "../../Messages/ObserverBase.h"
 
 #include <string>
 #include <map>
@@ -31,7 +32,7 @@
 {
   // This is an intermediate of IWebService that implements some caching on
   // the HTTP GET requests
-  class BaseWebService : public IWebService, public OrthancStone::IObserver
+  class BaseWebService : public IWebService, public OrthancStone::ObserverBase<BaseWebService>
   {
   public:
     class CachedHttpRequestSuccessMessage
@@ -90,10 +91,7 @@
     std::deque<std::string> orderedCacheKeys_;
 
   public:
-
-    BaseWebService(OrthancStone::MessageBroker& broker) :
-      IWebService(broker),
-      IObserver(broker),
+    BaseWebService() :
       cacheEnabled_(false),
       cacheCurrentSize_(0),
       cacheMaxSize_(100*1024*1024)
--- a/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h	Fri Nov 29 11:03:41 2019 +0100
@@ -35,22 +35,12 @@
   // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript).
   class IDelayedCallExecutor : public boost::noncopyable
   {
-  protected:
-    OrthancStone::MessageBroker& broker_;
-    
   public:
     ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage);
 
-    IDelayedCallExecutor(OrthancStone::MessageBroker& broker) :
-      broker_(broker)
-    {
-    }
-
-    
     virtual ~IDelayedCallExecutor()
     {
     }
-
     
     virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,
                           unsigned int timeoutInMs = 1000) = 0;
--- a/Framework/Deprecated/Toolbox/IWebService.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/IWebService.h	Fri Nov 29 11:03:41 2019 +0100
@@ -40,9 +40,6 @@
   // and you'll be notified when the response/error is ready.
   class IWebService : public boost::noncopyable
   {
-  protected:
-    OrthancStone::MessageBroker& broker_;
-    
   public:
     typedef std::map<std::string, std::string> HttpHeaders;
 
@@ -138,12 +135,6 @@
     };
 
 
-    IWebService(OrthancStone::MessageBroker& broker) :
-      broker_(broker)
-    {
-    }
-
-    
     virtual ~IWebService()
     {
     }
--- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -73,7 +73,6 @@
     std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> >            binaryHandler_;
     std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> >  failureHandler_;
     std::auto_ptr< Orthanc::IDynamicObject >                               userPayload_;
-    OrthancStone::MessageBroker&                                                         broker_;
     void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const
     {
       if (failureHandler_.get() != NULL)
@@ -84,14 +83,12 @@
     }
     
   public:
-    WebServicePayload(OrthancStone::MessageBroker& broker,
-                      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler,
+    WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler,
                       OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
                       Orthanc::IDynamicObject* userPayload) :
       emptyHandler_(handler),
       failureHandler_(failureHandler),
-      userPayload_(userPayload),
-      broker_(broker)
+      userPayload_(userPayload)
 
     {
       if (handler == NULL)
@@ -100,14 +97,12 @@
       }
     }
 
-    WebServicePayload(OrthancStone::MessageBroker& broker,
-                      OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler,
+    WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler,
                       OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
                       Orthanc::IDynamicObject* userPayload) :
       binaryHandler_(handler),
       failureHandler_(failureHandler),
-      userPayload_(userPayload),
-      broker_(broker)
+      userPayload_(userPayload)
     {
       if (handler == NULL)
       {
@@ -115,14 +110,12 @@
       }
     }
 
-    WebServicePayload(OrthancStone::MessageBroker& broker,
-                      OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler,
+    WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler,
                       OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
                       Orthanc::IDynamicObject* userPayload) :
       jsonHandler_(handler),
       failureHandler_(failureHandler),
-      userPayload_(userPayload),
-      broker_(broker)
+      userPayload_(userPayload)
     {
       if (handler == NULL)
       {
@@ -134,35 +127,26 @@
     {
       if (emptyHandler_.get() != NULL)
       {
-        if (broker_.IsActive(*(emptyHandler_->GetObserver())))
-        {
-          emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage
-                               (message.GetUri(), userPayload_.get()));
-        }
+        emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage
+                             (message.GetUri(), userPayload_.get()));
       }
       else if (binaryHandler_.get() != NULL)
       {
-        if (broker_.IsActive(*(binaryHandler_->GetObserver())))
-        {
-          binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage
-                                (message.GetUri(), message.GetAnswer(),
-                                 message.GetAnswerSize(), userPayload_.get()));
-        }
+        binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage
+                              (message.GetUri(), message.GetAnswer(),
+                               message.GetAnswerSize(), userPayload_.get()));
       }
       else if (jsonHandler_.get() != NULL)
       {
-        if (broker_.IsActive(*(jsonHandler_->GetObserver())))
+        Json::Value response;
+        if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize()))
         {
-          Json::Value response;
-          if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize()))
-          {
-            jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage
-                                (message.GetUri(), response, userPayload_.get()));
-          }
-          else
-          {
-            NotifyConversionError(message);
-          }
+          jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage
+                              (message.GetUri(), response, userPayload_.get()));
+        }
+        else
+        {
+          NotifyConversionError(message);
         }
       }
       else
@@ -182,11 +166,8 @@
   };
 
 
-  OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker,
-                                     IWebService& web,
+  OrthancApiClient::OrthancApiClient(IWebService& web,
                                      const std::string& baseUrl) :
-    IObservable(broker),
-    IObserver(broker),
     web_(web),
     baseUrl_(baseUrl)
   {
@@ -202,11 +183,11 @@
     IWebService::HttpHeaders emptyHeaders;
     web_.GetAsync(baseUrl_ + uri,
                   emptyHeaders,
-                  new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload),
+                  new WebServicePayload(successCallback, failureCallback, payload),
                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                  (*this, &OrthancApiClient::NotifyHttpSuccess),
+                  (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess),
                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                  (*this, &OrthancApiClient::NotifyHttpError));
+                  (GetSharedObserver(), &OrthancApiClient::NotifyHttpError));
   }
 
 
@@ -232,11 +213,11 @@
     // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str());
 
     web_.GetAsync(baseUrl_ + uri, headers,
-                  new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload),
+                  new WebServicePayload(successCallback, failureCallback, payload),
                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                  (*this, &OrthancApiClient::NotifyHttpSuccess),
+                  (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess),
                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                  (*this, &OrthancApiClient::NotifyHttpError));
+                  (GetSharedObserver(), &OrthancApiClient::NotifyHttpError));
   }
 
   
@@ -248,11 +229,11 @@
       Orthanc::IDynamicObject* payload)
   {
     web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
-                   new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload),
+                   new WebServicePayload(successCallback, failureCallback, payload),
                    new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                   (*this, &OrthancApiClient::NotifyHttpSuccess),
+                   (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess),
                    new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                   (*this, &OrthancApiClient::NotifyHttpError));
+                   (GetSharedObserver(), &OrthancApiClient::NotifyHttpError));
 
   }
 
@@ -271,11 +252,11 @@
       Orthanc::IDynamicObject* payload   /* takes ownership */)
   {
     web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
-                   new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload),
+                   new WebServicePayload(successCallback, failureCallback, payload),
                    new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                   (*this, &OrthancApiClient::NotifyHttpSuccess),
+                   (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess),
                    new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                   (*this, &OrthancApiClient::NotifyHttpError));
+                   (GetSharedObserver(), &OrthancApiClient::NotifyHttpError));
   }
 
   void OrthancApiClient::PostJsonAsyncExpectJson(
@@ -318,11 +299,11 @@
       Orthanc::IDynamicObject* payload)
   {
     web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(),
-                     new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload),
+                     new WebServicePayload(successCallback, failureCallback, payload),
                      new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                     (*this, &OrthancApiClient::NotifyHttpSuccess),
+                     (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess),
                      new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                     (*this, &OrthancApiClient::NotifyHttpError));
+                     (GetSharedObserver(), &OrthancApiClient::NotifyHttpError));
   }
 
 
--- a/Framework/Deprecated/Toolbox/OrthancApiClient.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/OrthancApiClient.h	Fri Nov 29 11:03:41 2019 +0100
@@ -25,13 +25,13 @@
 #include <json/json.h>
 
 #include "IWebService.h"
-#include "../../Messages/IObservable.h"
+#include "../../Messages/ObserverBase.h"
 
 namespace Deprecated
 {
   class OrthancApiClient :
       public OrthancStone::IObservable,
-      public OrthancStone::IObserver
+      public OrthancStone::ObserverBase<OrthancApiClient>
   {
   public:
     class JsonResponseReadyMessage : public OrthancStone::IMessage
@@ -157,8 +157,7 @@
     std::string   baseUrl_;
 
   public:
-    OrthancApiClient(OrthancStone::MessageBroker& broker,
-                     IWebService& web,
+    OrthancApiClient(IWebService& web,
                      const std::string& baseUrl);
     
     virtual ~OrthancApiClient()
--- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -639,10 +639,7 @@
   }
   
   
-  OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
-                                           OrthancApiClient& orthanc) :
-    OrthancStone::IObservable(broker),
-    OrthancStone::IObserver(broker),
+  OrthancSlicesLoader::OrthancSlicesLoader(boost::shared_ptr<OrthancApiClient> orthanc) :
     orthanc_(orthanc),
     state_(State_Initialization)
   {
@@ -658,10 +655,10 @@
     else
     {
       state_ = State_LoadingGeometry;
-      orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags",
-                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry),
-                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
-                            NULL);
+      orthanc_->GetJsonAsync("/series/" + seriesId + "/instances-tags",
+                             new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &OrthancSlicesLoader::ParseSeriesGeometry),
+                             new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError),
+                             NULL);
     }
   }
   
@@ -677,10 +674,10 @@
       
       // Tag "3004-000c" is "Grid Frame Offset Vector", which is
       // mandatory to read RT DOSE, but is too long to be returned by default
-      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
-                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry),
-                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
-                            Operation::DownloadInstanceGeometry(instanceId));
+      orthanc_->GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
+                             new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &OrthancSlicesLoader::ParseInstanceGeometry),
+                             new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError),
+                             Operation::DownloadInstanceGeometry(instanceId));
     }
   }
   
@@ -696,10 +693,10 @@
     {
       state_ = State_LoadingGeometry;
 
-      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags",
-                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry),
-                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
-                            Operation::DownloadFrameGeometry(instanceId, frame));
+      orthanc_->GetJsonAsync("/instances/" + instanceId + "/tags",
+                             new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &OrthancSlicesLoader::ParseFrameGeometry),
+                             new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError),
+                             Operation::DownloadFrameGeometry(instanceId, frame));
     }
   }
   
@@ -770,23 +767,23 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
     
-    orthanc_.GetBinaryAsync(uri, "image/png",
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        OrthancApiClient::BinaryResponseReadyMessage>
-          (*this, &OrthancSlicesLoader::ParseSliceImagePng),
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        IWebService::HttpRequestErrorMessage>
-          (*this, &OrthancSlicesLoader::OnSliceImageError),
-      Operation::DownloadSliceImage(
-        static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng));
-}
+    orthanc_->GetBinaryAsync(uri, "image/png",
+                             new OrthancStone::Callable<OrthancSlicesLoader, 
+                             OrthancApiClient::BinaryResponseReadyMessage>
+                             (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImagePng),
+                             new OrthancStone::Callable<OrthancSlicesLoader, 
+                             IWebService::HttpRequestErrorMessage>
+                             (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError),
+                             Operation::DownloadSliceImage(
+                               static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng));
+  }
   
   void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
                                                   size_t index)
   {
     std::string uri = 
       ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
-      boost::lexical_cast<std::string>(slice.GetFrame()));
+       boost::lexical_cast<std::string>(slice.GetFrame()));
 
     switch (slice.GetConverter().GetExpectedPixelFormat())
     {
@@ -806,15 +803,15 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        OrthancApiClient::BinaryResponseReadyMessage>
-          (*this, &OrthancSlicesLoader::ParseSliceImagePam),
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        IWebService::HttpRequestErrorMessage>
-          (*this, &OrthancSlicesLoader::OnSliceImageError),
-      Operation::DownloadSliceImage(static_cast<unsigned int>(index), 
-                                    slice, SliceImageQuality_FullPam));
+    orthanc_->GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
+                             new OrthancStone::Callable<OrthancSlicesLoader, 
+                             OrthancApiClient::BinaryResponseReadyMessage>
+                             (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImagePam),
+                             new OrthancStone::Callable<OrthancSlicesLoader, 
+                             IWebService::HttpRequestErrorMessage>
+                             (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError),
+                             Operation::DownloadSliceImage(static_cast<unsigned int>(index), 
+                                                           slice, SliceImageQuality_FullPam));
   }
 
 
@@ -849,15 +846,15 @@
                        "-" + slice.GetOrthancInstanceId() + "_" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
 
-    orthanc_.GetJsonAsync(uri,
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        OrthancApiClient::JsonResponseReadyMessage>
-          (*this, &OrthancSlicesLoader::ParseSliceImageJpeg),
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        IWebService::HttpRequestErrorMessage>
-          (*this, &OrthancSlicesLoader::OnSliceImageError),
-        Operation::DownloadSliceImage(
-          static_cast<unsigned int>(index), slice, quality));
+    orthanc_->GetJsonAsync(uri,
+                           new OrthancStone::Callable<OrthancSlicesLoader, 
+                           OrthancApiClient::JsonResponseReadyMessage>
+                           (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImageJpeg),
+                           new OrthancStone::Callable<OrthancSlicesLoader, 
+                           IWebService::HttpRequestErrorMessage>
+                           (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError),
+                           Operation::DownloadSliceImage(
+                             static_cast<unsigned int>(index), slice, quality));
   }
   
   
@@ -890,15 +887,15 @@
     {
       std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                          boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
-      orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(),
-        new OrthancStone::Callable<OrthancSlicesLoader, 
-          OrthancApiClient::BinaryResponseReadyMessage>
-            (*this, &OrthancSlicesLoader::ParseSliceRawImage),
-        new OrthancStone::Callable<OrthancSlicesLoader,
-          IWebService::HttpRequestErrorMessage>
-            (*this, &OrthancSlicesLoader::OnSliceImageError),
-        Operation::DownloadSliceRawImage(
-          static_cast<unsigned int>(index), slice));
+      orthanc_->GetBinaryAsync(uri, IWebService::HttpHeaders(),
+                               new OrthancStone::Callable<OrthancSlicesLoader, 
+                               OrthancApiClient::BinaryResponseReadyMessage>
+                               (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceRawImage),
+                               new OrthancStone::Callable<OrthancSlicesLoader,
+                               IWebService::HttpRequestErrorMessage>
+                               (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError),
+                               Operation::DownloadSliceRawImage(
+                                 static_cast<unsigned int>(index), slice));
     }
   }
 }
--- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "../../Messages/IObservable.h"
+#include "../../Messages/ObserverBase.h"
 #include "../../StoneEnumerations.h"
 #include "../../Toolbox/SlicesSorter.h"
 #include "IWebService.h"
@@ -33,7 +34,9 @@
 
 namespace Deprecated
 {
-  class OrthancSlicesLoader : public OrthancStone::IObservable, public OrthancStone::IObserver
+  class OrthancSlicesLoader :
+    public OrthancStone::IObservable,
+    public OrthancStone::ObserverBase<OrthancSlicesLoader>
   {
   public:
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader);
@@ -143,7 +146,7 @@
 
     class Operation;
 
-    OrthancApiClient&  orthanc_;
+    boost::shared_ptr<OrthancApiClient>  orthanc_;
     State         state_;
     OrthancStone::SlicesSorter  slices_;
 
@@ -183,9 +186,8 @@
     void SortAndFinalizeSlices();
     
   public:
-    OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
-                        //ISliceLoaderObserver& callback,
-                        OrthancApiClient& orthancApi);
+    OrthancSlicesLoader(//ISliceLoaderObserver& callback,
+      boost::shared_ptr<OrthancApiClient> orthancApi);
 
     void ScheduleLoadSeries(const std::string& seriesId);
 
--- a/Framework/Deprecated/Viewport/IViewport.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Viewport/IViewport.h	Fri Nov 29 11:03:41 2019 +0100
@@ -37,15 +37,6 @@
   public:
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport);
 
-    IViewport(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-    
-    virtual ~IViewport()
-    {
-    }
-
     virtual void FitContent() = 0;
 
     virtual void SetStatusBar(IStatusBar& statusBar) = 0;
--- a/Framework/Deprecated/Viewport/WidgetViewport.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Viewport/WidgetViewport.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,8 +26,7 @@
 
 namespace Deprecated
 {
-  WidgetViewport::WidgetViewport(OrthancStone::MessageBroker& broker) :
-    IViewport(broker),
+  WidgetViewport::WidgetViewport() :
     statusBar_(NULL),
     isMouseOver_(false),
     lastMouseX_(0),
@@ -57,7 +56,7 @@
   }
 
 
-  IWidget& WidgetViewport::SetCentralWidget(IWidget* widget)
+  void WidgetViewport::SetCentralWidget(boost::shared_ptr<IWidget> widget)
   {
     if (widget == NULL)
     {
@@ -66,7 +65,7 @@
 
     mouseTracker_.reset(NULL);
 
-    centralWidget_.reset(widget);
+    centralWidget_ = widget;
     centralWidget_->SetViewport(*this);
 
     if (statusBar_ != NULL)
@@ -75,8 +74,6 @@
     }
 
     NotifyBackgroundChanged();
-
-    return *widget;
   }
 
 
--- a/Framework/Deprecated/Viewport/WidgetViewport.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Viewport/WidgetViewport.h	Fri Nov 29 11:03:41 2019 +0100
@@ -31,7 +31,7 @@
   class WidgetViewport : public IViewport
   {
   private:
-    std::auto_ptr<IWidget>        centralWidget_;
+    boost::shared_ptr<IWidget>    centralWidget_;
     IStatusBar*                   statusBar_;
     std::auto_ptr<IMouseTracker>  mouseTracker_;
     bool                          isMouseOver_;
@@ -41,13 +41,13 @@
     bool                          backgroundChanged_;
 
   public:
-    WidgetViewport(OrthancStone::MessageBroker& broker);
+    WidgetViewport();
 
     virtual void FitContent();
 
     virtual void SetStatusBar(IStatusBar& statusBar);
 
-    IWidget& SetCentralWidget(IWidget* widget);  // Takes ownership
+    void SetCentralWidget(boost::shared_ptr<IWidget> widget);
 
     virtual void NotifyBackgroundChanged();
 
--- a/Framework/Deprecated/Volumes/ISlicedVolume.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Volumes/ISlicedVolume.h	Fri Nov 29 11:03:41 2019 +0100
@@ -65,11 +65,6 @@
     };
 
 
-    ISlicedVolume(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-    
     virtual size_t GetSliceCount() const = 0;
 
     virtual const Slice& GetSlice(size_t slice) const = 0;
--- a/Framework/Deprecated/Volumes/IVolumeLoader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Volumes/IVolumeLoader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -31,10 +31,5 @@
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeLoader);
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeLoader);
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeLoader);
-
-    IVolumeLoader(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
   };
 }
--- a/Framework/Deprecated/Volumes/StructureSetLoader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -27,10 +27,7 @@
 
 namespace Deprecated
 {
-  StructureSetLoader::StructureSetLoader(OrthancStone::MessageBroker& broker,
-                                         OrthancApiClient& orthanc) :
-    IVolumeLoader(broker),
-    IObserver(broker),
+  StructureSetLoader::StructureSetLoader(OrthancApiClient& orthanc) :
     orthanc_(orthanc)
   {
   }
@@ -60,7 +57,7 @@
          it != instances.end(); ++it)
     {
       orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it,
-                                         new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted));
+                                         new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &StructureSetLoader::OnLookupCompleted));
     }
 
     BroadcastMessage(GeometryReadyMessage(*this));
@@ -84,7 +81,7 @@
 
     const std::string& instance = lookup[0]["ID"].asString();
     orthanc_.GetJsonAsync("/instances/" + instance + "/tags",
-                          new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded));
+                          new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &StructureSetLoader::OnReferencedSliceLoaded));
   }
 
   
@@ -97,7 +94,7 @@
     else
     {
       orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050",
-                            new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded));
+                            new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &StructureSetLoader::OnStructureSetLoaded));
     }
   }
 
--- a/Framework/Deprecated/Volumes/StructureSetLoader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Volumes/StructureSetLoader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -21,6 +21,7 @@
 
 #pragma once
 
+#include "../../Messages/ObserverBase.h"
 #include "../../Toolbox/DicomStructureSet.h"
 #include "../Toolbox/OrthancApiClient.h"
 #include "IVolumeLoader.h"
@@ -29,7 +30,7 @@
 {
   class StructureSetLoader :
     public IVolumeLoader,
-    public OrthancStone::IObserver
+    public OrthancStone::ObserverBase<StructureSetLoader>
   {
   private:
     OrthancApiClient&                 orthanc_;
@@ -42,8 +43,7 @@
     void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message);
 
   public:
-    StructureSetLoader(OrthancStone::MessageBroker& broker,
-                       OrthancApiClient& orthanc);
+    StructureSetLoader(OrthancApiClient& orthanc);
 
     void ScheduleLoadInstance(const std::string& instance);
 
--- a/Framework/Deprecated/Widgets/LayoutWidget.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Widgets/LayoutWidget.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -85,14 +85,14 @@
   class LayoutWidget::ChildWidget : public boost::noncopyable
   {
   private:
-    std::auto_ptr<IWidget>  widget_;
+    boost::shared_ptr<IWidget>  widget_;
     int                     left_;
     int                     top_;
     unsigned int            width_;
     unsigned int            height_;
 
   public:
-    ChildWidget(IWidget* widget) :
+    ChildWidget(boost::shared_ptr<IWidget> widget) :
       widget_(widget)
     {
       assert(widget != NULL);
@@ -354,7 +354,7 @@
   }
 
 
-  IWidget& LayoutWidget::AddWidget(IWidget* widget)  // Takes ownership
+  void LayoutWidget::AddWidget(boost::shared_ptr<IWidget> widget)  // Takes ownership
   {
     if (widget == NULL)
     {
@@ -375,8 +375,6 @@
     {
       hasAnimation_ = true;
     }
-
-    return *widget;
   }
 
 
--- a/Framework/Deprecated/Widgets/LayoutWidget.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Widgets/LayoutWidget.h	Fri Nov 29 11:03:41 2019 +0100
@@ -94,7 +94,7 @@
       return paddingInternal_;
     }
 
-    IWidget& AddWidget(IWidget* widget);  // Takes ownership
+    void AddWidget(boost::shared_ptr<IWidget> widget);
 
     virtual void SetStatusBar(IStatusBar& statusBar);
 
--- a/Framework/Deprecated/Widgets/SliceViewerWidget.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Widgets/SliceViewerWidget.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -250,7 +250,7 @@
     {
       index = found->second;
       assert(index < layers_.size() &&
-             layers_[index] == &layer);
+             layers_[index].get() == &layer);
       return true;
     }
   }
@@ -364,42 +364,27 @@
   }
 
   
-  SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, 
-                                       const std::string& name) :
+  SliceViewerWidget::SliceViewerWidget(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(IVolumeSlicer& layer)
   {
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage>
-                                   (*this, &SliceViewerWidget::OnGeometryReady));
-    // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage>
-                                   (*this, &SliceViewerWidget::OnSliceChanged));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage>
-                                   (*this, &SliceViewerWidget::OnContentChanged));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage>
-                                   (*this, &SliceViewerWidget::OnLayerReady));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage>
-                                   (*this, &SliceViewerWidget::OnLayerError));
+    // currently ignoring errors of type IVolumeSlicer::GeometryErrorMessage
+
+    Register<IVolumeSlicer::GeometryReadyMessage>(layer, &SliceViewerWidget::OnGeometryReady);
+    Register<IVolumeSlicer::SliceContentChangedMessage>(layer, &SliceViewerWidget::OnSliceChanged);
+    Register<IVolumeSlicer::ContentChangedMessage>(layer, &SliceViewerWidget::OnContentChanged);
+    Register<IVolumeSlicer::LayerReadyMessage>(layer, &SliceViewerWidget::OnLayerReady);
+    Register<IVolumeSlicer::LayerErrorMessage>(layer, &SliceViewerWidget::OnLayerError);
   }
 
 
-  size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer)  // Takes ownership
+  size_t SliceViewerWidget::AddLayer(boost::shared_ptr<IVolumeSlicer> layer)
   {
     if (layer == NULL)
     {
@@ -409,7 +394,7 @@
     size_t index = layers_.size();
     layers_.push_back(layer);
     styles_.push_back(RenderStyle());
-    layersIndex_[layer] = index;
+    layersIndex_[layer.get()] = index;
 
     ResetPendingScene();
 
@@ -421,7 +406,8 @@
   }
 
 
-  void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer)  // Takes ownership
+  void SliceViewerWidget::ReplaceLayer(size_t index,
+                                       boost::shared_ptr<IVolumeSlicer> layer)
   {
     if (layer == NULL)
     {
@@ -433,9 +419,8 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    delete layers_[index];
     layers_[index] = layer;
-    layersIndex_[layer] = index;
+    layersIndex_[layer.get()] = index;
 
     ResetPendingScene();
 
@@ -452,13 +437,13 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    IVolumeSlicer* previousLayer = layers_[index];
+    IVolumeSlicer* previousLayer = layers_[index].get();
     layersIndex_.erase(layersIndex_.find(previousLayer));
     layers_.erase(layers_.begin() + index);
     changedLayers_.erase(changedLayers_.begin() + index);
     styles_.erase(styles_.begin() + index);
 
-    delete layers_[index];
+    layers_[index].reset();
 
     currentScene_->DeleteLayer(index);
     ResetPendingScene();
--- a/Framework/Deprecated/Widgets/SliceViewerWidget.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Deprecated/Widgets/SliceViewerWidget.h	Fri Nov 29 11:03:41 2019 +0100
@@ -24,7 +24,7 @@
 #include "WorldSceneWidget.h"
 #include "../Layers/IVolumeSlicer.h"
 #include "../../Toolbox/Extent2D.h"
-#include "../../Messages/IObserver.h"
+#include "../../Messages/ObserverBase.h"
 
 #include <map>
 
@@ -32,7 +32,7 @@
 {
   class SliceViewerWidget :
     public WorldSceneWidget,
-    public OrthancStone::IObserver,
+    public OrthancStone::ObserverBase<SliceViewerWidget>,
     public OrthancStone::IObservable
   {
   public:
@@ -72,7 +72,7 @@
 
     bool                         started_;
     LayersIndex                  layersIndex_;
-    std::vector<IVolumeSlicer*>  layers_;
+    std::vector<boost::shared_ptr<IVolumeSlicer> >  layers_;
     std::vector<RenderStyle>     styles_;
     OrthancStone::CoordinateSystem3D           plane_;
     std::auto_ptr<Scene>         currentScene_;
@@ -100,8 +100,7 @@
     void ResetChangedLayers();
 
   public:
-    SliceViewerWidget(OrthancStone::MessageBroker& broker, 
-                      const std::string& name);
+    SliceViewerWidget(const std::string& name);
 
     virtual OrthancStone::Extent2D GetSceneExtent();
 
@@ -120,11 +119,13 @@
     void InvalidateLayer(size_t layer);
     
   public:
-    virtual ~SliceViewerWidget();
+    virtual ~SliceViewerWidget()
+    {
+    }
 
-    size_t AddLayer(IVolumeSlicer* layer);  // Takes ownership
+    size_t AddLayer(boost::shared_ptr<IVolumeSlicer> layer);
 
-    void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership
+    void ReplaceLayer(size_t layerIndex, boost::shared_ptr<IVolumeSlicer> layer); // Takes ownership
 
     void RemoveLayer(size_t layerIndex);
 
--- a/Framework/Loaders/DicomStructureSetLoader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/DicomStructureSetLoader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -144,7 +144,7 @@
         command->SetHttpHeader("Accept-Encoding", "gzip");
         std::string uri = "/instances/" + instanceId + "/tags";
         command->SetUri(uri);
-        command->SetPayload(new AddReferencedInstance(loader, instanceId));
+        command->AcquirePayload(new AddReferencedInstance(loader, instanceId));
         Schedule(command.release());
       }
     }
@@ -231,7 +231,7 @@
         command->SetUri("/tools/lookup");
         command->SetMethod(Orthanc::HttpMethod_Post);
         command->SetBody(*it);
-        command->SetPayload(new LookupInstance(loader, *it));
+        command->AcquirePayload(new LookupInstance(loader, *it));
         Schedule(command.release());
       }
     }
@@ -347,7 +347,6 @@
   DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle,
                                                    IObservable& oracleObservable) :
     LoaderStateMachine(oracle, oracleObservable),
-    IObservable(oracleObservable.GetBroker()),
     revision_(0),
     countProcessedInstances_(0),
     countReferencedInstances_(0),
@@ -383,7 +382,7 @@
       std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
 
       command->SetUri(uri);
-      command->SetPayload(new LoadStructure(*this));
+      command->AcquirePayload(new LoadStructure(*this));
       Schedule(command.release());
     }
   }
--- a/Framework/Loaders/LoaderCache.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/LoaderCache.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -65,7 +65,7 @@
 
   }
 #else
-  LoaderCache::LoaderCache(ThreadedOracle& oracle, LockingEmitter& lockingEmitter)
+  LoaderCache::LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter)
     : oracle_(oracle)
     , lockingEmitter_(lockingEmitter)
   {
@@ -98,7 +98,7 @@
 #if ORTHANC_ENABLE_WASM == 1
           loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_));
 #else
-          LockingEmitter::WriterLock lock(lockingEmitter_);
+          Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
           loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable()));
 #endif
 //          LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get();
@@ -167,10 +167,8 @@
 #if ORTHANC_ENABLE_WASM == 1
           loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, oracle_));
 #else
-          LockingEmitter::WriterLock lock(lockingEmitter_);
-          loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, 
-            oracle_, 
-            lock.GetOracleObservable()));
+          Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
+          loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, lock.GetOracleObservable()));
 #endif
           loader->LoadInstance(instanceUuid);
         }
@@ -270,7 +268,7 @@
 #if ORTHANC_ENABLE_WASM == 1
           loader.reset(new DicomStructureSetLoader(oracle_, oracle_));
 #else
-          LockingEmitter::WriterLock lock(lockingEmitter_);
+          Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
           loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable()));
 #endif
           loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures);
@@ -366,7 +364,7 @@
   void LoaderCache::ClearCache()
   {
 #if ORTHANC_ENABLE_WASM != 1
-    LockingEmitter::WriterLock lock(lockingEmitter_);
+    Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
 #endif
     
 //#ifndef NDEBUG
--- a/Framework/Loaders/LoaderCache.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/LoaderCache.h	Fri Nov 29 11:03:41 2019 +0100
@@ -43,7 +43,10 @@
   class WebAssemblyOracle;
 #else
   class ThreadedOracle;
-  class LockingEmitter;
+  namespace Deprecated
+  {
+    class LockingEmitter;
+  }
 #endif
 
   class LoaderCache
@@ -52,7 +55,7 @@
 #if ORTHANC_ENABLE_WASM == 1
     LoaderCache(WebAssemblyOracle& oracle);
 #else
-    LoaderCache(ThreadedOracle& oracle, LockingEmitter& lockingEmitter);
+    LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter);
 #endif
 
     boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader>
@@ -87,7 +90,7 @@
     WebAssemblyOracle& oracle_;
 #else
     ThreadedOracle& oracle_;
-    LockingEmitter& lockingEmitter_;
+    Deprecated::LockingEmitter& lockingEmitter_;
 #endif
 
     std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> >
--- a/Framework/Loaders/LoaderStateMachine.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/LoaderStateMachine.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -43,11 +43,11 @@
   }
 
 
-  void LoaderStateMachine::Schedule(OracleCommandWithPayload* command)
+  void LoaderStateMachine::Schedule(OracleCommandBase* command)
   {
     LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()";
 
-    std::auto_ptr<OracleCommandWithPayload> protection(command);
+    std::auto_ptr<OracleCommandBase> protection(command);
 
     if (command == NULL)
     {
@@ -97,7 +97,8 @@
         ") < simultaneousDownloads_ (" << simultaneousDownloads_ << 
         ") --> will Schedule command addr " << std::hex << nextCommand << std::dec;
 
-      oracle_.Schedule(*this, nextCommand);
+      boost::shared_ptr<IObserver> observer(GetSharedObserver());
+      oracle_.Schedule(observer, nextCommand);
       pendingCommands_.pop_front();
 
       activeCommands_++;
@@ -136,7 +137,6 @@
   template <typename T>
   void LoaderStateMachine::HandleSuccessMessage(const T& message)
   {
-    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage(). Receiver fingerprint = " << GetFingerprint();
     if (activeCommands_ <= 0) {
       LOG(ERROR) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage : activeCommands_ should be > 0 but is: " << activeCommands_;
     }
@@ -159,35 +159,22 @@
 
   LoaderStateMachine::LoaderStateMachine(IOracle& oracle,
                                          IObservable& oracleObservable) :
-    IObserver(oracleObservable.GetBroker()),
     oracle_(oracle),
-    oracleObservable_(oracleObservable),
     active_(false),
     simultaneousDownloads_(4),
     activeCommands_(0)
   {
     LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()";
 
-    oracleObservable.RegisterObserverCallback(
-      new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage>
-      (*this, &LoaderStateMachine::HandleSuccessMessage));
-
-    oracleObservable.RegisterObserverCallback(
-      new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage>
-      (*this, &LoaderStateMachine::HandleSuccessMessage));
-
-    oracleObservable.RegisterObserverCallback(
-      new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage>
-      (*this, &LoaderStateMachine::HandleSuccessMessage));
-
-    oracleObservable.RegisterObserverCallback(
-      new Callable<LoaderStateMachine, OracleCommandExceptionMessage>
-      (*this, &LoaderStateMachine::HandleExceptionMessage));
+    // TODO => Move this out of constructor
+    Register<OrthancRestApiCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
+    Register<GetOrthancImageCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
+    Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
+    Register<OracleCommandExceptionMessage>(oracleObservable, &LoaderStateMachine::HandleExceptionMessage);
   }
 
   LoaderStateMachine::~LoaderStateMachine()
   {
-    oracleObservable_.Unregister(this);
     LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()";
     Clear();
   }
--- a/Framework/Loaders/LoaderStateMachine.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/LoaderStateMachine.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Messages/IObservable.h"
-#include "../Messages/IObserver.h"
+#include "../Messages/ObserverBase.h"
 #include "../Oracle/GetOrthancImageCommand.h"
 #include "../Oracle/GetOrthancWebViewerJpegCommand.h"
 #include "../Oracle/IOracle.h"
@@ -41,7 +41,7 @@
      rest once slots become available. It is used, a.o., by the 
      OrtancMultiframeVolumeLoader class.
   */
-  class LoaderStateMachine : public IObserver
+  class LoaderStateMachine : public ObserverBase<LoaderStateMachine>
   {
   protected:
     class State : public Orthanc::IDynamicObject
@@ -60,7 +60,7 @@
       {
       }
 
-      void Schedule(OracleCommandWithPayload* command) const
+      void Schedule(OracleCommandBase* command) const
       {
         that_.Schedule(command);
       }
@@ -78,7 +78,7 @@
       virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message);
     };
 
-    void Schedule(OracleCommandWithPayload* command);
+    void Schedule(OracleCommandBase* command);
 
     void Start();
 
@@ -95,7 +95,6 @@
     typedef std::list<IOracleCommand*>  PendingCommands;
 
     IOracle&         oracle_;
-    IObservable&     oracleObservable_;
     bool             active_;
     unsigned int     simultaneousDownloads_;
     PendingCommands  pendingCommands_;
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -101,7 +101,7 @@
         std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
         command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
                         Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
-        command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release()));
+        command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release()));
 
         Schedule(command.release());
       }
@@ -175,7 +175,7 @@
       command->SetHttpHeader("Accept-Encoding", "gzip");
       command->SetUri("/instances/" + instanceId_ + "/content/" +
                       Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
-      command->SetPayload(new LoadUncompressedPixelData(*this));
+      command->AcquirePayload(new LoadUncompressedPixelData(*this));
       Schedule(command.release());
     }
     else
@@ -345,7 +345,6 @@
                                                                IOracle& oracle,
                                                                IObservable& oracleObservable) :
     LoaderStateMachine(oracle, oracleObservable),
-    IObservable(oracleObservable.GetBroker()),
     volume_(volume),
     pixelDataLoaded_(false)
   {
@@ -370,14 +369,14 @@
       std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
       command->SetHttpHeader("Accept-Encoding", "gzip");
       command->SetUri("/instances/" + instanceId + "/tags");
-      command->SetPayload(new LoadGeometry(*this));
+      command->AcquirePayload(new LoadGeometry(*this));
       Schedule(command.release());
     }
 
     {
       std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
       command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
-      command->SetPayload(new LoadTransferSyntax(*this));
+      command->AcquirePayload(new LoadTransferSyntax(*this));
       Schedule(command.release());
     }
   }
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -30,8 +30,7 @@
 {
   class OrthancMultiframeVolumeLoader :
     public LoaderStateMachine,
-    public IObservable,
-    public IGeometryProvider
+    public IObservable
   {
   private:
     class LoadRTDoseGeometry;
@@ -57,8 +56,8 @@
 
     void SetUncompressedPixelData(const std::string& pixelData);
 
-    virtual bool HasGeometry() const ORTHANC_OVERRIDE;
-    virtual const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE;
+    bool HasGeometry() const;
+    const VolumeImageGeometry& GetImageGeometry() const;
 
   public:
     OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume,
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -185,18 +185,27 @@
 
       CheckVolume();
 
-      const double spacingZ = slices.ComputeSpacingBetweenSlices();
-      LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
+      double spacingZ;
+
+      if (slices.ComputeSpacingBetweenSlices(spacingZ))
+      {
+        LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
       
-      const DicomInstanceParameters& parameters = *slices_[0];
+        const DicomInstanceParameters& parameters = *slices_[0];
 
-      geometry_.reset(new VolumeImageGeometry);
-      geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(),
-                         parameters.GetImageInformation().GetHeight(),
-                         static_cast<unsigned int>(slices.GetSlicesCount()));
-      geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
-      geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
-                                    parameters.GetPixelSpacingY(), spacingZ);
+        geometry_.reset(new VolumeImageGeometry);
+        geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(),
+                                   parameters.GetImageInformation().GetHeight(),
+                                   static_cast<unsigned int>(slices.GetSlicesCount()));
+        geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
+        geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
+                                      parameters.GetPixelSpacingY(), spacingZ);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "The origins of the slices of a volume image are not regularly spaced");
+     }
     }
   }
 
@@ -237,8 +246,9 @@
   }
 
 
-  static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
+  static unsigned int GetSliceIndexPayload(const OracleCommandBase& command)
   {
+    assert(command.HasPayload());
     return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
   }
 
@@ -261,7 +271,7 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
-      std::auto_ptr<OracleCommandWithPayload> command;
+      std::auto_ptr<OracleCommandBase> command;
         
       if (quality == BEST_QUALITY)
       {
@@ -291,8 +301,10 @@
         command.reset(tmp.release());
       }
 
-      command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
-      oracle_.Schedule(*this, command.release());
+      command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
+
+      boost::shared_ptr<IObserver> observer(GetSharedObserver());
+      oracle_.Schedule(observer, command.release());
     }
     else
     {
@@ -400,7 +412,7 @@
   {
     unsigned int quality;
       
-    switch (message.GetOrigin().GetQuality())
+    switch (dynamic_cast<const GetOrthancWebViewerJpegCommand&>(message.GetOrigin()).GetQuality())
     {
       case 50:
         quality = LOW_QUALITY;
@@ -421,32 +433,26 @@
   OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
                                                                              IOracle& oracle,
                                                                              IObservable& oracleObservable) :
-    IObserver(oracleObservable.GetBroker()),
-    IObservable(oracleObservable.GetBroker()),
     oracle_(oracle),
-    oracleObservable_(oracleObservable),
     active_(false),
     simultaneousDownloads_(4),
     volume_(volume),
     sorter_(new BasicFetchingItemsSorter::Factory),
     volumeImageReadyInHighQuality_(false)
   {
-    oracleObservable.RegisterObserverCallback(
-      new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage>
-      (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry));
+    // TODO => Move this out of constructor
+    Register<OrthancRestApiCommand::SuccessMessage>
+      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry);
 
-    oracleObservable.RegisterObserverCallback(
-      new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage>
-      (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent));
+    Register<GetOrthancImageCommand::SuccessMessage>
+      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent);
 
-    oracleObservable.RegisterObserverCallback(
-      new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
-      (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent));
+    Register<GetOrthancWebViewerJpegCommand::SuccessMessage>
+      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent);
   }
 
   OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()
   {
-    oracleObservable_.Unregister(this);
     LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()";
   }
 
@@ -485,7 +491,8 @@
       command->SetUri("/series/" + seriesId + "/instances-tags");
 
 //      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule";
-      oracle_.Schedule(*this, command.release());
+      boost::shared_ptr<IObserver> observer(GetSharedObserver());
+      oracle_.Schedule(observer, command.release());
 //      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule";
     }
   }
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Messages/IObservable.h"
-#include "../Messages/IObserver.h"
+#include "../Messages/ObserverBase.h"
 #include "../Oracle/GetOrthancImageCommand.h"
 #include "../Oracle/GetOrthancWebViewerJpegCommand.h"
 #include "../Oracle/IOracle.h"
@@ -42,10 +42,9 @@
     is stored in a Dicom series.
   */
   class OrthancSeriesVolumeProgressiveLoader : 
-    public IObserver,
+    public ObserverBase<OrthancSeriesVolumeProgressiveLoader>,
     public IObservable,
-    public IVolumeSlicer,
-    public IGeometryProvider
+    public IVolumeSlicer
   {
   private:
     static const unsigned int LOW_QUALITY = 0;
@@ -106,7 +105,6 @@
     void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message);
 
     IOracle&                                      oracle_;
-    IObservable&                                  oracleObservable_;
     bool                                          active_;
     unsigned int                                  simultaneousDownloads_;
     SeriesGeometry                                seriesGeometry_;
@@ -141,7 +139,7 @@
     subscribing, for instance if they are created or listening only AFTER the
     "geometry loaded" message is broadcast 
     */
-    bool HasGeometry() const ORTHANC_OVERRIDE
+    bool HasGeometry() const
     {
       return seriesGeometry_.HasGeometry();
     }
@@ -149,7 +147,7 @@
     /**
     Same remark as HasGeometry
     */
-    const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE
+    const VolumeImageGeometry& GetImageGeometry() const
     {
       return seriesGeometry_.GetImageGeometry();
     }
--- a/Framework/Messages/ICallable.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/ICallable.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,20 +22,20 @@
 #pragma once
 
 #include "IMessage.h"
+#include "IObserver.h"
 
 #include <Core/Logging.h>
 
 #include <boost/noncopyable.hpp>
+#include <boost/weak_ptr.hpp>
 
 #include <string>
 
-namespace OrthancStone {
-
-  class IObserver;
-
+namespace OrthancStone 
+{
   // This is referencing an object and member function that can be notified
   // by an IObservable.  The object must derive from IO
-  // The member functions must be of type "void Function(const IMessage& message)" or reference a derived class of IMessage
+  // The member functions must be of type "void Method(const IMessage& message)" or reference a derived class of IMessage
   class ICallable : public boost::noncopyable
   {
   public:
@@ -47,11 +47,14 @@
 
     virtual const MessageIdentifier& GetMessageIdentifier() = 0;
 
-    virtual IObserver* GetObserver() const = 0;
+    // TODO - Is this needed?
+    virtual boost::weak_ptr<IObserver> GetObserver() const = 0;
   };
 
+
+  // TODO - Remove this class
   template <typename TMessage>
-  class MessageHandler: public ICallable
+  class MessageHandler : public ICallable
   {
   };
 
@@ -61,47 +64,28 @@
   class Callable : public MessageHandler<TMessage>
   {
   private:
-    typedef void (TObserver::* MemberFunction) (const TMessage&);
+    typedef void (TObserver::* MemberMethod) (const TMessage&);
 
-    TObserver&         observer_;
-    MemberFunction     function_;
-    std::string        observerFingerprint_;
+    boost::weak_ptr<IObserver>  observer_;
+    MemberMethod                function_;
 
   public:
-    Callable(TObserver& observer,
-             MemberFunction function) :
+    Callable(boost::shared_ptr<TObserver> observer,
+             MemberMethod function) :
       observer_(observer),
-      function_(function),
-      observerFingerprint_(observer.GetFingerprint())
-    {
-    }
-
-    void ApplyInternal(const TMessage& message)
+      function_(function)
     {
-      std::string currentFingerprint(observer_.GetFingerprint());
-      if (observerFingerprint_ != currentFingerprint)
-      {
-        LOG(TRACE) << "The observer at address " << 
-          std::hex << &observer_ << std::dec << 
-          ") has a different fingerprint than the one recorded at callback " <<
-          "registration time. This means that it is not the same object as " <<
-          "the one recorded, even though their addresses are the same. " <<
-          "Callback will NOT be sent!";
-        LOG(TRACE) << " recorded fingerprint = " << observerFingerprint_ << 
-          " current fingerprint = " << currentFingerprint;
-      }
-      else
-      {
-        LOG(TRACE) << "The recorded fingerprint is " << observerFingerprint_
-          << " and the current fingerprint is " << currentFingerprint
-          << " -- callable will be called.";
-        (observer_.*function_) (message);
-      }
     }
 
     virtual void Apply(const IMessage& message)
     {
-      ApplyInternal(dynamic_cast<const TMessage&>(message));
+      boost::shared_ptr<IObserver> lock(observer_);
+      if (lock)
+      {
+        TObserver& observer = dynamic_cast<TObserver&>(*lock);
+        const TMessage& typedMessage = dynamic_cast<const TMessage&>(message);
+        (observer.*function_) (typedMessage);
+      }
     }
 
     virtual const MessageIdentifier& GetMessageIdentifier()
@@ -109,41 +93,9 @@
       return TMessage::GetStaticIdentifier();
     }
 
-    virtual IObserver* GetObserver() const
+    virtual boost::weak_ptr<IObserver> GetObserver() const
     {
-      return &observer_;
+      return observer_;
     }
   };
-
-#if 0 /* __cplusplus >= 201103L*/
-
-#include <functional>
-
-  template <typename TMessage>
-  class LambdaCallable : public MessageHandler<TMessage>
-  {
-  private:
-
-    IObserver&      observer_;
-    std::function<void (const TMessage&)> lambda_;
-
-  public:
-    LambdaCallable(IObserver& observer,
-                    std::function<void (const TMessage&)> lambdaFunction) :
-             observer_(observer),
-             lambda_(lambdaFunction)
-    {
-    }
-
-    virtual void Apply(const IMessage& message)
-    {
-      lambda_(dynamic_cast<const TMessage&>(message));
-    }
-
-    virtual IObserver* GetObserver() const
-    {
-      return &observer_;
-    }
-  };
-#endif //__cplusplus >= 201103L
 }
--- a/Framework/Messages/IMessage.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/IMessage.h	Fri Nov 29 11:03:41 2019 +0100
@@ -21,6 +21,7 @@
 
 #pragma once
 
+#include <boost/lexical_cast.hpp>
 #include <boost/noncopyable.hpp>
 
 #include <string.h>
@@ -33,6 +34,12 @@
     const char*  file_;
     int          line_;
 
+    bool IsEqual(const MessageIdentifier& other) const
+    {
+      return (line_ == other.line_ &&
+              strcmp(file_, other.file_) == 0);
+    }
+
   public:
     MessageIdentifier(const char* file,
                       int line) :
@@ -47,6 +54,11 @@
     {
     }
 
+    std::string AsString() const
+    {
+      return std::string(file_) + ":" + boost::lexical_cast<std::string>(line_);
+    }
+
     bool operator< (const MessageIdentifier& other) const
     {
       if (file_ == NULL)
@@ -62,6 +74,16 @@
         return strcmp(file_, other.file_) < 0;
       }
     }
+
+    bool operator== (const MessageIdentifier& other) const
+    {
+      return IsEqual(other);
+    }
+
+    bool operator!= (const MessageIdentifier& other) const
+    {
+      return !IsEqual(other);
+    }
   };
 
     
--- a/Framework/Messages/IMessageEmitter.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/IMessageEmitter.h	Fri Nov 29 11:03:41 2019 +0100
@@ -24,6 +24,8 @@
 #include "IObserver.h"
 #include "IMessage.h"
 
+#include <boost/weak_ptr.hpp>
+
 namespace OrthancStone
 {
   /**
@@ -39,7 +41,7 @@
     {
     }
 
-    virtual void EmitMessage(const IObserver& observer,
+    virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
                              const IMessage& message) = 0;
   };
 }
--- a/Framework/Messages/IObservable.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/IObservable.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -21,8 +21,9 @@
 
 #include "IObservable.h"
 
+#include "../StoneException.h"
+
 #include <Core/Logging.h>
-#include <Core/OrthancException.h>
 
 #include <cassert>
 
@@ -40,20 +41,10 @@
         delete *it2;
       }
     }
-
-    // unregister the forwarders but don't delete them (they'll be
-    // deleted by the observable they are observing as any other
-    // callable)
-    for (Forwarders::iterator it = forwarders_.begin();
-         it != forwarders_.end(); ++it)
-    {
-      IMessageForwarder* fw = *it;
-      broker_.Unregister(dynamic_cast<IObserver&>(*fw));
-    }
   }
   
 
-  void IObservable::RegisterObserverCallback(ICallable* callable)
+  void IObservable::RegisterCallable(ICallable* callable)
   {
     if (callable == NULL)
     {
@@ -64,35 +55,10 @@
     callables_[id].insert(callable);
   }
 
-  void IObservable::Unregister(IObserver *observer)
-  {
-    LOG(TRACE) << "IObservable::Unregister for IObserver at addr: "
-      << std::hex << observer << std::dec;
-    // delete all callables from this observer
-    for (Callables::iterator itCallableSet = callables_.begin();
-         itCallableSet != callables_.end(); ++itCallableSet)
-    {
-      for (std::set<ICallable*>::const_iterator
-             itCallable = itCallableSet->second.begin(); itCallable != itCallableSet->second.end(); )
-      {
-        if ((*itCallable)->GetObserver() == observer)
-        {
-          LOG(TRACE) << "  ** IObservable::Unregister : deleting callable: "
-            << std::hex << (*itCallable) << std::dec;
-          delete *itCallable;
-          itCallableSet->second.erase(itCallable++);
-        }
-        else
-          ++itCallable;
-      }
-    }
-  }
-  
   void IObservable::EmitMessageInternal(const IObserver* receiver,
                                         const IMessage& message)
   {
-    LOG(TRACE) << "IObservable::EmitMessageInternal receiver = "
-      << std::hex << receiver << std::dec;
+    //LOG(TRACE) << "IObservable::EmitMessageInternal receiver = " << std::hex << receiver << std::dec;
     Callables::const_iterator found = callables_.find(message.GetIdentifier());
 
     if (found != callables_.end())
@@ -102,15 +68,36 @@
       {
         assert(*it != NULL);
 
-        const IObserver* observer = (*it)->GetObserver();
-        if (broker_.IsActive(*observer))
+        boost::shared_ptr<IObserver> observer((*it)->GetObserver().lock());
+
+        if (observer)
         {
           if (receiver == NULL ||    // Are we broadcasting?
-              observer == receiver)  // Not broadcasting, but this is the receiver
+              observer.get() == receiver)  // Not broadcasting, but this is the receiver
           {
-            (*it)->Apply(message);
+            try
+            {
+              (*it)->Apply(message);
+            }
+            catch (Orthanc::OrthancException& e)
+            {
+              LOG(ERROR) << "Exception on callable: " << e.What();
+            }
+            catch (StoneException& e)
+            {
+              LOG(ERROR) << "Exception on callable: " << e.What();
+            }
+            catch (...)
+            {
+              LOG(ERROR) << "Native exception on callable";
+            }
           }
         }
+        else
+        {
+          // TODO => Remove "it" from the list of callables => This
+          // allows to suppress the need for "Unregister()"
+        }
       }
     }
   }
@@ -122,21 +109,15 @@
   }
 
   
-  void IObservable::EmitMessage(const IObserver& observer,
+  void IObservable::EmitMessage(boost::weak_ptr<IObserver> observer,
                                 const IMessage& message)
   {
-    LOG(TRACE) << "IObservable::EmitMessage observer = "
-      << std::hex << &observer << std::dec;
-    EmitMessageInternal(&observer, message);
-  }
-  
-  void IObservable::RegisterForwarder(IMessageForwarder* forwarder)
-  {
-    if (forwarder == NULL)
+    //LOG(TRACE) << "IObservable::EmitMessage observer = " << std::hex << &observer << std::dec;
+
+    boost::shared_ptr<IObserver> lock(observer.lock());
+    if (lock)
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      EmitMessageInternal(lock.get(), message);
     }
-    
-    forwarders_.insert(forwarder);
   }
 }
--- a/Framework/Messages/IObservable.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/IObservable.h	Fri Nov 29 11:03:41 2019 +0100
@@ -24,8 +24,6 @@
 #include "../StoneEnumerations.h"
 #include "ICallable.h"
 #include "IObserver.h"
-#include "MessageBroker.h"
-#include "MessageForwarder.h"
 
 #include <set>
 #include <map>
@@ -37,39 +35,20 @@
   private:
     typedef std::map<MessageIdentifier, std::set<ICallable*> >  Callables;
 
-    typedef std::set<IMessageForwarder*>     Forwarders;
-
-    MessageBroker&  broker_;
     Callables       callables_;
-    Forwarders      forwarders_;
 
     void EmitMessageInternal(const IObserver* receiver,
                              const IMessage& message);
 
   public:
-    IObservable(MessageBroker& broker) :
-      broker_(broker)
-    {
-    }
-
     virtual ~IObservable();
 
-    MessageBroker& GetBroker() const
-    {
-      return broker_;
-    }
-
-    // Takes ownsership
-    void RegisterObserverCallback(ICallable* callable);
-
-    void Unregister(IObserver* observer);
+    // Takes ownsership of the callable
+    void RegisterCallable(ICallable* callable);
 
     void BroadcastMessage(const IMessage& message);
 
-    void EmitMessage(const IObserver& observer,
+    void EmitMessage(boost::weak_ptr<IObserver> observer,
                      const IMessage& message);
-
-    // Takes ownsership
-    void RegisterForwarder(IMessageForwarder* forwarder);
   };
 }
--- a/Framework/Messages/IObserver.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "IObserver.h"
-
-#include "IMessage.h"
-#include "../StoneException.h"
-
-#include <Core/Logging.h>
-#include <Core/Toolbox.h>
-
-namespace OrthancStone 
-{
-  IObserver::IObserver(MessageBroker& broker)
-    : broker_(broker)
-    , fingerprint_()
-  {
-    // we store the fingerprint_ as a char array to avoid problems when
-    // reading it in a deceased object.
-    // remember this is panic-level code to track zombie object usage
-    std::string fingerprint = Orthanc::Toolbox::GenerateUuid();
-    const char* fingerprintRaw = fingerprint.c_str();
-    memcpy(fingerprint_, fingerprintRaw, 37);
-    broker_.Register(*this);
-  }
-
-
-  IObserver::~IObserver()
-  {
-    try
-    {
-      LOG(TRACE) << "IObserver(" << std::hex << this << std::dec << ")::~IObserver : fingerprint_ == " << fingerprint_;
-      const char* deadMarker = "deadbeef-dead-dead-0000-0000deadbeef";
-      ORTHANC_ASSERT(strlen(deadMarker) == 36);
-      memcpy(fingerprint_, deadMarker, 37);
-      broker_.Unregister(*this);
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in ~IObserver: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in ~IObserver: " << e.What();
-      }
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in ~IObserver: " << e.what();
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in ~IObserver";
-    }
-  }
-
-
-  bool IObserver::DoesFingerprintLookGood() const
-  {
-    for (size_t i = 0; i < 36; ++i) {
-      bool ok = false;
-      if (fingerprint_[i] >= 'a' && fingerprint_[i] <= 'f')
-        ok = true;
-      if (fingerprint_[i] >= '0' && fingerprint_[i] <= '9')
-        ok = true;
-      if (fingerprint_[i] == '-')
-        ok = true;
-      if (!ok)
-        return false;
-    }
-    return fingerprint_[36] == 0;
-  }
-}
--- a/Framework/Messages/IObserver.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/IObserver.h	Fri Nov 29 11:03:41 2019 +0100
@@ -21,33 +21,19 @@
 
 #pragma once
 
-#include "MessageBroker.h"
+#include <boost/noncopyable.hpp>
 
 namespace OrthancStone 
 {
   class IObserver : public boost::noncopyable
   {
-  private:
-    MessageBroker&  broker_;
-    // the following is a UUID that is used to disambiguate different observers
-    // that may have the same address
-    char     fingerprint_[37];
-
   public:
-    IObserver(MessageBroker& broker);
-
-    virtual ~IObserver();
-
-    const char* GetFingerprint() const
+    IObserver()
     {
-      return fingerprint_;
     }
 
-    bool DoesFingerprintLookGood() const;
-
-    MessageBroker& GetBroker() const
+    virtual ~IObserver()
     {
-      return broker_;
     }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/LockingEmitter.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,43 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "LockingEmitter.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  namespace Deprecated
+  {
+    void LockingEmitter::EmitMessage(boost::weak_ptr<IObserver> observer,
+                                     const IMessage& message)
+    {
+      try
+      {
+        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
+        oracleObservable_.EmitMessage(observer, message);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while emitting a message: " << e.What();
+      }
+    }
+  }
+}
--- a/Framework/Messages/LockingEmitter.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Messages/LockingEmitter.h	Fri Nov 29 11:03:41 2019 +0100
@@ -26,89 +26,67 @@
 #include "IMessageEmitter.h"
 #include "IObservable.h"
 
-#include <boost/thread.hpp>
+#include <Core/Enumerations.h>  // For ORTHANC_OVERRIDE
+
+#include <boost/thread/shared_mutex.hpp>
 
 namespace OrthancStone
 {
-  /**
-   * This class is used when using the ThreadedOracle : since messages
-   * can be sent from multiple Oracle threads, this IMessageEmitter
-   * implementation serializes the callbacks.
-   * 
-   * The internal mutex used in Oracle messaging can also be used to 
-   * protect the application data. Thus, this class can be used as a single
-   * application-wide mutex.
-   */
-  class LockingEmitter : public IMessageEmitter
+  namespace Deprecated
   {
-  private:
-    boost::shared_mutex  mutex_;
-    MessageBroker        broker_;
-    IObservable          oracleObservable_;
-
-  public:
-    LockingEmitter() :
-      oracleObservable_(broker_)
+    /**
+     * This class is used when using the ThreadedOracle : since messages
+     * can be sent from multiple Oracle threads, this IMessageEmitter
+     * implementation serializes the callbacks.
+     * 
+     * The internal mutex used in Oracle messaging can also be used to 
+     * protect the application data. Thus, this class can be used as a single
+     * application-wide mutex.
+     */
+    class LockingEmitter : public IMessageEmitter
     {
-    }
-
-    MessageBroker& GetBroker()
-    {
-      return broker_;
-    }
+    private:
+      boost::shared_mutex  mutex_;
+      IObservable          oracleObservable_;
 
-    virtual void EmitMessage(const IObserver& observer,
-      const IMessage& message) ORTHANC_OVERRIDE
-    {
-      try
-      {
-        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
-        oracleObservable_.EmitMessage(observer, message);
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        LOG(ERROR) << "Exception while emitting a message: " << e.What();
-      }
-    }
+    public:
+      virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
+                               const IMessage& message) ORTHANC_OVERRIDE;
 
 
-    class ReaderLock : public boost::noncopyable
-    {
-    private:
-      LockingEmitter& that_;
-      boost::shared_lock<boost::shared_mutex>  lock_;
+      class ReaderLock : public boost::noncopyable
+      {
+      private:
+        LockingEmitter& that_;
+        boost::shared_lock<boost::shared_mutex>  lock_;
 
-    public:
-      ReaderLock(LockingEmitter& that) :
+      public:
+        ReaderLock(LockingEmitter& that) :
         that_(that),
         lock_(that.mutex_)
-      {
-      }
-    };
+        {
+        }
+      };
 
 
-    class WriterLock : public boost::noncopyable
-    {
-    private:
-      LockingEmitter& that_;
-      boost::unique_lock<boost::shared_mutex>  lock_;
+      class WriterLock : public boost::noncopyable
+      {
+      private:
+        LockingEmitter& that_;
+        boost::unique_lock<boost::shared_mutex>  lock_;
 
-    public:
-      WriterLock(LockingEmitter& that) :
+      public:
+        WriterLock(LockingEmitter& that) :
         that_(that),
         lock_(that.mutex_)
-      {
-      }
-
-      MessageBroker& GetBroker()
-      {
-        return that_.broker_;
-      }
+        {
+        }
 
-      IObservable& GetOracleObservable()
-      {
-        return that_.oracleObservable_;
-      }
+        IObservable& GetOracleObservable()
+        {
+          return that_.oracleObservable_;
+        }
+      };
     };
-  };
+  }
 }
--- a/Framework/Messages/MessageBroker.h	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "boost/noncopyable.hpp"
-
-#include <set>
-
-namespace OrthancStone
-{
-  class IObserver;
-
-  /*
-   * This is a central message broker.  It keeps track of all observers and knows
-   * when an observer is deleted.
-   * This way, it can prevent an observable to send a message to a deleted observer.
-   */
-  class MessageBroker : public boost::noncopyable
-  {
-  private:
-    std::set<const IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
-
-  public:
-    MessageBroker()
-    {
-    }
-
-    void Register(const IObserver& observer)
-    {
-      activeObservers_.insert(&observer);
-    }
-
-    void Unregister(const IObserver& observer)
-    {
-      activeObservers_.erase(&observer);
-    }
-
-    bool IsActive(const IObserver& observer)
-    {
-      return activeObservers_.find(&observer) != activeObservers_.end();
-    }
-  };
-}
--- a/Framework/Messages/MessageForwarder.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "MessageForwarder.h"
-
-#include "IObservable.h"
-
-namespace OrthancStone
-{
-
-  void IMessageForwarder::ForwardMessageInternal(const IMessage& message)
-  {
-    emitter_.BroadcastMessage(message);
-  }
-
-  void IMessageForwarder::RegisterForwarderInEmitter()
-  {
-    emitter_.RegisterForwarder(this);
-  }
-}
--- a/Framework/Messages/MessageForwarder.h	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "ICallable.h"
-#include "IObserver.h"
-
-#include <boost/noncopyable.hpp>
-
-namespace OrthancStone
-{
-
-  class IObservable;
-
-  class IMessageForwarder : public IObserver
-  {
-    IObservable& emitter_;
-  public:
-    IMessageForwarder(MessageBroker& broker, IObservable& emitter)
-      : IObserver(broker),
-        emitter_(emitter)
-    {}
-    virtual ~IMessageForwarder() {}
-
-  protected:
-    void ForwardMessageInternal(const IMessage& message);
-    void RegisterForwarderInEmitter();
-
-  };
-
-  /* When an Observer (B) simply needs to re-emit a message it has received, instead of implementing
-   * a specific member function to forward the message, it can create a MessageForwarder.
-   * The MessageForwarder will re-emit the message "in the name of (B)"
-   *
-   * Consider the chain where
-   * A is an observable
-   * |
-   * B is an observer of A and observable
-   * |
-   * C is an observer of B and knows that B is re-emitting many messages from A
-   *
-   * instead of implementing a callback, B will create a MessageForwarder that will emit the messages in his name:
-   * A.RegisterObserverCallback(new MessageForwarder<A::MessageType>(broker, *this)  // where "this" is B
-   *
-   * in C:
-   * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback))   // where "this" is C
-   */
-  template<typename TMessage>
-  class MessageForwarder : public IMessageForwarder, public Callable<MessageForwarder<TMessage>, TMessage>
-  {
-  public:
-    MessageForwarder(MessageBroker& broker,
-                     IObservable& emitter // the object that will emit the messages to forward
-                     )
-      : IMessageForwarder(broker, emitter),
-        Callable<MessageForwarder<TMessage>, TMessage>(*this, &MessageForwarder::ForwardMessage)
-    {
-      RegisterForwarderInEmitter();
-    }
-
-protected:
-    void ForwardMessage(const TMessage& message)
-    {
-      ForwardMessageInternal(message);
-    }
-
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/ObserverBase.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ICallable.h"
+#include "IObserver.h"
+#include "IObservable.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/enable_shared_from_this.hpp>
+
+namespace OrthancStone 
+{
+  template <typename TObserver>
+  class ObserverBase : 
+    public IObserver,
+    public boost::enable_shared_from_this<TObserver>
+  {
+  public:
+    boost::shared_ptr<TObserver> GetSharedObserver()
+    {
+      try
+      {
+        return this->shared_from_this();
+      }
+      catch (boost::bad_weak_ptr&)
+      {
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_InternalError,
+          "Cannot get a shared pointer to an observer from its constructor, "
+          "or the observer is not created as a shared pointer");
+      }
+    }
+
+    template <typename TMessage>
+    ICallable* CreateCallable(void (TObserver::* MemberMethod) (const TMessage&))
+    {
+      return new Callable<TObserver, TMessage>(GetSharedObserver(), MemberMethod);
+    }
+
+    template <typename TMessage>
+    void Register(IObservable& observable,
+                  void (TObserver::* MemberMethod) (const TMessage&))
+    {
+      observable.RegisterCallable(CreateCallable(MemberMethod));
+    }
+  };
+}
--- a/Framework/OpenGL/OpenGLIncludes.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/OpenGL/OpenGLIncludes.h	Fri Nov 29 11:03:41 2019 +0100
@@ -32,6 +32,8 @@
 #if defined(__APPLE__)
 #  include <OpenGL/gl.h>
 #  include <OpenGL/glext.h>
+#elif defined(QT_VERSION_MAJOR) && (QT_VERSION >= 5)
+// Qt5 takes care of the inclusions
 #elif defined(_WIN32)
 // On Windows, use the compatibility headers provided by glew
 #  include <GL/glew.h>
--- a/Framework/OpenGL/SdlOpenGLContext.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/OpenGL/SdlOpenGLContext.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -68,6 +68,7 @@
         GLenum err = glewInit();
         if (GLEW_OK != err)
         {
+          LOG(ERROR) << glewGetErrorString(err);
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
                                           "Cannot initialize glew");
         }
--- a/Framework/OpenGL/SdlOpenGLContext.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/OpenGL/SdlOpenGLContext.h	Fri Nov 29 11:03:41 2019 +0100
@@ -26,6 +26,8 @@
 #include "IOpenGLContext.h"
 #include "../Viewport/SdlWindow.h"
 
+#include <SDL_render.h>
+
 #include <Core/Enumerations.h>
 
 namespace OrthancStone
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GenericOracleRunner.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,518 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "GenericOracleRunner.h"
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#include "GetOrthancImageCommand.h"
+#include "GetOrthancWebViewerJpegCommand.h"
+#include "HttpCommand.h"
+#include "OracleCommandExceptionMessage.h"
+#include "OrthancRestApiCommand.h"
+#include "ParseDicomFromFileCommand.h"
+#include "ParseDicomFromWadoCommand.h"
+#include "ReadFileCommand.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "ParseDicomSuccessMessage.h"
+#  include <dcmtk/dcmdata/dcdeftag.h>
+#  include <dcmtk/dcmdata/dcfilefo.h>
+static unsigned int BUCKET_DICOMDIR = 0;
+static unsigned int BUCKET_SOP = 1;
+#endif
+
+#include <Core/Compression/GzipCompressor.h>
+#include <Core/HttpClient.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+#include <Core/SystemToolbox.h>
+
+#include <boost/filesystem.hpp>
+
+
+
+namespace OrthancStone
+{
+  static void CopyHttpHeaders(Orthanc::HttpClient& client,
+                              const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator
+           it = headers.begin(); it != headers.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+  }
+
+
+  static void DecodeAnswer(std::string& answer,
+                           const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
+
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); 
+         it != headers.end(); ++it)
+    {
+      std::string s;
+      Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+      if (s == "content-encoding")
+      {
+        if (it->second == "gzip")
+        {
+          contentEncoding = Orthanc::HttpCompression_Gzip;
+        }
+        else 
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                          "Unsupported HTTP Content-Encoding: " + it->second);
+        }
+
+        break;
+      }
+    }
+
+    if (contentEncoding == Orthanc::HttpCompression_Gzip)
+    {
+      std::string compressed;
+      answer.swap(compressed);
+          
+      Orthanc::GzipCompressor compressor;
+      compressor.Uncompress(answer, compressed.c_str(), compressed.size());
+
+      LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
+                << " to " << answer.size() << " bytes";
+    }
+  }
+
+
+  static void RunHttpCommand(std::string& answer,
+                             Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                             const HttpCommand& command)
+  {
+    Orthanc::HttpClient client;
+    client.SetUrl(command.GetUrl());
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.HasCredentials())
+    {
+      client.SetCredentials(command.GetUsername().c_str(), command.GetPassword().c_str());
+    }
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetBody(command.GetBody());
+    }
+
+    client.ApplyAndThrowException(answer, answerHeaders);
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const HttpCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunHttpCommand(answer, answerHeaders, command);
+    
+    HttpCommand::SuccessMessage message(command, answerHeaders, answer);
+    emitter.EmitMessage(receiver, message);
+  }
+
+  
+  static void RunOrthancRestApiCommand(std::string& answer,
+                                       Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                                       const Orthanc::WebServiceParameters& orthanc,
+                                       const OrthancRestApiCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetBody(command.GetBody());
+    }
+
+    client.ApplyAndThrowException(answer, answerHeaders);
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const OrthancRestApiCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
+
+    OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
+    emitter.EmitMessage(receiver, message);
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const GetOrthancImageCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+    
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(receiver, emitter, answer, answerHeaders);
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const GetOrthancWebViewerJpegCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(receiver, emitter, answer);
+  }
+
+
+  static std::string GetPath(const std::string& root,
+                             const std::string& file)
+  {
+    boost::filesystem::path a(root);
+    boost::filesystem::path b(file);
+
+    boost::filesystem::path c;
+    if (b.is_absolute())
+    {
+      c = b;
+    }
+    else
+    {
+      c = a / b;
+    }
+
+    return c.string();
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const std::string& root,
+                          const ReadFileCommand& command)
+  {
+    std::string path = GetPath(root, command.GetPath());
+    LOG(TRACE) << "Oracle reading file: " << path;
+
+    std::string content;
+    Orthanc::SystemToolbox::ReadFile(content, path, true /* log */);
+
+    ReadFileCommand::SuccessMessage message(command, content);
+    emitter.EmitMessage(receiver, message);
+  }
+
+
+#if ORTHANC_ENABLE_DCMTK == 1
+  static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize,  /* OUT */
+                                              const std::string& path,
+                                              bool isPixelData)
+  {
+    if (!Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
+    }
+    
+    LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without")
+               << " pixel data: " << path;
+    
+    boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
+    
+    fileSize = Orthanc::SystemToolbox::GetFileSize(path);
+    
+    // Check for 32bit systems
+    if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    DcmFileFormat dicom;
+    bool ok;
+    
+    if (isPixelData)
+    {
+      ok = dicom.loadFile(path.c_str()).good();
+    }
+    else
+    {
+#if DCMTK_VERSION_NUMBER >= 362
+      /**
+       * NB : We could stop at (0x3007, 0x0000) instead of
+       * DCM_PixelData as the Stone framework does not use further
+       * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still
+       * use "PixelData" as this does not change the runtime much, and
+       * as it is more explicit.
+       **/
+      static const DcmTagKey STOP = DCM_PixelData;
+      //static const DcmTagKey STOP(0x3007, 0x0000);
+
+      ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
+                                  DCM_MaxReadLength, ERM_autoDetect, STOP).good();
+#else
+      // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
+      ok = dicom.loadFile(path.c_str()).good();
+#endif
+    }
+
+    if (ok)
+    {
+      std::auto_ptr<Orthanc::ParsedDicomFile> result(new Orthanc::ParsedDicomFile(dicom));
+
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
+
+      return result.release();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Cannot parse file: " + path);
+    }
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const std::string& root,
+                          const ParseDicomFromFileCommand& command)
+  {
+    const std::string path = GetPath(root, command.GetPath());
+
+    if (cache)
+    {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path);
+      if (reader.IsValid() &&
+          (!command.IsPixelDataIncluded() ||
+           reader.HasPixelData()))
+      {
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
+    }
+
+    uint64_t fileSize;
+    std::auto_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded()));
+
+    if (fileSize != static_cast<size_t>(fileSize))
+    {
+      // Cannot load such a large file on 32-bit architecture
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    {
+      ParseDicomSuccessMessage message
+        (command, *parsed, static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+      emitter.EmitMessage(receiver, message);
+    }
+
+    if (cache)
+    {
+      // Store it into the cache for future use
+      
+      // Invalidate to overwrite DICOM instance that would already
+      // be stored without pixel data
+      cache->Invalidate(BUCKET_DICOMDIR, path);
+      
+      cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(),
+                     static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+    }
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const ParseDicomFromWadoCommand& command)
+  {
+    if (cache)
+    {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid());
+      if (reader.IsValid() &&
+          reader.HasPixelData())
+      {
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
+    }
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+
+    switch (command.GetRestCommand().GetType())
+    {
+      case IOracleCommand::Type_Http:
+        RunHttpCommand(answer, answerHeaders, dynamic_cast<const HttpCommand&>(command.GetRestCommand()));
+        break;
+        
+      case IOracleCommand::Type_OrthancRestApi:
+        RunOrthancRestApiCommand(answer, answerHeaders, orthanc,
+                                 dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand()));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    size_t fileSize;
+    std::auto_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
+
+    {
+      ParseDicomSuccessMessage message(command, *parsed, fileSize,
+                                       true /* pixel data always is included in WADO-RS */);
+      emitter.EmitMessage(receiver, message);
+    }
+
+    if (cache)
+    {
+      // Store it into the cache for future use
+      cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true);
+    }
+  }
+#endif
+
+
+  void GenericOracleRunner::Run(boost::weak_ptr<IObserver> receiver,
+                                IMessageEmitter& emitter,
+                                const IOracleCommand& command)
+  {
+    Orthanc::ErrorCode error = Orthanc::ErrorCode_Success;
+    
+    try
+    {
+      switch (command.GetType())
+      {
+        case IOracleCommand::Type_Sleep:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
+                                          "Sleep command cannot be executed by the runner");
+
+        case IOracleCommand::Type_Http:
+          RunInternal(receiver, emitter, dynamic_cast<const HttpCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_OrthancRestApi:
+          RunInternal(receiver, emitter, orthanc_,
+                      dynamic_cast<const OrthancRestApiCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_GetOrthancImage:
+          RunInternal(receiver, emitter, orthanc_,
+                      dynamic_cast<const GetOrthancImageCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+          RunInternal(receiver, emitter, orthanc_,
+                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_ReadFile:
+          RunInternal(receiver, emitter, rootDirectory_,
+                      dynamic_cast<const ReadFileCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_ParseDicomFromFile:
+        case IOracleCommand::Type_ParseDicomFromWado:
+#if ORTHANC_ENABLE_DCMTK == 1
+          switch (command.GetType())
+          {
+            case IOracleCommand::Type_ParseDicomFromFile:
+              RunInternal(receiver, emitter, dicomCache_, rootDirectory_,
+                          dynamic_cast<const ParseDicomFromFileCommand&>(command));
+              break;
+
+            case IOracleCommand::Type_ParseDicomFromWado:
+              RunInternal(receiver, emitter, dicomCache_, orthanc_,
+                          dynamic_cast<const ParseDicomFromWadoCommand&>(command));
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }            
+          break;
+#else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                          "DCMTK must be enabled to parse DICOM files");
+#endif
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception within the oracle: " << e.What();
+      error = e.GetErrorCode();
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Threaded exception within the oracle";
+      error = Orthanc::ErrorCode_InternalError;
+    }
+
+    if (error != Orthanc::ErrorCode_Success)
+    {
+      OracleCommandExceptionMessage message(command, error);
+      emitter.EmitMessage(receiver, message);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GenericOracleRunner.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,87 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../Toolbox/ParsedDicomCache.h"
+#endif
+
+#include "IOracleCommand.h"
+#include "../Messages/IMessageEmitter.h"
+
+#include <Core/Enumerations.h>  // For ORTHANC_OVERRIDE
+#include <Core/WebServiceParameters.h>
+
+namespace OrthancStone
+{
+  class GenericOracleRunner : public boost::noncopyable
+  {
+  private:
+    Orthanc::WebServiceParameters  orthanc_;
+    std::string                    rootDirectory_;
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    boost::shared_ptr<ParsedDicomCache>  dicomCache_;
+#endif
+
+  public:
+    GenericOracleRunner() :
+      rootDirectory_(".")
+    {
+    }
+
+    void SetOrthanc(const Orthanc::WebServiceParameters& orthanc)
+    {
+      orthanc_ = orthanc;
+    }
+
+    const Orthanc::WebServiceParameters& GetOrthanc() const
+    {
+      return orthanc_;
+    }
+
+    void SetRootDirectory(const std::string& rootDirectory)
+    {
+      rootDirectory_ = rootDirectory;
+    }
+
+    const std::string GetRootDirectory() const
+    {
+      return rootDirectory_;
+    }
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    void SetDicomCache(boost::shared_ptr<ParsedDicomCache> cache)
+    {
+      dicomCache_ = cache;
+    }
+#endif
+
+    void Run(boost::weak_ptr<IObserver> receiver,
+             IMessageEmitter& emitter,
+             const IOracleCommand& command);
+  };
+}
--- a/Framework/Oracle/GetOrthancImageCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/GetOrthancImageCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -29,20 +29,6 @@
 
 namespace OrthancStone
 {
-  GetOrthancImageCommand::SuccessMessage::SuccessMessage(const GetOrthancImageCommand& command,
-                                                         Orthanc::ImageAccessor* image,   // Takes ownership
-                                                         Orthanc::MimeType mime) :
-    OriginMessage(command),
-    image_(image),
-    mime_(mime)
-  {
-    if (image == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-  }
-
-
   GetOrthancImageCommand::GetOrthancImageCommand() :
     uri_("/"),
     timeout_(600),
@@ -58,32 +44,43 @@
   }
 
 
-  void GetOrthancImageCommand::SetInstanceUri(const std::string& instance,
-                                              Orthanc::PixelFormat pixelFormat)
+  static std::string GetFormatSuffix(Orthanc::PixelFormat pixelFormat)
   {
-    uri_ = "/instances/" + instance;
-          
     switch (pixelFormat)
     {
       case Orthanc::PixelFormat_RGB24:
-        uri_ += "/preview";
-        break;
+        return "preview";
       
       case Orthanc::PixelFormat_Grayscale16:
-        uri_ += "/image-uint16";
-        break;
+        return "image-uint16";
       
       case Orthanc::PixelFormat_SignedGrayscale16:
-        uri_ += "/image-int16";
-        break;
+        return "image-int16";
       
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
   }
 
-  void GetOrthancImageCommand::ProcessHttpAnswer(IMessageEmitter& emitter,
-                                                 const IObserver& receiver,
+
+  void GetOrthancImageCommand::SetInstanceUri(const std::string& instance,
+                                              Orthanc::PixelFormat pixelFormat)
+  {
+    uri_ = "/instances/" + instance + "/" + GetFormatSuffix(pixelFormat);
+  }
+
+
+  void GetOrthancImageCommand::SetFrameUri(const std::string& instance,
+                                           unsigned int frame,
+                                           Orthanc::PixelFormat pixelFormat)
+  {
+    uri_ = ("/instances/" + instance + "/frames/" +
+            boost::lexical_cast<std::string>(frame) + "/" + GetFormatSuffix(pixelFormat));
+  }
+
+
+  void GetOrthancImageCommand::ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
+                                                 IMessageEmitter& emitter,
                                                  const std::string& answer,
                                                  const HttpHeaders& answerHeaders) const
   {
@@ -147,7 +144,7 @@
       }
     }
 
-    SuccessMessage message(*this, image.release(), contentType);
+    SuccessMessage message(*this, *image, contentType);
     emitter.EmitMessage(receiver, message);
   }
 }
--- a/Framework/Oracle/GetOrthancImageCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/GetOrthancImageCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Messages/IMessageEmitter.h"
-#include "OracleCommandWithPayload.h"
+#include "OracleCommandBase.h"
 
 #include <Core/Images/ImageAccessor.h>
 
@@ -30,7 +30,7 @@
 
 namespace OrthancStone
 {
-  class GetOrthancImageCommand : public OracleCommandWithPayload
+  class GetOrthancImageCommand : public OracleCommandBase
   {
   public:
     typedef std::map<std::string, std::string>  HttpHeaders;
@@ -40,17 +40,22 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      std::auto_ptr<Orthanc::ImageAccessor>  image_;
-      Orthanc::MimeType                      mime_;
+      const Orthanc::ImageAccessor&  image_;
+      Orthanc::MimeType              mime_;
 
     public:
       SuccessMessage(const GetOrthancImageCommand& command,
-                     Orthanc::ImageAccessor* image,   // Takes ownership
-                     Orthanc::MimeType mime);
+                     const Orthanc::ImageAccessor& image,
+                     Orthanc::MimeType mime) :
+        OriginMessage(command),
+        image_(image),
+        mime_(mime)
+      {
+      }
 
       const Orthanc::ImageAccessor& GetImage() const
       {
-        return *image_;
+        return image_;
       }
 
       Orthanc::MimeType GetMimeType() const
@@ -67,6 +72,15 @@
     bool                  hasExpectedFormat_;
     Orthanc::PixelFormat  expectedFormat_;
 
+    GetOrthancImageCommand(const GetOrthancImageCommand& other) :
+      uri_(other.uri_),
+      headers_(other.headers_),
+      timeout_(other.timeout_),
+      hasExpectedFormat_(other.hasExpectedFormat_),
+      expectedFormat_(other.expectedFormat_)
+    {
+    }
+
   public:
     GetOrthancImageCommand();
 
@@ -75,6 +89,11 @@
       return Type_GetOrthancImage;
     }
 
+    virtual IOracleCommand* Clone() const
+    {
+      return new GetOrthancImageCommand(*this);
+    }
+
     void SetExpectedPixelFormat(Orthanc::PixelFormat format);
 
     void SetUri(const std::string& uri)
@@ -85,6 +104,10 @@
     void SetInstanceUri(const std::string& instance,
                         Orthanc::PixelFormat pixelFormat);
 
+    void SetFrameUri(const std::string& instance,
+                     unsigned int frame,
+                     Orthanc::PixelFormat pixelFormat);
+
     void SetHttpHeader(const std::string& key,
                        const std::string& value)
     {
@@ -111,8 +134,8 @@
       return timeout_;
     }
 
-    void ProcessHttpAnswer(IMessageEmitter& emitter,
-                           const IObserver& receiver,
+    void ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
+                           IMessageEmitter& emitter,
                            const std::string& answer,
                            const HttpHeaders& answerHeaders) const;
   };
--- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -34,18 +34,6 @@
 
 namespace OrthancStone
 {
-  GetOrthancWebViewerJpegCommand::SuccessMessage::SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
-                                                                 Orthanc::ImageAccessor* image) :   // Takes ownership
-    OriginMessage(command),
-    image_(image)
-  {
-    if (image == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-  }
-
-
   GetOrthancWebViewerJpegCommand::GetOrthancWebViewerJpegCommand() :
     frame_(0),
     quality_(95),
@@ -76,8 +64,8 @@
   }
 
 
-  void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter,
-                                                         const IObserver& receiver,
+  void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
+                                                         IMessageEmitter& emitter,
                                                          const std::string& answer) const
   {
     // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
@@ -149,7 +137,7 @@
       }
       else
       {
-        SuccessMessage message(*this, reader.release());
+        SuccessMessage message(*this, *reader);
         emitter.EmitMessage(receiver, message);
         return;
       }
@@ -168,7 +156,7 @@
       }
       else
       {
-        SuccessMessage message(*this, reader.release());
+        SuccessMessage message(*this, *reader);
         emitter.EmitMessage(receiver, message);
         return;
       }
@@ -210,8 +198,8 @@
       float offset = static_cast<float>(stretchLow) / scaling;
       Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
     }
-    
-    SuccessMessage message(*this, image.release());
+
+    SuccessMessage message(*this, *image);
     emitter.EmitMessage(receiver, message);
   }
 }
--- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Messages/IMessageEmitter.h"
-#include "OracleCommandWithPayload.h"
+#include "OracleCommandBase.h"
 
 #include <Core/Images/ImageAccessor.h>
 
@@ -30,7 +30,7 @@
 
 namespace OrthancStone
 {
-  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
+  class GetOrthancWebViewerJpegCommand : public OracleCommandBase
   {
   public:
     typedef std::map<std::string, std::string>  HttpHeaders;
@@ -40,15 +40,19 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+      const Orthanc::ImageAccessor&  image_;
 
     public:
       SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
-                     Orthanc::ImageAccessor* image);   // Takes ownership
+                     const Orthanc::ImageAccessor& image) :
+        OriginMessage(command),
+        image_(image)
+      {
+      }
 
       const Orthanc::ImageAccessor& GetImage() const
       {
-        return *image_;
+        return image_;
       }
     };
 
@@ -60,6 +64,16 @@
     unsigned int          timeout_;
     Orthanc::PixelFormat  expectedFormat_;
 
+    GetOrthancWebViewerJpegCommand(const GetOrthancWebViewerJpegCommand& other) :
+      instanceId_(other.instanceId_),
+      frame_(other.frame_),
+      quality_(other.quality_),
+      headers_(other.headers_),
+      timeout_(other.timeout_),
+      expectedFormat_(other.expectedFormat_)
+    {
+    }
+    
   public:
     GetOrthancWebViewerJpegCommand();
 
@@ -68,6 +82,11 @@
       return Type_GetOrthancWebViewerJpeg;
     }
 
+    virtual IOracleCommand* Clone() const
+    {
+      return new GetOrthancWebViewerJpegCommand(*this);
+    }
+
     void SetExpectedPixelFormat(Orthanc::PixelFormat format)
     {
       expectedFormat_ = format;
@@ -128,8 +147,8 @@
 
     std::string GetUri() const;
 
-    void ProcessHttpAnswer(IMessageEmitter& emitter,
-                           const IObserver& receiver,
+    void ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver,
+                           IMessageEmitter& emitter,
                            const std::string& answer) const;
   };
 }
--- a/Framework/Oracle/HttpCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/HttpCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -28,16 +28,6 @@
 
 namespace OrthancStone
 {
-  HttpCommand::SuccessMessage::SuccessMessage(const HttpCommand& command,
-                                              const HttpHeaders& answerHeaders,
-                                              std::string& answer) :
-    OriginMessage(command),
-    headers_(answerHeaders),
-    answer_(answer)
-  {
-  }
-
-
   void HttpCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const
   {
     Json::Reader reader;
@@ -76,4 +66,30 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
   }
+
+
+  const std::string& HttpCommand::GetUsername() const
+  {
+    if (HasCredentials())
+    {
+      return username_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  const std::string& HttpCommand::GetPassword() const
+  {
+    if (HasCredentials())
+    {
+      return password_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
 }
--- a/Framework/Oracle/HttpCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/HttpCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Messages/IMessage.h"
-#include "OracleCommandWithPayload.h"
+#include "OracleCommandBase.h"
 
 #include <Core/Enumerations.h>
 
@@ -31,7 +31,7 @@
 
 namespace OrthancStone
 {
-  class HttpCommand : public OracleCommandWithPayload
+  class HttpCommand : public OracleCommandBase
   {
   public:
     typedef std::map<std::string, std::string>  HttpHeaders;
@@ -41,13 +41,18 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      HttpHeaders   headers_;
-      std::string   answer_;
+      const HttpHeaders&  headers_;
+      const std::string&  answer_;
 
     public:
       SuccessMessage(const HttpCommand& command,
                      const HttpHeaders& answerHeaders,
-                     std::string& answer  /* will be swapped to avoid a memcpy() */);
+                     const std::string& answer) :
+        OriginMessage(command),
+        headers_(answerHeaders),
+        answer_(answer)
+      {
+      }
 
       const std::string& GetAnswer() const
       {
@@ -56,7 +61,7 @@
 
       void ParseJsonBody(Json::Value& target) const;
 
-      const HttpHeaders&  GetAnswerHeaders() const
+      const HttpHeaders& GetAnswerHeaders() const
       {
         return headers_;
       }
@@ -69,6 +74,19 @@
     std::string          body_;
     HttpHeaders          headers_;
     unsigned int         timeout_;
+    std::string          username_;
+    std::string          password_;
+
+    HttpCommand(const HttpCommand& other) :
+      method_(other.method_),
+      url_(other.url_),
+      body_(other.body_),
+      headers_(other.headers_),
+      timeout_(other.timeout_),
+      username_(other.username_),
+      password_(other.password_)
+    {
+    }
 
   public:
     HttpCommand();
@@ -78,6 +96,11 @@
       return Type_Http;
     }
 
+    virtual IOracleCommand* Clone() const
+    {
+      return new HttpCommand(*this);
+    }
+
     void SetMethod(Orthanc::HttpMethod method)
     {
       method_ = method;
@@ -137,5 +160,27 @@
     {
       return timeout_;
     }
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password)
+    {
+      username_ = username;
+      password_ = password;
+    }
+
+    void ClearCredentials()
+    {
+      username_.clear();
+      password_.clear();
+    }
+
+    bool HasCredentials() const
+    {
+      return !username_.empty();
+    }
+
+    const std::string& GetUsername() const;
+
+    const std::string& GetPassword() const;
   };
 }
--- a/Framework/Oracle/IOracle.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/IOracle.h	Fri Nov 29 11:03:41 2019 +0100
@@ -24,6 +24,8 @@
 #include "../Messages/IObserver.h"
 #include "IOracleCommand.h"
 
+#include <boost/shared_ptr.hpp>
+
 namespace OrthancStone
 {
   class IOracle : public boost::noncopyable
@@ -33,7 +35,12 @@
     {
     }
 
-    virtual void Schedule(const IObserver& receiver,
+    /**
+     * Returns "true" iff the command has actually been queued. If
+     * "false" is returned, the command has been freed, and it won't
+     * be processed (this is the case if the oracle is stopped).
+     **/
+    virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
                           IOracleCommand* command) = 0;  // Takes ownership
   };
 }
--- a/Framework/Oracle/IOracleCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/IOracleCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include <boost/noncopyable.hpp>
+#include <Core/IDynamicObject.h>
 
 namespace OrthancStone
 {
@@ -30,11 +30,14 @@
   public:
     enum Type
     {
+      Type_GetOrthancImage,
+      Type_GetOrthancWebViewerJpeg,
       Type_Http,
-      Type_Sleep,
       Type_OrthancRestApi,
-      Type_GetOrthancImage,
-      Type_GetOrthancWebViewerJpeg
+      Type_ParseDicomFromFile,
+      Type_ParseDicomFromWado,
+      Type_ReadFile,
+      Type_Sleep
     };
 
     virtual ~IOracleCommand()
@@ -42,5 +45,8 @@
     }
 
     virtual Type GetType() const = 0;
+
+    // This only clones the command, *not* its possibly associated payload
+    virtual IOracleCommand* Clone() const = 0;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OracleCommandBase.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,67 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "OracleCommandBase.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void OracleCommandBase::AcquirePayload(Orthanc::IDynamicObject* payload)
+  {
+    if (payload == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      payload_.reset(payload);
+    }    
+  }
+
+
+  Orthanc::IDynamicObject& OracleCommandBase::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      LOG(ERROR) << "OracleCommandBase::GetPayload(): (!HasPayload())";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  Orthanc::IDynamicObject* OracleCommandBase::ReleasePayload()
+  {
+    if (HasPayload())
+    {
+      return payload_.release();
+    }
+    else
+    {
+      LOG(ERROR) << "OracleCommandBase::ReleasePayload(): (!HasPayload())";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OracleCommandBase.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,47 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "IOracleCommand.h"
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class OracleCommandBase : public IOracleCommand
+  {
+  private:
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+
+  public:
+    void AcquirePayload(Orthanc::IDynamicObject* payload);
+
+    virtual bool HasPayload() const
+    {
+      return (payload_.get() != NULL);
+    }
+
+    virtual Orthanc::IDynamicObject& GetPayload() const;
+
+    Orthanc::IDynamicObject* ReleasePayload();
+  };
+}
--- a/Framework/Oracle/OracleCommandExceptionMessage.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/OracleCommandExceptionMessage.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,34 +28,28 @@
 
 namespace OrthancStone
 {
-  class OracleCommandExceptionMessage : public IMessage
+  class OracleCommandExceptionMessage : public OriginMessage<IOracleCommand>
   {
     ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
 
   private:
-    const IOracleCommand&       command_;
-    Orthanc::OrthancException   exception_;
+    Orthanc::OrthancException  exception_;
 
   public:
     OracleCommandExceptionMessage(const IOracleCommand& command,
-                                  const Orthanc::OrthancException& exception) :
-      command_(command),
-      exception_(exception)
+                                  const Orthanc::ErrorCode& error) :
+      OriginMessage(command),
+      exception_(error)
     {
     }
 
     OracleCommandExceptionMessage(const IOracleCommand& command,
-                                  const Orthanc::ErrorCode& error) :
-      command_(command),
-      exception_(error)
+                                  const Orthanc::OrthancException& exception) :
+      OriginMessage(command),
+      exception_(exception)
     {
     }
 
-    const IOracleCommand& GetCommand() const
-    {
-      return command_;
-    }
-    
     const Orthanc::OrthancException& GetException() const
     {
       return exception_;
--- a/Framework/Oracle/OracleCommandWithPayload.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "OracleCommandWithPayload.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  void OracleCommandWithPayload::SetPayload(Orthanc::IDynamicObject* payload)
-  {
-    if (payload == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else
-    {
-      payload_.reset(payload);
-    }    
-  }
-
-
-  Orthanc::IDynamicObject& OracleCommandWithPayload::GetPayload() const
-  {
-    if (HasPayload())
-    {
-      return *payload_;
-    }
-    else
-    {
-      LOG(ERROR) << "OracleCommandWithPayload::GetPayload(): (!HasPayload())";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
-  Orthanc::IDynamicObject* OracleCommandWithPayload::ReleasePayload()
-  {
-    if (HasPayload())
-    {
-      return payload_.release();
-    }
-    else
-    {
-      LOG(ERROR) << "OracleCommandWithPayload::ReleasePayload(): (!HasPayload())";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-}
--- a/Framework/Oracle/OracleCommandWithPayload.h	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "IOracleCommand.h"
-
-#include <Core/IDynamicObject.h>
-
-#include <memory>
-
-namespace OrthancStone
-{
-  class OracleCommandWithPayload : public IOracleCommand
-  {
-  private:
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-
-  public:
-    void SetPayload(Orthanc::IDynamicObject* payload);
-
-    bool HasPayload() const
-    {
-      return (payload_.get() != NULL);
-    }
-
-    Orthanc::IDynamicObject& GetPayload() const;
-
-    Orthanc::IDynamicObject* ReleasePayload();
-  };
-}
--- a/Framework/Oracle/OrthancRestApiCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/OrthancRestApiCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -28,16 +28,6 @@
 
 namespace OrthancStone
 {
-  OrthancRestApiCommand::SuccessMessage::SuccessMessage(const OrthancRestApiCommand& command,
-                                                        const HttpHeaders& answerHeaders,
-                                                        std::string& answer) :
-    OriginMessage(command),
-    headers_(answerHeaders),
-    answer_(answer)
-  {
-  }
-
-
   void OrthancRestApiCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const
   {
     Json::Reader reader;
@@ -51,7 +41,8 @@
   OrthancRestApiCommand::OrthancRestApiCommand() :
     method_(Orthanc::HttpMethod_Get),
     uri_("/"),
-    timeout_(600)
+    timeout_(600),
+    applyPlugins_(false)
   {
   }
 
--- a/Framework/Oracle/OrthancRestApiCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/OrthancRestApiCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Messages/IMessage.h"
-#include "OracleCommandWithPayload.h"
+#include "OracleCommandBase.h"
 
 #include <Core/Enumerations.h>
 
@@ -31,7 +31,7 @@
 
 namespace OrthancStone
 {
-  class OrthancRestApiCommand : public OracleCommandWithPayload
+  class OrthancRestApiCommand : public OracleCommandBase
   {
   public:
     typedef std::map<std::string, std::string>  HttpHeaders;
@@ -41,14 +41,19 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      HttpHeaders   headers_;
-      std::string   answer_;
+      const HttpHeaders&  headers_;
+      const std::string&  answer_;
 
     public:
       SuccessMessage(const OrthancRestApiCommand& command,
                      const HttpHeaders& answerHeaders,
-                     std::string& answer  /* will be swapped to avoid a memcpy() */);
-
+                     const std::string& answer) :
+        OriginMessage(command),
+        headers_(answerHeaders),
+        answer_(answer)
+      {
+      }
+      
       const std::string& GetAnswer() const
       {
         return answer_;
@@ -69,7 +74,18 @@
     std::string          body_;
     HttpHeaders          headers_;
     unsigned int         timeout_;
+    bool                 applyPlugins_;  // Only makes sense for Stone as an Orthanc plugin
 
+    OrthancRestApiCommand(const OrthancRestApiCommand& other) :
+      method_(other.method_),
+      uri_(other.uri_),
+      body_(other.body_),
+      headers_(other.headers_),
+      timeout_(other.timeout_),
+      applyPlugins_(other.applyPlugins_)
+    {
+    }
+    
   public:
     OrthancRestApiCommand();
 
@@ -78,6 +94,11 @@
       return Type_OrthancRestApi;
     }
 
+    virtual IOracleCommand* Clone() const
+    {
+      return new OrthancRestApiCommand(*this);
+    }
+
     void SetMethod(Orthanc::HttpMethod method)
     {
       method_ = method;
@@ -137,5 +158,15 @@
     {
       return timeout_;
     }
+
+    void SetApplyPlugins(bool applyPlugins)
+    {
+      applyPlugins_ = applyPlugins;
+    }
+
+    bool IsApplyPlugins() const
+    {
+      return applyPlugins_;
+    }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromFileCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,43 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParseDicomFromFileCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/filesystem/path.hpp>
+
+namespace OrthancStone
+{
+  std::string ParseDicomFromFileCommand::GetDicomDirPath(const std::string& dicomDirPath,
+                                                         const std::string& file)
+  {
+    std::string tmp = file;
+
+#if !defined(_WIN32)
+    std::replace(tmp.begin(), tmp.end(), '\\', '/');
+#endif
+
+    boost::filesystem::path base = boost::filesystem::path(dicomDirPath).parent_path();
+
+    return (base / tmp).string();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromFileCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,84 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "OracleCommandBase.h"
+
+#include <string>
+
+namespace OrthancStone
+{
+  class ParseDicomFromFileCommand : public OracleCommandBase
+  {
+  private:
+    std::string  path_;
+    bool         pixelDataIncluded_;
+
+    ParseDicomFromFileCommand(const ParseDicomFromFileCommand& other) :
+      path_(other.path_),
+      pixelDataIncluded_(other.pixelDataIncluded_)
+    {
+    }
+
+  public:
+    ParseDicomFromFileCommand(const std::string& path) :
+      path_(path),
+      pixelDataIncluded_(true)
+    {
+    }
+
+    ParseDicomFromFileCommand(const std::string& dicomDirPath,
+                              const std::string& file) :
+      path_(GetDicomDirPath(dicomDirPath, file)),
+      pixelDataIncluded_(true)
+    {
+    }
+    
+    static std::string GetDicomDirPath(const std::string& dicomDirPath,
+                                       const std::string& file);
+
+    virtual Type GetType() const
+    {
+      return Type_ParseDicomFromFile;
+    }
+
+    virtual IOracleCommand* Clone() const
+    {
+      return new ParseDicomFromFileCommand(*this);
+    }
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    bool IsPixelDataIncluded() const
+    {
+      return pixelDataIncluded_;
+    }
+
+    void SetPixelDataIncluded(bool included)
+    {
+      pixelDataIncluded_ = included;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromWadoCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,58 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParseDicomFromWadoCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  ParseDicomFromWadoCommand::ParseDicomFromWadoCommand(const std::string& sopInstanceUid,
+                                                       IOracleCommand* restCommand) :
+    sopInstanceUid_(sopInstanceUid),
+    restCommand_(restCommand)
+  {
+    if (restCommand == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (restCommand_->GetType() != Type_Http &&
+        restCommand_->GetType() != Type_OrthancRestApi)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }        
+  }
+
+  
+  IOracleCommand* ParseDicomFromWadoCommand::Clone() const
+  {
+    assert(restCommand_.get() != NULL);
+    return new ParseDicomFromWadoCommand(sopInstanceUid_, restCommand_->Clone());
+  }
+
+
+  const IOracleCommand& ParseDicomFromWadoCommand::GetRestCommand() const
+  {
+    assert(restCommand_.get() != NULL);
+    return *restCommand_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromWadoCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,54 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "OracleCommandBase.h"
+
+#include <string>
+
+namespace OrthancStone
+{
+  class ParseDicomFromWadoCommand : public OracleCommandBase
+  {
+  private:
+    std::string                    sopInstanceUid_;
+    std::auto_ptr<IOracleCommand>  restCommand_;
+
+  public:
+    ParseDicomFromWadoCommand(const std::string& sopInstanceUid,
+                              IOracleCommand* restCommand);
+
+    virtual Type GetType() const
+    {
+      return Type_ParseDicomFromWado;
+    }
+
+    virtual IOracleCommand* Clone() const;
+
+    const std::string& GetSopInstanceUid() const
+    {
+      return sopInstanceUid_;
+    }
+    
+    const IOracleCommand& GetRestCommand() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomSuccessMessage.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,107 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParseDicomSuccessMessage.h"
+
+#include <Core/DicomParsing/ParsedDicomFile.h>
+#include <Core/HttpServer/MultipartStreamReader.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class MultipartHandler : public Orthanc::MultipartStreamReader::IHandler
+  {
+  private:
+    std::auto_ptr<Orthanc::ParsedDicomFile>  dicom_;
+    size_t                                   size_;
+
+  public:
+    MultipartHandler() :
+      size_(0)
+    {
+    }
+      
+    virtual void HandlePart(const std::map<std::string, std::string>& headers,
+                            const void* part,
+                            size_t size)
+    {
+      if (dicom_.get())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Multiple DICOM instances were contained in a WADO-RS request");
+      }
+      else
+      {
+        dicom_.reset(new Orthanc::ParsedDicomFile(part, size));
+        size_ = size;
+      }
+    }
+
+    Orthanc::ParsedDicomFile* ReleaseDicom()
+    {
+      if (dicom_.get())
+      {
+        return dicom_.release();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "WADO-RS request didn't contain any DICOM instance");
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return size_;
+    }
+  };
+
+  
+  Orthanc::ParsedDicomFile* ParseDicomSuccessMessage::ParseWadoAnswer(
+    size_t& fileSize /* OUT */,
+    const std::string& answer,
+    const std::map<std::string, std::string>& headers)
+  {
+    std::string contentType, subType, boundary, header;
+    if (Orthanc::MultipartStreamReader::GetMainContentType(header, headers) &&
+        Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, header) &&
+        contentType == "multipart/related" &&
+        subType == "application/dicom")
+    {
+      MultipartHandler handler;
+
+      {
+        Orthanc::MultipartStreamReader reader(boundary);
+        reader.SetHandler(handler);
+        reader.AddChunk(answer);
+        reader.CloseStream();
+      }
+
+      fileSize = handler.GetSize();
+      return handler.ReleaseDicom();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                      "Multipart/related answer of application/dicom was expected from DICOMweb server");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomSuccessMessage.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,85 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#if ORTHANC_ENABLE_DCMTK != 1
+#  error Support for DCMTK must be enabled to use ParseDicomFromFileCommand
+#endif
+
+#include "OracleCommandBase.h"
+#include "../Messages/IMessageEmitter.h"
+#include "../Messages/IObserver.h"
+
+#include <map>
+
+namespace Orthanc
+{
+  class ParsedDicomFile;
+}
+
+namespace OrthancStone
+{
+  class ParseDicomSuccessMessage : public OriginMessage<OracleCommandBase>
+  {
+    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+    
+  private:
+    Orthanc::ParsedDicomFile&  dicom_;
+    size_t                     fileSize_;
+    bool                       hasPixelData_;
+    
+  public:
+    ParseDicomSuccessMessage(const OracleCommandBase& command,
+                             Orthanc::ParsedDicomFile& dicom,
+                             size_t fileSize,
+                             bool hasPixelData) :
+      OriginMessage(command),
+      dicom_(dicom),
+      fileSize_(fileSize),
+      hasPixelData_(hasPixelData)
+    {
+    }
+      
+    Orthanc::ParsedDicomFile& GetDicom() const
+    {
+      return dicom_;
+    }
+
+    size_t GetFileSize() const
+    {
+      return fileSize_;
+    }
+
+    bool HasPixelData() const
+    {
+      return hasPixelData_;
+    }
+    
+    static Orthanc::ParsedDicomFile* ParseWadoAnswer(size_t& fileSize /* OUT */,
+                                                     const std::string& answer,
+                                                     const std::map<std::string, std::string>& headers);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ReadFileCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../Messages/IMessage.h"
+#include "OracleCommandBase.h"
+
+namespace OrthancStone
+{
+  class ReadFileCommand : public OracleCommandBase
+  {
+  public:
+    class SuccessMessage : public OriginMessage<ReadFileCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      const std::string& content_;
+
+    public:
+      SuccessMessage(const ReadFileCommand& command,
+                     const std::string& content) :
+        OriginMessage(command),
+        content_(content)
+      {
+      }
+
+      const std::string& GetContent() const
+      {
+        return content_;
+      }
+    };
+
+
+  private:
+    std::string  path_;
+
+  public:
+    ReadFileCommand(const std::string& path) : 
+      path_(path)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_ReadFile;
+    }
+
+    virtual IOracleCommand* Clone() const
+    {
+      return new ReadFileCommand(path_);
+    }
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+  };
+}
--- a/Framework/Oracle/SleepOracleCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/SleepOracleCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,11 +22,11 @@
 #pragma once
 
 #include "../Messages/IMessage.h"
-#include "OracleCommandWithPayload.h"
+#include "OracleCommandBase.h"
 
 namespace OrthancStone
 {
-  class SleepOracleCommand : public OracleCommandWithPayload
+  class SleepOracleCommand : public OracleCommandBase
   {
   private:
     unsigned int  milliseconds_;
@@ -44,6 +44,11 @@
       return Type_Sleep;
     }
 
+    virtual IOracleCommand* Clone() const
+    {
+      return new SleepOracleCommand(milliseconds_);
+    }
+
     unsigned int GetDelay() const
     {
       return milliseconds_;
--- a/Framework/Oracle/ThreadedOracle.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/ThreadedOracle.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -21,29 +21,21 @@
 
 #include "ThreadedOracle.h"
 
-#include "GetOrthancImageCommand.h"
-#include "GetOrthancWebViewerJpegCommand.h"
-#include "HttpCommand.h"
-#include "OrthancRestApiCommand.h"
 #include "SleepOracleCommand.h"
-#include "OracleCommandExceptionMessage.h"
 
-#include <Core/Compression/GzipCompressor.h>
-#include <Core/HttpClient.h>
+#include <Core/Logging.h>
 #include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-
 
 namespace OrthancStone
 {
   class ThreadedOracle::Item : public Orthanc::IDynamicObject
   {
   private:
-    const IObserver&                receiver_;
+    boost::weak_ptr<IObserver>      receiver_;
     std::auto_ptr<IOracleCommand>   command_;
 
   public:
-    Item(const IObserver& receiver,
+    Item(boost::weak_ptr<IObserver> receiver,
          IOracleCommand* command) :
       receiver_(receiver),
       command_(command)
@@ -54,7 +46,7 @@
       }
     }
 
-    const IObserver& GetReceiver() const
+    boost::weak_ptr<IObserver> GetReceiver()
     {
       return receiver_;
     }
@@ -73,12 +65,12 @@
     class Item
     {
     private:
-      const IObserver&                   receiver_;
+      boost::weak_ptr<IObserver>         receiver_;
       std::auto_ptr<SleepOracleCommand>  command_;
       boost::posix_time::ptime           expiration_;
 
     public:
-      Item(const IObserver& receiver,
+      Item(boost::weak_ptr<IObserver> receiver,
            SleepOracleCommand* command) :
         receiver_(receiver),
         command_(command)
@@ -123,7 +115,7 @@
       }
     }
 
-    void Add(const IObserver& receiver,
+    void Add(boost::weak_ptr<IObserver> receiver,
              SleepOracleCommand* command)   // Takes ownership
     {
       boost::mutex::scoped_lock lock(mutex_);
@@ -160,154 +152,6 @@
   };
 
 
-  static void CopyHttpHeaders(Orthanc::HttpClient& client,
-                              const Orthanc::HttpClient::HttpHeaders& headers)
-  {
-    for (Orthanc::HttpClient::HttpHeaders::const_iterator
-           it = headers.begin(); it != headers.end(); it++ )
-    {
-      client.AddHeader(it->first, it->second);
-    }
-  }
-
-
-  static void DecodeAnswer(std::string& answer,
-                           const Orthanc::HttpClient::HttpHeaders& headers)
-  {
-    Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
-
-    for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); 
-         it != headers.end(); ++it)
-    {
-      std::string s;
-      Orthanc::Toolbox::ToLowerCase(s, it->first);
-
-      if (s == "content-encoding")
-      {
-        if (it->second == "gzip")
-        {
-          contentEncoding = Orthanc::HttpCompression_Gzip;
-        }
-        else 
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                          "Unsupported HTTP Content-Encoding: " + it->second);
-        }
-
-        break;
-      }
-    }
-
-    if (contentEncoding == Orthanc::HttpCompression_Gzip)
-    {
-      std::string compressed;
-      answer.swap(compressed);
-          
-      Orthanc::GzipCompressor compressor;
-      compressor.Uncompress(answer, compressed.c_str(), compressed.size());
-
-      LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
-                << " to " << answer.size() << " bytes";
-    }
-  }
-
-
-  static void Execute(IMessageEmitter& emitter,
-                      const IObserver& receiver,
-                      const HttpCommand& command)
-  {
-    Orthanc::HttpClient client;
-    client.SetUrl(command.GetUrl());
-    client.SetMethod(command.GetMethod());
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-
-    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
-        command.GetMethod() == Orthanc::HttpMethod_Put)
-    {
-      client.SetBody(command.GetBody());
-    }
-
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
-
-    HttpCommand::SuccessMessage message(command, answerHeaders, answer);
-    emitter.EmitMessage(receiver, message);
-  }
-
-
-  static void Execute(IMessageEmitter& emitter,
-                      const Orthanc::WebServiceParameters& orthanc,
-                      const IObserver& receiver,
-                      const OrthancRestApiCommand& command)
-  {
-    Orthanc::HttpClient client(orthanc, command.GetUri());
-    client.SetMethod(command.GetMethod());
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-
-    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
-        command.GetMethod() == Orthanc::HttpMethod_Put)
-    {
-      client.SetBody(command.GetBody());
-    }
-
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
-
-    OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
-    emitter.EmitMessage(receiver, message);
-  }
-
-
-  static void Execute(IMessageEmitter& emitter,
-                      const Orthanc::WebServiceParameters& orthanc,
-                      const IObserver& receiver,
-                      const GetOrthancImageCommand& command)
-  {
-    Orthanc::HttpClient client(orthanc, command.GetUri());
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-    
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
-
-    command.ProcessHttpAnswer(emitter, receiver, answer, answerHeaders);
-  }
-
-
-  static void Execute(IMessageEmitter& emitter,
-                      const Orthanc::WebServiceParameters& orthanc,
-                      const IObserver& receiver,
-                      const GetOrthancWebViewerJpegCommand& command)
-  {
-    Orthanc::HttpClient client(orthanc, command.GetUri());
-    client.SetTimeout(command.GetTimeout());
-
-    CopyHttpHeaders(client, command.GetHttpHeaders());
-
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
-
-    command.ProcessHttpAnswer(emitter, receiver, answer);
-  }
-
-
   void ThreadedOracle::Step()
   {
     std::auto_ptr<Orthanc::IDynamicObject>  object(queue_.Dequeue(100));
@@ -316,60 +160,37 @@
     {
       Item& item = dynamic_cast<Item&>(*object);
 
-      try
+      if (item.GetCommand().GetType() == IOracleCommand::Type_Sleep)
       {
-        switch (item.GetCommand().GetType())
+        SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand());
+          
+        std::auto_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay()));
+          
+        if (command.HasPayload())
         {
-          case IOracleCommand::Type_Sleep:
-          {
-            SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand());
-
-            std::auto_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay()));
-
-            if (command.HasPayload())
-            {
-              copy->SetPayload(command.ReleasePayload());
-            }
-
-            sleepingCommands_->Add(item.GetReceiver(), copy.release());
-
-            break;
-          }
-
-          case IOracleCommand::Type_Http:
-            Execute(emitter_, item.GetReceiver(), 
-                    dynamic_cast<const HttpCommand&>(item.GetCommand()));
-            break;
+          copy->AcquirePayload(command.ReleasePayload());
+        }
+          
+        sleepingCommands_->Add(item.GetReceiver(), copy.release());
+      }
+      else
+      {
+        GenericOracleRunner runner;
 
-          case IOracleCommand::Type_OrthancRestApi:
-            Execute(emitter_, orthanc_, item.GetReceiver(), 
-                    dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
-            break;
-
-          case IOracleCommand::Type_GetOrthancImage:
-            Execute(emitter_, orthanc_, item.GetReceiver(), 
-                    dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
-            break;
-
-          case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-            Execute(emitter_, orthanc_, item.GetReceiver(), 
-                    dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
-            break;
+        {
+          boost::mutex::scoped_lock lock(mutex_);
+          runner.SetOrthanc(orthanc_);
+          runner.SetRootDirectory(rootDirectory_);
 
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+#if ORTHANC_ENABLE_DCMTK == 1
+          if (dicomCache_)
+          {
+            runner.SetDicomCache(dicomCache_);
+          }
+#endif
         }
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        LOG(ERROR) << "Exception within the oracle: " << e.What();
-        emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
-      }
-      catch (...)
-      {
-        LOG(ERROR) << "Threaded exception within the oracle";
-        emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
-                             (item.GetCommand(), Orthanc::ErrorCode_InternalError));
+
+        runner.Run(item.GetReceiver(), emitter_, item.GetCommand());
       }
     }
   }
@@ -453,6 +274,7 @@
 
   ThreadedOracle::ThreadedOracle(IMessageEmitter& emitter) :
     emitter_(emitter),
+    rootDirectory_("."),
     state_(State_Setup),
     workers_(4),
     sleepingCommands_(new SleepingCommands),
@@ -480,23 +302,21 @@
     catch (...)
     {
       LOG(ERROR) << "Native exception while stopping the threaded oracle";
-    }           
+    }
   }
 
   
   void ThreadedOracle::SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    orthanc_ = orthanc;
+  }
 
-    if (state_ != State_Setup)
-    {
-      LOG(ERROR) << "ThreadedOracle::SetOrthancParameters(): (state_ != State_Setup)";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      orthanc_ = orthanc;
-    }
+
+  void ThreadedOracle::SetRootDirectory(const std::string& rootDirectory)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    rootDirectory_ = rootDirectory;
   }
 
 
@@ -540,6 +360,31 @@
   }
 
 
+  void ThreadedOracle::SetDicomCacheSize(size_t size)
+  {
+#if ORTHANC_ENABLE_DCMTK == 1
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (state_ != State_Setup)
+    {
+      LOG(ERROR) << "ThreadedOracle::SetDicomCacheSize(): (state_ != State_Setup)";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      if (size == 0)
+      {
+        dicomCache_.reset();
+      }
+      else
+      {
+        dicomCache_.reset(new ParsedDicomCache(size));
+      }
+    }
+#endif
+  }
+
+
   void ThreadedOracle::Start()
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -551,6 +396,7 @@
     }
     else
     {
+      LOG(WARNING) << "Starting oracle with " << workers_.size() << " worker threads";
       state_ = State_Running;
 
       for (unsigned int i = 0; i < workers_.size(); i++)
@@ -563,9 +409,25 @@
   }
 
 
-  void ThreadedOracle::Schedule(const IObserver& receiver,
+  bool ThreadedOracle::Schedule(boost::shared_ptr<IObserver> receiver,
                                 IOracleCommand* command)
   {
-    queue_.Enqueue(new Item(receiver, command));
+    std::auto_ptr<Item> item(new Item(receiver, command));
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (state_ == State_Running)
+      {
+        //LOG(INFO) << "New oracle command queued";
+        queue_.Enqueue(item.release());
+        return true;
+      }
+      else
+      {
+        LOG(TRACE) << "Command not enqueued, as the oracle has stopped";
+        return false;
+      }
+    }
   }
 }
--- a/Framework/Oracle/ThreadedOracle.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/ThreadedOracle.h	Fri Nov 29 11:03:41 2019 +0100
@@ -25,14 +25,22 @@
 #  error The macro ORTHANC_ENABLE_THREADS must be defined
 #endif
 
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
 #if ORTHANC_ENABLE_THREADS != 1
 #  error This file can only compiled for native targets
 #endif
 
-#include "../Messages/IMessageEmitter.h"
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "../Toolbox/ParsedDicomCache.h"
+#endif
+
 #include "IOracle.h"
+#include "GenericOracleRunner.h"
+#include "../Messages/IMessageEmitter.h"
 
-#include <Core/WebServiceParameters.h>
 #include <Core/MultiThreading/SharedMessageQueue.h>
 
 
@@ -53,6 +61,7 @@
 
     IMessageEmitter&                     emitter_;
     Orthanc::WebServiceParameters        orthanc_;
+    std::string                          rootDirectory_;
     Orthanc::SharedMessageQueue          queue_;
     State                                state_;
     boost::mutex                         mutex_;
@@ -61,6 +70,10 @@
     boost::thread                        sleepingWorker_;
     unsigned int                         sleepingTimeResolution_;
 
+#if ORTHANC_ENABLE_DCMTK == 1
+    boost::shared_ptr<ParsedDicomCache>  dicomCache_;
+#endif
+    
     void Step();
 
     static void Worker(ThreadedOracle* that);
@@ -74,13 +87,16 @@
 
     virtual ~ThreadedOracle();
 
-    // The reference is not stored.
     void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc);
 
+    void SetRootDirectory(const std::string& rootDirectory);
+
     void SetThreadsCount(unsigned int count);
 
     void SetSleepingTimeResolution(unsigned int milliseconds);
 
+    void SetDicomCacheSize(size_t size);
+
     void Start();
 
     void Stop()
@@ -88,7 +104,7 @@
       StopInternal();
     }
 
-    virtual void Schedule(const IObserver& receiver,
-                          IOracleCommand* command);
+    virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
+                          IOracleCommand* command) ORTHANC_OVERRIDE;
   };
 }
--- a/Framework/Oracle/WebAssemblyOracle.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/WebAssemblyOracle.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -695,7 +695,7 @@
 
 
 
-  void WebAssemblyOracle::Schedule(const IObserver& receiver,
+  void WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver>& receiver,
                                    IOracleCommand* command)
   {
     LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
--- a/Framework/Oracle/WebAssemblyOracle.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Oracle/WebAssemblyOracle.h	Fri Nov 29 11:03:41 2019 +0100
@@ -76,7 +76,7 @@
       orthancRoot_ = root;
     }
     
-    virtual void Schedule(const IObserver& receiver,
-                          IOracleCommand* command);
+    virtual void Schedule(boost::shared_ptr<IObserver>& receiver,
+                          IOracleCommand* command) ORTHANC_OVERRIDE;
   };
 }
--- a/Framework/Radiography/RadiographyAlphaLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyAlphaLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -37,8 +37,8 @@
     float                                  foreground_;  // in the range [0.0, 65535.0]
 
   public:
-    RadiographyAlphaLayer(MessageBroker& broker, const RadiographyScene& scene) :
-      RadiographyLayer(broker, scene),
+    RadiographyAlphaLayer(const RadiographyScene& scene) :
+      RadiographyLayer(scene),
       foreground_(0)
     {
     }
--- a/Framework/Radiography/RadiographyDicomLayer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyDicomLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -48,7 +48,8 @@
   }
 
 
-  RadiographyDicomLayer::RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) : RadiographyLayer(broker, scene)
+  RadiographyDicomLayer::RadiographyDicomLayer(const RadiographyScene& scene) :
+    RadiographyLayer(scene)
   {
 
   }
--- a/Framework/Radiography/RadiographyDicomLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyDicomLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -42,7 +42,7 @@
     void ApplyConverter();
 
   public:
-    RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene);
+    RadiographyDicomLayer(const RadiographyScene& scene);
 
     void SetInstance(const std::string& instanceId, unsigned int frame)
     {
--- a/Framework/Radiography/RadiographyLayer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -119,8 +119,7 @@
   }
 
 
-  RadiographyLayer::RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene) :
-    IObservable(broker),
+  RadiographyLayer::RadiographyLayer(const RadiographyScene& scene) :
     index_(0),
     hasSize_(false),
     width_(0),
--- a/Framework/Radiography/RadiographyLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -248,7 +248,7 @@
                      double zoom);
 
   public:
-    RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene);
+    RadiographyLayer(const RadiographyScene& scene);
 
     virtual ~RadiographyLayer()
     {
--- a/Framework/Radiography/RadiographyMaskLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyMaskLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -40,9 +40,9 @@
 
     mutable std::auto_ptr<Orthanc::ImageAccessor>  mask_;
   public:
-    RadiographyMaskLayer(MessageBroker& broker, const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer,
+    RadiographyMaskLayer(const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer,
                          float foreground) :
-      RadiographyLayer(broker, scene),
+      RadiographyLayer(scene),
       dicomLayer_(dicomLayer),
       invalidated_(true),
       foreground_(foreground)
--- a/Framework/Radiography/RadiographyScene.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyScene.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -134,15 +134,13 @@
 
     std::auto_ptr<RadiographyLayer> raii(layer);
 
-    // LOG(INFO) << "Registering layer: " << countLayers_;
-
     size_t index = nextLayerIndex_++;
     raii->SetIndex(index);
     layers_[index] = raii.release();
 
     BroadcastMessage(GeometryChangedMessage(*this, *layer));
     BroadcastMessage(ContentChangedMessage(*this, *layer));
-    layer->RegisterObserverCallback(new Callable<RadiographyScene, RadiographyLayer::LayerEditedMessage>(*this, &RadiographyScene::OnLayerEdited));
+    Register<RadiographyLayer::LayerEditedMessage>(*layer, &RadiographyScene::OnLayerEdited);
 
     return *layer;
   }
@@ -162,9 +160,8 @@
     BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin()));
   }
 
-  RadiographyScene::RadiographyScene(MessageBroker& broker) :
-    IObserver(broker),
-    IObservable(broker),
+  
+  RadiographyScene::RadiographyScene() :
     nextLayerIndex_(0),
     hasWindowing_(false),
     windowingCenter_(0),  // Dummy initialization
@@ -286,7 +283,7 @@
                                                uint8_t foreground,
                                                RadiographyLayer::Geometry* geometry)
   {
-    std::auto_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(IObservable::GetBroker(), *this));
+    std::auto_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(*this));
     alpha->LoadText(utf8, fontSize, foreground);
     if (geometry != NULL)
     {
@@ -333,7 +330,7 @@
                                                float foreground,
                                                RadiographyLayer::Geometry* geometry)
   {
-    std::auto_ptr<RadiographyMaskLayer>  mask(new RadiographyMaskLayer(IObservable::GetBroker(), *this, dicomLayer, foreground));
+    std::auto_ptr<RadiographyMaskLayer>  mask(new RadiographyMaskLayer(*this, dicomLayer, foreground));
     mask->SetCorners(corners);
     if (geometry != NULL)
     {
@@ -346,7 +343,7 @@
 
   RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry)
   {
-    std::auto_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(IObservable::GetBroker(), *this));
+    std::auto_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(*this));
     alpha->SetAlpha(bitmap);
     if (geometry != NULL)
     {
@@ -363,7 +360,7 @@
                                                      RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
                                                      RadiographyLayer::Geometry* geometry)
   {
-    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)));
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(*this)));
 
     layer.SetInstance(instance, frame);
 
@@ -385,7 +382,7 @@
                                                      bool httpCompression,
                                                      RadiographyLayer::Geometry* geometry)
   {
-    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)));
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer( *this)));
     layer.SetInstance(instance, frame);
 
     if (geometry != NULL)
@@ -400,7 +397,7 @@
       orthanc.GetBinaryAsync(
             uri, headers,
             new Callable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
-            (*this, &RadiographyScene::OnTagsReceived), NULL,
+            (GetSharedObserver(), &RadiographyScene::OnTagsReceived), NULL,
             new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
     }
 
@@ -419,7 +416,7 @@
       orthanc.GetBinaryAsync(
             uri, headers,
             new Callable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage>
-            (*this, &RadiographyScene::OnFrameReceived), NULL,
+            (GetSharedObserver(), &RadiographyScene::OnFrameReceived), NULL,
             new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
     }
 
@@ -429,7 +426,7 @@
 
   RadiographyLayer& RadiographyScene::LoadDicomWebFrame(Deprecated::IWebService& web)
   {
-    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this));
+    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(*this));
 
 
     return layer;
@@ -759,7 +756,7 @@
     orthanc.PostJsonAsyncExpectJson(
           "/tools/create-dicom", createDicomRequestContent,
           new Callable<RadiographyScene, Deprecated::OrthancApiClient::JsonResponseReadyMessage>
-          (*this, &RadiographyScene::OnDicomExported),
+          (GetSharedObserver(), &RadiographyScene::OnDicomExported),
           NULL, NULL);
 
   }
--- a/Framework/Radiography/RadiographyScene.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyScene.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "RadiographyLayer.h"
+#include "../Messages/ObserverBase.h"
 #include "../Deprecated/Toolbox/DicomFrameConverter.h"
 #include "../Deprecated/Toolbox/OrthancApiClient.h"
 #include "../StoneEnumerations.h"
@@ -33,8 +34,8 @@
   class RadiographyDicomLayer;
 
   class RadiographyScene :
-      public IObserver,
-      public IObservable
+    public ObserverBase<RadiographyScene>,
+    public IObservable
   {
   public:
     class GeometryChangedMessage : public OriginMessage<RadiographyScene>
@@ -180,7 +181,7 @@
 
     virtual void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message);
   public:
-    RadiographyScene(MessageBroker& broker);
+    RadiographyScene();
     
     virtual ~RadiographyScene();
 
--- a/Framework/Radiography/RadiographySceneReader.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographySceneReader.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -50,7 +50,7 @@
 
   RadiographyDicomLayer* RadiographySceneReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry)
   {
-    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry)));
+    return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(*orthancApiClient_, instanceId, frame, false, geometry)));
   }
 
   void RadiographySceneBuilder::Read(const Json::Value& input)
@@ -151,7 +151,7 @@
       if (jsonLayer["type"].asString() == "dicom")
       {
         ReadLayerGeometry(geometry, jsonLayer);
-        dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry)));
+        dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(*orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry)));
       }
       else if (jsonLayer["type"].asString() == "mask")
       {
--- a/Framework/Radiography/RadiographySceneReader.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographySceneReader.h	Fri Nov 29 11:03:41 2019 +0100
@@ -68,10 +68,12 @@
 
   class RadiographySceneReader : public RadiographySceneBuilder
   {
-    Deprecated::OrthancApiClient&             orthancApiClient_;
+  private:
+    boost::shared_ptr<Deprecated::OrthancApiClient>  orthancApiClient_;
 
   public:
-    RadiographySceneReader(RadiographyScene& scene, Deprecated::OrthancApiClient& orthancApiClient) :
+    RadiographySceneReader(RadiographyScene& scene,
+                           boost::shared_ptr<Deprecated::OrthancApiClient> orthancApiClient) :
       RadiographySceneBuilder(scene),
       orthancApiClient_(orthancApiClient)
     {
--- a/Framework/Radiography/RadiographyTextLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyTextLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -37,8 +37,8 @@
     static bool                                       fontHasBeenConfigured_;
     static Orthanc::EmbeddedResources::FileResourceId fontResourceId_;
   public:
-    RadiographyTextLayer(MessageBroker& broker, const RadiographyScene& scene) :
-      RadiographyAlphaLayer(broker, scene)
+    RadiographyTextLayer(const RadiographyScene& scene) :
+      RadiographyAlphaLayer(scene)
     {
     }
 
--- a/Framework/Radiography/RadiographyWidget.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyWidget.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -164,11 +164,9 @@
   }
 
 
-  RadiographyWidget::RadiographyWidget(MessageBroker& broker,
-                                       boost::shared_ptr<RadiographyScene> scene,
+  RadiographyWidget::RadiographyWidget(boost::shared_ptr<RadiographyScene> scene,
                                        const std::string& name) :
     WorldSceneWidget(name),
-    IObserver(broker),
     invert_(false),
     interpolation_(ImageInterpolation_Nearest),
     hasSelection_(false),
@@ -249,24 +247,11 @@
 
   void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene)
   {
-    if (scene_ != NULL)
-    {
-      scene_->Unregister(this);
-    }
-
     scene_ = scene;
 
-    scene_->RegisterObserverCallback(
-          new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage>
-          (*this, &RadiographyWidget::OnGeometryChanged));
-
-    scene_->RegisterObserverCallback(
-          new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage>
-          (*this, &RadiographyWidget::OnContentChanged));
-
-    scene_->RegisterObserverCallback(
-          new Callable<RadiographyWidget, RadiographyScene::LayerRemovedMessage>
-          (*this, &RadiographyWidget::OnLayerRemoved));
+    Register<RadiographyScene::GeometryChangedMessage>(*scene_, &RadiographyWidget::OnGeometryChanged);
+    Register<RadiographyScene::ContentChangedMessage>(*scene_, &RadiographyWidget::OnContentChanged);
+    Register<RadiographyScene::LayerRemovedMessage>(*scene_, &RadiographyWidget::OnLayerRemoved);
 
     NotifyContentChanged();
 
--- a/Framework/Radiography/RadiographyWidget.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Radiography/RadiographyWidget.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "../Deprecated/Widgets/WorldSceneWidget.h"
+#include "../Messages/ObserverBase.h"
 #include "RadiographyScene.h"
 
 
@@ -31,7 +32,7 @@
 
   class RadiographyWidget :
     public Deprecated::WorldSceneWidget,
-    public IObserver
+    public ObserverBase<RadiographyWidget>
   {
   private:
     boost::shared_ptr<RadiographyScene>    scene_;
@@ -60,8 +61,7 @@
     bool IsInvertedInternal() const;
 
   public:
-    RadiographyWidget(MessageBroker& broker,
-                      boost::shared_ptr<RadiographyScene> scene,  // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now)
+    RadiographyWidget(boost::shared_ptr<RadiographyScene> scene,  // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now)
                       const std::string& name);
 
     RadiographyScene& GetScene() const
--- a/Framework/Scene2D/FloatTextureSceneLayer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -92,6 +92,14 @@
     IncrementRevision();
   }
 
+
+  void FloatTextureSceneLayer::SetApplyLog(bool apply)
+  {
+    applyLog_ = apply;
+    IncrementRevision();
+  }
+
+
   void FloatTextureSceneLayer::FitRange()
   {
     float minValue, maxValue;
@@ -123,6 +131,7 @@
     cloned->customCenter_ = customCenter_;
     cloned->customWidth_ = customWidth_;
     cloned->inverted_ = inverted_;
+    cloned->applyLog_ = applyLog_;
 
     return cloned.release();
   }
--- a/Framework/Scene2D/FloatTextureSceneLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/FloatTextureSceneLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -32,6 +32,7 @@
     float            customCenter_;
     float            customWidth_;
     bool             inverted_;
+    bool             applyLog_;
 
   public:
     // The pixel format must be convertible to "Float32"
@@ -60,6 +61,13 @@
 
     void FitRange();
 
+    void SetApplyLog(bool apply);
+
+    bool IsApplyLog() const
+    {
+      return applyLog_;
+    }
+
     virtual ISceneLayer* Clone() const;
 
     virtual Type GetType() const
--- a/Framework/Scene2D/GrayscaleStyleConfigurator.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -27,6 +27,17 @@
 
 namespace OrthancStone
 {
+  GrayscaleStyleConfigurator::GrayscaleStyleConfigurator() :
+    revision_(0),
+    linearInterpolation_(false),
+    hasWindowing_(false),
+    customWindowWidth_(0),
+    customWindowCenter_(0),
+    inverted_(false),
+    applyLog_(false)
+  {
+  }
+
   void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing)
   {
     hasWindowing_ = true;
@@ -59,6 +70,12 @@
     revision_++;
   }
 
+  void GrayscaleStyleConfigurator::SetApplyLog(bool apply)
+  {
+    applyLog_ = apply;
+    revision_++;
+  }
+
   TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromImage(
     const Orthanc::ImageAccessor& image) const
   {
@@ -99,6 +116,8 @@
         l.SetCustomWindowing(customWindowCenter_, customWindowWidth_);
       }
     }
+
     l.SetInverted(inverted_);
+    l.SetApplyLog(applyLog_);
   }
 }
--- a/Framework/Scene2D/GrayscaleStyleConfigurator.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h	Fri Nov 29 11:03:41 2019 +0100
@@ -39,17 +39,10 @@
     float           customWindowWidth_;
     float           customWindowCenter_;
     bool            inverted_;
+    bool            applyLog_;
     
   public:
-    GrayscaleStyleConfigurator() :
-      revision_(0),
-      linearInterpolation_(false),
-      hasWindowing_(false),
-      customWindowWidth_(0),
-      customWindowCenter_(0),
-      inverted_(false)
-    {
-    }
+    GrayscaleStyleConfigurator();
 
     void SetWindowing(ImageWindowing windowing);
 
@@ -66,6 +59,13 @@
       return linearInterpolation_;
     }
 
+    void SetApplyLog(bool apply);
+
+    bool IsApplyLog() const
+    {
+      return applyLog_;
+    }
+
     virtual uint64_t GetRevision() const
     {
       return revision_;
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -53,6 +53,8 @@
              target.GetFormat() == Orthanc::PixelFormat_BGRA32 &&
              sizeof(float) == 4);
 
+      static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f);
+      
       for (unsigned int y = 0; y < height; y++)
       {
         const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
@@ -70,6 +72,14 @@
             v = 255;
           }
 
+          if (l.IsApplyLog())
+          {
+            // https://theailearner.com/2019/01/01/log-transformation/
+            v = LOG_NORMALIZATION * log(1.0f + static_cast<float>(v));
+          }
+
+          assert(v >= 0.0f && v <= 255.0f);
+
           uint8_t vv = static_cast<uint8_t>(v);
 
           if (l.IsInverted())
--- a/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -37,18 +37,6 @@
       textureTransform_ = l.GetTransform();
       isLinearInterpolation_ = l.IsLinearInterpolation();
 
-      const float a = l.GetMinValue();
-      float slope;
-
-      if (l.GetMinValue() >= l.GetMaxValue())
-      {
-        slope = 0;
-      }
-      else
-      {
-        slope = 256.0f / (l.GetMaxValue() - l.GetMinValue());
-      }
-
       const Orthanc::ImageAccessor& source = l.GetTexture();
       const unsigned int width = source.GetWidth();
       const unsigned int height = source.GetHeight();
@@ -56,46 +44,8 @@
 
       Orthanc::ImageAccessor target;
       texture_.GetWriteableAccessor(target);
-
-      const std::vector<uint8_t>& lut = l.GetLookupTable();
-      if (lut.size() != 4 * 256)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      assert(source.GetFormat() == Orthanc::PixelFormat_Float32 &&
-             target.GetFormat() == Orthanc::PixelFormat_BGRA32 &&
-             sizeof(float) == 4);
-
-      for (unsigned int y = 0; y < height; y++)
-      {
-        const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-
-        for (unsigned int x = 0; x < width; x++)
-        {
-          float v = (*p - a) * slope;
-          if (v <= 0)
-          {
-            v = 0;
-          }
-          else if (v >= 255)
-          {
-            v = 255;
-          }
-
-          uint8_t vv = static_cast<uint8_t>(v);
-
-          q[0] = lut[4 * vv + 2];  // B
-          q[1] = lut[4 * vv + 1];  // G
-          q[2] = lut[4 * vv + 0];  // R
-          q[3] = lut[4 * vv + 3];  // A
-
-          p++;
-          q += 4;
-        }
-      }
-
+      l.Render(target);
+      
       cairo_surface_mark_dirty(texture_.GetObject());
     }
 
--- a/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -21,6 +21,8 @@
 
 #include "OpenGLFloatTextureRenderer.h"
 
+#include <Core/OrthancException.h>
+
 namespace OrthancStone
 {
   namespace Internals
@@ -32,6 +34,11 @@
       {
         if (loadTexture)
         {
+          if (layer.IsApplyLog())
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+          
           context_.MakeCurrent();
           texture_.reset(new OpenGLFloatTextureProgram::Data(
             context_, layer.GetTexture(), layer.IsLinearInterpolation()));
--- a/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -36,74 +36,14 @@
         const unsigned int width = source.GetWidth();
         const unsigned int height = source.GetHeight();
 
-        if ((texture_.get() == NULL) ||
-          (texture_->GetWidth() != width) ||
-          (texture_->GetHeight() != height))
+        if (texture_.get() == NULL ||
+            texture_->GetWidth() != width ||
+            texture_->GetHeight() != height)
         {
-
-          texture_.reset(new Orthanc::Image(
-            Orthanc::PixelFormat_RGBA32,
-            width,
-            height,
-            false));
+          texture_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, false));
         }
 
-        {
-
-          const float a = layer.GetMinValue();
-          float slope = 0;
-
-          if (layer.GetMinValue() >= layer.GetMaxValue())
-          {
-            slope = 0;
-          }
-          else
-          {
-            slope = 256.0f / (layer.GetMaxValue() - layer.GetMinValue());
-          }
-
-          Orthanc::ImageAccessor target;
-          texture_->GetWriteableAccessor(target);
-
-          const std::vector<uint8_t>& lut = layer.GetLookupTable();
-          if (lut.size() != 4 * 256)
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-
-          assert(source.GetFormat() == Orthanc::PixelFormat_Float32 &&
-            target.GetFormat() == Orthanc::PixelFormat_RGBA32 &&
-            sizeof(float) == 4);
-
-          for (unsigned int y = 0; y < height; y++)
-          {
-            const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
-            uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-
-            for (unsigned int x = 0; x < width; x++)
-            {
-              float v = (*p - a) * slope;
-              if (v <= 0)
-              {
-                v = 0;
-              }
-              else if (v >= 255)
-              {
-                v = 255;
-              }
-
-              uint8_t vv = static_cast<uint8_t>(v);
-
-              q[0] = lut[4 * vv + 0];  // R
-              q[1] = lut[4 * vv + 1];  // G
-              q[2] = lut[4 * vv + 2];  // B
-              q[3] = lut[4 * vv + 3];  // A
-
-              p++;
-              q += 4;
-            }
-          }
-        }
+        layer.Render(*texture_);
 
         context_.MakeCurrent();
         glTexture_.reset(new OpenGL::OpenGLTexture(context_));
--- a/Framework/Scene2D/LookupTableStyleConfigurator.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -39,7 +39,8 @@
   LookupTableStyleConfigurator::LookupTableStyleConfigurator() :
     revision_(0),
     hasLut_(false),
-    hasRange_(false)
+    hasRange_(false),
+    applyLog_(false)
   {
   }
 
@@ -82,6 +83,12 @@
     }
   }
 
+  void LookupTableStyleConfigurator::SetApplyLog(bool apply)
+  {
+    applyLog_ = apply;
+    revision_++;
+  }
+
   TextureBaseSceneLayer* LookupTableStyleConfigurator::CreateTextureFromImage(const Orthanc::ImageAccessor& image) const
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
@@ -104,5 +111,7 @@
     {
       l.FitRange();
     }
+
+    l.SetApplyLog(applyLog_);
   }
 }
--- a/Framework/Scene2D/LookupTableStyleConfigurator.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/LookupTableStyleConfigurator.h	Fri Nov 29 11:03:41 2019 +0100
@@ -39,6 +39,7 @@
     bool                  hasRange_;
     float                 minValue_;
     float                 maxValue_;
+    bool                  applyLog_;
 
   public:
     LookupTableStyleConfigurator();
@@ -55,6 +56,13 @@
 
     void SetRange(float minValue, float maxValue);
 
+    void SetApplyLog(bool apply);
+
+    bool IsApplyLog() const
+    {
+      return applyLog_;
+    }
+    
     virtual uint64_t GetRevision() const
     {
       return revision_;
@@ -63,7 +71,7 @@
     virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const;
 
     virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
-      const DicomInstanceParameters& parameters) const
+                                                          const DicomInstanceParameters& parameters) const
     {
       return parameters.CreateLookupTableTexture(frame);
     }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -132,6 +132,12 @@
     }
   }
 
+  void LookupTableTextureSceneLayer::SetApplyLog(bool apply)
+  {
+    applyLog_ = apply;
+    IncrementRevision();
+  }
+
   void LookupTableTextureSceneLayer::FitRange()
   {
     Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture());
@@ -158,4 +164,147 @@
 
     return cloned.release();
   }
+
+
+  // Templatized function to speed up computations, by avoiding
+  // testing conditions on each pixel
+  template <bool IsApplyLog,
+            Orthanc::PixelFormat TargetFormat>
+  static void RenderInternal(Orthanc::ImageAccessor& target,
+                             const Orthanc::ImageAccessor& source,
+                             float minValue,
+                             float slope,
+                             const std::vector<uint8_t>& lut)
+  {
+    static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f);
+
+    const unsigned int width = source.GetWidth();
+    const unsigned int height = source.GetHeight();
+    
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
+      uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++)
+      {
+        float v = (*p - minValue) * slope;
+        if (v <= 0)
+        {
+          v = 0;
+        }
+        else if (v >= 255)
+        {
+          v = 255;
+        }
+
+        if (IsApplyLog)
+        {
+          // https://theailearner.com/2019/01/01/log-transformation/
+          v = LOG_NORMALIZATION * log(1.0f + static_cast<float>(v));
+        }
+
+        assert(v >= 0.0f && v <= 255.0f);
+
+        uint8_t vv = static_cast<uint8_t>(v);
+
+        switch (TargetFormat)
+        {
+          case Orthanc::PixelFormat_BGRA32:
+            // For Cairo surfaces
+            q[0] = lut[4 * vv + 2];  // B
+            q[1] = lut[4 * vv + 1];  // G
+            q[2] = lut[4 * vv + 0];  // R
+            q[3] = lut[4 * vv + 3];  // A
+            break;
+
+          case Orthanc::PixelFormat_RGBA32:
+            // For OpenGL
+            q[0] = lut[4 * vv + 0];  // R
+            q[1] = lut[4 * vv + 1];  // G
+            q[2] = lut[4 * vv + 2];  // B
+            q[3] = lut[4 * vv + 3];  // A
+            break;
+
+          default:
+            assert(0);
+        }
+            
+        p++;
+        q += 4;
+      }
+    }
+  }
+  
+
+  void LookupTableTextureSceneLayer::Render(Orthanc::ImageAccessor& target) const
+  {
+    assert(sizeof(float) == 4);
+
+    if (!HasTexture())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    const Orthanc::ImageAccessor& source = GetTexture();
+    
+    if (source.GetFormat() != Orthanc::PixelFormat_Float32 ||
+        (target.GetFormat() != Orthanc::PixelFormat_RGBA32 &&
+         target.GetFormat() != Orthanc::PixelFormat_BGRA32))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (source.GetWidth() != target.GetWidth() ||
+        source.GetHeight() != target.GetHeight())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+
+    const float minValue = GetMinValue();
+    float slope;
+
+    if (GetMinValue() >= GetMaxValue())
+    {
+      slope = 0;
+    }
+    else
+    {
+      slope = 256.0f / (GetMaxValue() - GetMinValue());
+    }
+
+    const std::vector<uint8_t>& lut = GetLookupTable();
+    if (lut.size() != 4 * 256)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    switch (target.GetFormat())
+    {
+      case Orthanc::PixelFormat_RGBA32:
+        if (applyLog_)
+        {
+          RenderInternal<true, Orthanc::PixelFormat_RGBA32>(target, source, minValue, slope, lut);
+        }
+        else
+        {
+          RenderInternal<false, Orthanc::PixelFormat_RGBA32>(target, source, minValue, slope, lut);
+        }
+        break;
+
+      case Orthanc::PixelFormat_BGRA32:
+        if (applyLog_)
+        {
+          RenderInternal<true, Orthanc::PixelFormat_BGRA32>(target, source, minValue, slope, lut);
+        }
+        else
+        {
+          RenderInternal<false, Orthanc::PixelFormat_BGRA32>(target, source, minValue, slope, lut);
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
 }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h	Fri Nov 29 11:03:41 2019 +0100
@@ -32,6 +32,7 @@
     float                 minValue_;
     float                 maxValue_;
     std::vector<uint8_t>  lut_;
+    bool                  applyLog_;
 
     void SetLookupTableRgb(const std::vector<uint8_t>& lut);
 
@@ -66,11 +67,22 @@
       return lut_;
     }
 
+    void SetApplyLog(bool apply);
+
+    bool IsApplyLog() const
+    {
+      return applyLog_;
+    }
+
     virtual ISceneLayer* Clone() const;
 
     virtual Type GetType() const
     {
       return Type_LookupTableTexture;
     }
+
+    // Render the texture to a color image of format BGRA32 (Cairo
+    // surfaces) or RGBA32 (OpenGL)
+    void Render(Orthanc::ImageAccessor& target) const;
   };
 }
--- a/Framework/Scene2D/ScenePoint2D.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2D/ScenePoint2D.h	Fri Nov 29 11:03:41 2019 +0100
@@ -64,115 +64,115 @@
       return ScenePoint2D(x, y);
     }
 
-    const ScenePoint2D operator-(const ScenePoint2D& a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ - a.x_;
-      v.y_ = y_ - a.y_;
-
-      return v;
-    }
-
-    const ScenePoint2D operator+(const ScenePoint2D& a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ + a.x_;
-      v.y_ = y_ + a.y_;
-
-      return v;
-    }
-
-    const ScenePoint2D operator*(double a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ * a;
-      v.y_ = y_ * a;
-
-      return v;
-    }
-
-    const ScenePoint2D operator/(double a) const
-    {
-      ScenePoint2D v;
-      v.x_ = x_ / a;
-      v.y_ = y_ / a;
-
-      return v;
-    }
-
-    static void MidPoint(ScenePoint2D& result, const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      result.x_ = 0.5 * (a.x_ + b.x_);
-      result.y_ = 0.5 * (a.y_ + b.y_);
-    }
-
-    static double Dot(const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      return a.x_ * b.x_ + a.y_ * b.y_;
-    }
+    const ScenePoint2D operator-(const ScenePoint2D& a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ - a.x_;
+      v.y_ = y_ - a.y_;
+
+      return v;
+    }
+
+    const ScenePoint2D operator+(const ScenePoint2D& a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ + a.x_;
+      v.y_ = y_ + a.y_;
+
+      return v;
+    }
+
+    const ScenePoint2D operator*(double a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ * a;
+      v.y_ = y_ * a;
 
-    static double SquaredMagnitude(const ScenePoint2D& v)
-    {
-      return v.x_ * v.x_ + v.y_ * v.y_;
-    }
+      return v;
+    }
+
+    const ScenePoint2D operator/(double a) const
+    {
+      ScenePoint2D v;
+      v.x_ = x_ / a;
+      v.y_ = y_ / a;
+
+      return v;
+    }
+
+    static void MidPoint(ScenePoint2D& result, const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      result.x_ = 0.5 * (a.x_ + b.x_);
+      result.y_ = 0.5 * (a.y_ + b.y_);
+    }
+
+    static double Dot(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      return a.x_ * b.x_ + a.y_ * b.y_;
+    }
+
+    static double SquaredMagnitude(const ScenePoint2D& v)
+    {
+      return v.x_ * v.x_ + v.y_ * v.y_;
+    }
 
-    static double Magnitude(const ScenePoint2D& v)
-    {
-      double squaredMagnitude = SquaredMagnitude(v);
-      if (LinearAlgebra::IsCloseToZero(squaredMagnitude))
-        return 0.0;
-      return sqrt(squaredMagnitude);
-    }
+    static double Magnitude(const ScenePoint2D& v)
+    {
+      double squaredMagnitude = SquaredMagnitude(v);
+      if (LinearAlgebra::IsCloseToZero(squaredMagnitude))
+        return 0.0;
+      return sqrt(squaredMagnitude);
+    }
 
-    static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      ScenePoint2D n = b - a;
-      return Dot(n, n);
-    }
+    static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      ScenePoint2D n = b - a;
+      return Dot(n, n);
+    }
 
-    static double DistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
-    {
-      double squaredDist = SquaredDistancePtPt(a, b);
-      return sqrt(squaredDist);
-    }
+    static double DistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b)
+    {
+      double squaredDist = SquaredDistancePtPt(a, b);
+      return sqrt(squaredDist);
+    }
+
+    /**
+    Distance from point p to [a,b] segment
 
-    /**
-    Distance from point p to [a,b] segment
-
-    Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/
-    */
-    static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p)
-    {
-      ScenePoint2D n = b - a;
-      ScenePoint2D pa = a - p;
-
-      double c = Dot(n, pa);
-
-      // Closest point is a
-      if (c > 0.0)
-        return Dot(pa, pa);
-
-      ScenePoint2D bp = p - b;
-
-      // Closest point is b
-      if (Dot(n, bp) > 0.0)
-        return Dot(bp, bp);
-
-      // if segment length is very short, we approximate distance to the
-      // distance with a
-      double nq = Dot(n, n);
-      if (LinearAlgebra::IsCloseToZero(nq))
-      {
-        // segment is very small: approximate distance from point to segment
-        // with distance from p to a
-        return Dot(pa, pa);
-      }
-      else
-      {
-        // Closest point is between a and b
-        ScenePoint2D e = pa - n * (c / nq);
-        return Dot(e, e);
-      }
+    Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/
+    */
+    static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p)
+    {
+      ScenePoint2D n = b - a;
+      ScenePoint2D pa = a - p;
+
+      double c = Dot(n, pa);
+
+      // Closest point is a
+      if (c > 0.0)
+        return Dot(pa, pa);
+
+      ScenePoint2D bp = p - b;
+
+      // Closest point is b
+      if (Dot(n, bp) > 0.0)
+        return Dot(bp, bp);
+
+      // if segment length is very short, we approximate distance to the
+      // distance with a
+      double nq = Dot(n, n);
+      if (LinearAlgebra::IsCloseToZero(nq))
+      {
+        // segment is very small: approximate distance from point to segment
+        // with distance from p to a
+        return Dot(pa, pa);
+      }
+      else
+      {
+        // Closest point is between a and b
+        ScenePoint2D e = pa - n * (c / nq);
+        return Dot(e, e);
+      }
     }
   };
 }
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -42,8 +42,8 @@
   // the params in the LayerHolder ctor specify the number of polyline and text
   // layers
   AngleMeasureTool::AngleMeasureTool(
-    MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
-    : MeasureTool(broker, controllerW)
+    boost::weak_ptr<ViewportController> controllerW)
+    : MeasureTool(controllerW)
 #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1
     , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5))
 #else
@@ -189,8 +189,9 @@
         boost::weak_ptr<ViewportController>          controllerW,
         const PointerEvent & e);
     */
+
     boost::shared_ptr<EditAngleMeasureTracker> editAngleMeasureTracker(
-      new EditAngleMeasureTracker(shared_from_this(), GetBroker(), GetController(), e));
+      new EditAngleMeasureTracker(shared_from_this(), GetController(), e));
     return editAngleMeasureTracker;
   }
 
--- a/Framework/Scene2DViewport/AngleMeasureTool.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Fri Nov 29 11:03:41 2019 +0100
@@ -37,10 +37,10 @@
 
 namespace OrthancStone
 {
-  class AngleMeasureTool : public MeasureTool, public boost::enable_shared_from_this<AngleMeasureTool>
+  class AngleMeasureTool : public MeasureTool
   {
   public:
-    AngleMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
+    AngleMeasureTool(boost::weak_ptr<ViewportController> controllerW);
 
     ~AngleMeasureTool();
 
--- a/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,12 +26,11 @@
 namespace OrthancStone
 {
   CreateAngleMeasureCommand::CreateAngleMeasureCommand(
-    MessageBroker& broker,
     boost::weak_ptr<ViewportController> controllerW,
     ScenePoint2D           point)
     : CreateMeasureCommand(controllerW)
     , measureTool_(
-      boost::make_shared<AngleMeasureTool>(boost::ref(broker), controllerW))
+      boost::make_shared<AngleMeasureTool>(controllerW))
   {
     GetController()->AddMeasureTool(measureTool_);
     measureTool_->SetSide1End(point);
--- a/Framework/Scene2DViewport/CreateAngleMeasureCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateAngleMeasureCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,7 +28,6 @@
   public:
     /** Ctor sets end of side 1*/
     CreateAngleMeasureCommand(
-      MessageBroker& broker,
       boost::weak_ptr<ViewportController> controllerW,
       ScenePoint2D           point);
 
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,7 +26,6 @@
 namespace OrthancStone
 {
   CreateAngleMeasureTracker::CreateAngleMeasureTracker(
-    MessageBroker&                  broker,
     boost::weak_ptr<ViewportController>          controllerW,
     const PointerEvent&             e)
     : CreateMeasureTracker(controllerW)
@@ -34,7 +33,6 @@
   {
     command_.reset(
       new CreateAngleMeasureCommand(
-        broker,
         controllerW,
         e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform())));
   }
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Fri Nov 29 11:03:41 2019 +0100
@@ -38,7 +38,6 @@
     must be supplied, too
     */
     CreateAngleMeasureTracker(
-      MessageBroker&                  broker,
       boost::weak_ptr<ViewportController>          controllerW,
       const PointerEvent&             e);
 
--- a/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,12 +26,11 @@
 namespace OrthancStone
 {
   CreateLineMeasureCommand::CreateLineMeasureCommand(
-    MessageBroker& broker,
     boost::weak_ptr<ViewportController> controllerW,
     ScenePoint2D           point)
     : CreateMeasureCommand(controllerW)
     , measureTool_(
-      boost::make_shared<LineMeasureTool>(boost::ref(broker), controllerW))
+      boost::make_shared<LineMeasureTool>(controllerW))
   {
     GetController()->AddMeasureTool(measureTool_);
     measureTool_->Set(point, point);
--- a/Framework/Scene2DViewport/CreateLineMeasureCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateLineMeasureCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -27,7 +27,6 @@
   {
   public:
     CreateLineMeasureCommand(
-      MessageBroker& broker,
       boost::weak_ptr<ViewportController> controllerW,
       ScenePoint2D           point);
 
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,14 +26,12 @@
 namespace OrthancStone
 {
   CreateLineMeasureTracker::CreateLineMeasureTracker(
-    MessageBroker&                  broker,
     boost::weak_ptr<ViewportController>          controllerW,
     const PointerEvent&             e)
     : CreateMeasureTracker(controllerW)
   {
     command_.reset(
       new CreateLineMeasureCommand(
-        broker,
         controllerW,
         e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform())));
   }
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Fri Nov 29 11:03:41 2019 +0100
@@ -38,7 +38,6 @@
     must be supplied, too
     */
     CreateLineMeasureTracker(
-      MessageBroker&                  broker,
       boost::weak_ptr<ViewportController>          controllerW,
       const PointerEvent&             e);
 
--- a/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -23,8 +23,7 @@
 namespace OrthancStone
 {
   EditAngleMeasureCommand::EditAngleMeasureCommand(
-    boost::shared_ptr<AngleMeasureTool>  measureTool,
-    MessageBroker& broker,
+    boost::shared_ptr<MeasureTool>  measureTool,
     boost::weak_ptr<ViewportController> controllerW)
     : EditMeasureCommand(measureTool, controllerW)
     , measureTool_(measureTool)
@@ -33,21 +32,21 @@
 
   void EditAngleMeasureCommand::SetCenter(ScenePoint2D scenePos)
   {
-    measureTool_->SetCenter(scenePos);
+    dynamic_cast<AngleMeasureTool&>(*measureTool_).SetCenter(scenePos);
     mementoModified_ = measureTool_->GetMemento();
   }
 
 
   void EditAngleMeasureCommand::SetSide1End(ScenePoint2D scenePos)
   {
-    measureTool_->SetSide1End(scenePos);
+    dynamic_cast<AngleMeasureTool&>(*measureTool_).SetSide1End(scenePos);
     mementoModified_ = measureTool_->GetMemento();
   }
 
 
   void EditAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos)
   {
-    measureTool_->SetSide2End(scenePos);
+    dynamic_cast<AngleMeasureTool&>(*measureTool_).SetSide2End(scenePos);
     mementoModified_ = measureTool_->GetMemento();
   }
 }
--- a/Framework/Scene2DViewport/EditAngleMeasureCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditAngleMeasureCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,8 +28,7 @@
   public:
     /** Ctor sets end of side 1*/
     EditAngleMeasureCommand(
-      boost::shared_ptr<AngleMeasureTool>  measureTool,
-      MessageBroker& broker,
+      boost::shared_ptr<MeasureTool>  measureTool,
       boost::weak_ptr<ViewportController> controllerW);
 
     /** This method sets center*/
@@ -46,6 +45,6 @@
     {
       return measureTool_;
     }
-    boost::shared_ptr<AngleMeasureTool> measureTool_;
+    boost::shared_ptr<MeasureTool> measureTool_;
   };
 }
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,8 +26,7 @@
 namespace OrthancStone
 {
   EditAngleMeasureTracker::EditAngleMeasureTracker(
-    boost::shared_ptr<AngleMeasureTool>  measureTool,
-    MessageBroker& broker,
+    boost::shared_ptr<MeasureTool>  measureTool,
     boost::weak_ptr<ViewportController> controllerW,
     const PointerEvent& e)
     : EditMeasureTracker(controllerW, e)
@@ -35,9 +34,9 @@
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
       GetScene().GetCanvasToSceneTransform());
 
-    modifiedZone_ = measureTool->AngleHitTest(scenePos);
+    modifiedZone_ = dynamic_cast<AngleMeasureTool&>(*measureTool).AngleHitTest(scenePos);
 
-    command_.reset(new EditAngleMeasureCommand(measureTool, broker, controllerW));
+    command_.reset(new EditAngleMeasureCommand(measureTool, controllerW));
   }
 
   EditAngleMeasureTracker::~EditAngleMeasureTracker()
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.h	Fri Nov 29 11:03:41 2019 +0100
@@ -37,8 +37,7 @@
     must be supplied, too
     */
     EditAngleMeasureTracker(
-      boost::shared_ptr<AngleMeasureTool>  measureTool,
-      MessageBroker& broker,
+      boost::shared_ptr<MeasureTool>  measureTool,
       boost::weak_ptr<ViewportController> controllerW,
       const PointerEvent& e);
 
--- a/Framework/Scene2DViewport/EditLineMeasureCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditLineMeasureCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -23,8 +23,7 @@
 namespace OrthancStone
 {
   EditLineMeasureCommand::EditLineMeasureCommand(
-    boost::shared_ptr<LineMeasureTool>  measureTool,
-    MessageBroker& broker,
+    boost::shared_ptr<MeasureTool>  measureTool,
     boost::weak_ptr<ViewportController> controllerW)
     : EditMeasureCommand(measureTool, controllerW)
     , measureTool_(measureTool)
@@ -34,14 +33,14 @@
 
   void EditLineMeasureCommand::SetStart(ScenePoint2D scenePos)
   {
-    measureTool_->SetStart(scenePos);
+    dynamic_cast<LineMeasureTool&>(*measureTool_).SetStart(scenePos);
     mementoModified_ = measureTool_->GetMemento();
   }
 
 
   void EditLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
   {
-    measureTool_->SetEnd(scenePos);
+    dynamic_cast<LineMeasureTool&>(*measureTool_).SetEnd(scenePos);
     mementoModified_ = measureTool_->GetMemento();
   }
 }
--- a/Framework/Scene2DViewport/EditLineMeasureCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditLineMeasureCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -27,8 +27,7 @@
   {
   public:
     EditLineMeasureCommand(
-      boost::shared_ptr<LineMeasureTool>  measureTool,
-      MessageBroker& broker,
+      boost::shared_ptr<MeasureTool>  measureTool,
       boost::weak_ptr<ViewportController> controllerW);
 
     void SetStart(ScenePoint2D scenePos);
@@ -39,7 +38,6 @@
     {
       return measureTool_;
     }
-    boost::shared_ptr<LineMeasureTool> measureTool_;
+    boost::shared_ptr<MeasureTool> measureTool_;
   };
 }
-
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -27,8 +27,7 @@
 namespace OrthancStone
 {
   EditLineMeasureTracker::EditLineMeasureTracker(
-    boost::shared_ptr<LineMeasureTool>  measureTool,
-    MessageBroker& broker,
+    boost::shared_ptr<MeasureTool>  measureTool,
     boost::weak_ptr<ViewportController> controllerW,
     const PointerEvent& e) 
     : EditMeasureTracker(controllerW, e)
@@ -36,13 +35,9 @@
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
       GetScene().GetCanvasToSceneTransform());
 
-    modifiedZone_ = measureTool->LineHitTest(scenePos);
+    modifiedZone_ = dynamic_cast<LineMeasureTool&>(*measureTool).LineHitTest(scenePos);
 
-    command_.reset(
-      new EditLineMeasureCommand(
-        measureTool,
-        broker,
-        controllerW));
+    command_.reset(new EditLineMeasureCommand(measureTool, controllerW));
   }
 
   EditLineMeasureTracker::~EditLineMeasureTracker()
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.h	Fri Nov 29 11:03:41 2019 +0100
@@ -37,8 +37,7 @@
     must be supplied, too
     */
     EditLineMeasureTracker(
-      boost::shared_ptr<LineMeasureTool>  measureTool,
-      MessageBroker&                      broker,
+      boost::shared_ptr<MeasureTool>  measureTool,
       boost::weak_ptr<ViewportController> controllerW,
       const PointerEvent&                 e);
 
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -32,8 +32,8 @@
 {
 
   LineMeasureTool::LineMeasureTool(
-    MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
-    : MeasureTool(broker, controllerW)
+    boost::weak_ptr<ViewportController> controllerW)
+    : MeasureTool(controllerW)
 #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1
     , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5))
 #else
@@ -148,7 +148,7 @@
         const PointerEvent & e);
     */
     boost::shared_ptr<EditLineMeasureTracker> editLineMeasureTracker(
-      new EditLineMeasureTracker(shared_from_this(), GetBroker(), GetController(), e));
+      new EditLineMeasureTracker(shared_from_this(), GetController(), e));
     return editLineMeasureTracker;
   }
 
--- a/Framework/Scene2DViewport/LineMeasureTool.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Fri Nov 29 11:03:41 2019 +0100
@@ -35,10 +35,10 @@
 
 namespace OrthancStone
 {
-  class LineMeasureTool : public MeasureTool, public boost::enable_shared_from_this<LineMeasureTool>
+  class LineMeasureTool : public MeasureTool
   {
   public:
-    LineMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
+    LineMeasureTool(boost::weak_ptr<ViewportController> controllerW);
 
     ~LineMeasureTool();
 
--- a/Framework/Scene2DViewport/MeasureTool.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/MeasureTool.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -28,14 +28,6 @@
 
 namespace OrthancStone
 {
-  MeasureTool::~MeasureTool()
-  {
-    // if the controller is dead, let's not bother.
-    boost::shared_ptr<ViewportController> controller = controllerW_.lock();
-    if (controller)
-      controller->Unregister(this);
-  }
-
   void MeasureTool::Enable()
   {
     enabled_ = true;
@@ -79,15 +71,13 @@
 #endif
   }
 
-  MeasureTool::MeasureTool(MessageBroker& broker,
+  MeasureTool::MeasureTool(
     boost::weak_ptr<ViewportController> controllerW)
-    : IObserver(broker)
-    , controllerW_(controllerW)
+    : controllerW_(controllerW)
     , enabled_(true)
   {
-    GetController()->RegisterObserverCallback(
-      new Callable<MeasureTool, ViewportController::SceneTransformChanged>
-      (*this, &MeasureTool::OnSceneTransformChanged));
+    // TODO => Move this out of constructor
+    Register<ViewportController::SceneTransformChanged>(*GetController(), &MeasureTool::OnSceneTransformChanged);
   }
 
 
--- a/Framework/Scene2DViewport/MeasureTool.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/MeasureTool.h	Fri Nov 29 11:03:41 2019 +0100
@@ -20,6 +20,7 @@
 
 #pragma once
 
+#include "../Messages/ObserverBase.h"
 #include "../Scene2D/PolylineSceneLayer.h"
 #include "../Scene2D/Scene2D.h"
 #include "../Scene2D/ScenePoint2D.h"
@@ -27,7 +28,6 @@
 #include "../Scene2DViewport/PredeclaredTypes.h"
 #include "../Scene2DViewport/ViewportController.h"
 
-#include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
 
 #include <vector>
@@ -38,10 +38,12 @@
   class IFlexiblePointerTracker;
   class MeasureToolMemento;
 
-  class MeasureTool : public IObserver
+  class MeasureTool : public ObserverBase<MeasureTool>
   {
   public:
-    virtual ~MeasureTool();
+    virtual ~MeasureTool()
+    {
+    }
 
     /**
     Enabled tools are rendered in the scene.
@@ -111,7 +113,7 @@
     virtual std::string GetDescription() = 0;
 
   protected:
-    MeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
+    MeasureTool(boost::weak_ptr<ViewportController> controllerW);
 
     /**
     The measuring tool may exist in a standalone fashion, without any available
--- a/Framework/Scene2DViewport/ViewportController.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -30,10 +30,8 @@
 namespace OrthancStone
 {
   ViewportController::ViewportController(boost::weak_ptr<UndoStack> undoStackW,
-                                         MessageBroker& broker,
                                          IViewport& viewport)
-    : IObservable(broker)
-    , undoStackW_(undoStackW)
+    : undoStackW_(undoStackW)
     , canvasToSceneFactor_(0.0)
     , viewport_(viewport)
   {
--- a/Framework/Scene2DViewport/ViewportController.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Scene2DViewport/ViewportController.h	Fri Nov 29 11:03:41 2019 +0100
@@ -81,7 +81,6 @@
       SceneTransformChanged, ViewportController);
 
     ViewportController(boost::weak_ptr<UndoStack> undoStackW,
-                       MessageBroker& broker,
                        IViewport& viewport);
 
 
--- a/Framework/StoneEnumerations.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/StoneEnumerations.h	Fri Nov 29 11:03:41 2019 +0100
@@ -122,6 +122,15 @@
     BitmapAnchor_TopRight
   };
 
+  enum SliceAction
+  {
+    SliceAction_FastPlus,
+    SliceAction_Plus,
+    SliceAction_None,
+    SliceAction_Minus,
+    SliceAction_FastMinus
+  };
+
   SopClassUid StringToSopClassUid(const std::string& source);
 
   void ComputeWindowing(float& targetCenter,
--- a/Framework/StoneInitialization.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/StoneInitialization.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -21,20 +21,53 @@
 
 #include "StoneInitialization.h"
 
-#include <Core/OrthancException.h>
-
 #if !defined(ORTHANC_ENABLE_SDL)
 #  error Macro ORTHANC_ENABLE_SDL must be defined
 #endif
 
+#if !defined(ORTHANC_ENABLE_QT)
+#  error Macro ORTHANC_ENABLE_QT must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_SSL)
+#  error Macro ORTHANC_ENABLE_SSL must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_CURL)
+#  error Macro ORTHANC_ENABLE_CURL must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error Macro ORTHANC_ENABLE_DCMTK must be defined
+#  if !defined(DCMTK_VERSION_NUMBER)
+#    error Macro DCMTK_VERSION_NUMBER must be defined
+#  endif
+#endif
+
 #if ORTHANC_ENABLE_SDL == 1
 #  include "Viewport/SdlWindow.h"
 #endif
 
+#if ORTHANC_ENABLE_QT == 1
+#  include <QCoreApplication>
+#endif
+
 #if ORTHANC_ENABLE_CURL == 1
-#include <Core/HttpClient.h>
+#  include <Core/HttpClient.h>
+#endif
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <Core/DicomParsing/FromDcmtkBridge.h>
 #endif
 
+#include "Toolbox/LinearAlgebra.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <locale>
+
+
 namespace OrthancStone
 {
 #if ORTHANC_ENABLE_LOGGING_PLUGIN == 1
@@ -49,14 +82,91 @@
     Orthanc::Logging::Initialize();
 #endif
 
-#if ORTHANC_ENABLE_SDL == 1
-    OrthancStone::SdlWindow::GlobalInitialize();
+#if ORTHANC_ENABLE_SSL == 1
+    // Must be before curl
+    Orthanc::Toolbox::InitializeOpenSsl();
 #endif
 
 #if ORTHANC_ENABLE_CURL == 1
     Orthanc::HttpClient::GlobalInitialize();
+#  if ORTHANC_ENABLE_SSL == 1
+    Orthanc::HttpClient::ConfigureSsl(false, "");
+#  endif
+#endif
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    Orthanc::FromDcmtkBridge::InitializeDictionary(true);
+    Orthanc::FromDcmtkBridge::InitializeCodecs();
+#  if DCMTK_VERSION_NUMBER <= 360
+    OFLog::configure(OFLogger::FATAL_LOG_LEVEL);
+#  else
+    OFLog::configure(OFLogger::OFF_LOG_LEVEL);
+#  endif
+#endif
+
+    /**
+     * This call is necessary to make "boost::lexical_cast<>" work in
+     * a consistent way in the presence of "double" or "float", and of
+     * a numeric locale that replaces dot (".") by comma (",") as the
+     * decimal separator.
+     * https://stackoverflow.com/a/18981514/881731
+     **/
+    std::locale::global(std::locale::classic());
+
+    {
+      // Run-time checks of locale settings, to be run after Qt has
+      // been initialized, as Qt changes locale settings
+
+#if ORTHANC_ENABLE_QT == 1
+      if (QCoreApplication::instance() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "Qt must be initialized before Stone");
+      }
+#endif
+      
+      {
+        OrthancStone::Vector v;
+        if (!OrthancStone::LinearAlgebra::ParseVector(v, "1.3671875\\-1.3671875") ||
+            v.size() != 2 ||
+            !OrthancStone::LinearAlgebra::IsNear(1.3671875f, v[0]) ||
+            !OrthancStone::LinearAlgebra::IsNear(-1.3671875f, v[1]))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Error in the locale settings, giving up");
+        }
+      }
+
+      {
+        Json::Value dicomweb = Json::objectValue;
+        dicomweb["00280030"] = Json::objectValue;
+        dicomweb["00280030"]["vr"] = "DS";
+        dicomweb["00280030"]["Value"] = Json::arrayValue;
+        dicomweb["00280030"]["Value"].append(1.2f);
+        dicomweb["00280030"]["Value"].append(-1.5f);
+
+        Orthanc::DicomMap source;
+        source.FromDicomWeb(dicomweb);
+
+        std::string s;
+        OrthancStone::Vector v;
+        if (!source.LookupStringValue(s, Orthanc::DICOM_TAG_PIXEL_SPACING, false) ||
+            !OrthancStone::LinearAlgebra::ParseVector(v, s) ||
+            v.size() != 2 ||
+            !OrthancStone::LinearAlgebra::IsNear(1.2f, v[0]) ||
+            !OrthancStone::LinearAlgebra::IsNear(-1.5f, v[1]))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Error in the locale settings, giving up");
+        }
+      }
+    }
+
+#if ORTHANC_ENABLE_SDL == 1
+    OrthancStone::SdlWindow::GlobalInitialize();
 #endif
   }
+  
 
   void StoneFinalize()
   {
@@ -64,10 +174,18 @@
     OrthancStone::SdlWindow::GlobalFinalize();
 #endif
     
+#if ORTHANC_ENABLE_DCMTK == 1
+    Orthanc::FromDcmtkBridge::FinalizeCodecs();
+#endif
+
 #if ORTHANC_ENABLE_CURL == 1
     Orthanc::HttpClient::GlobalFinalize();
 #endif
 
+#if ORTHANC_ENABLE_SSL == 1
+    Orthanc::Toolbox::FinalizeOpenSsl();
+#endif
+
     Orthanc::Logging::Finalize();
   }
 }
--- a/Framework/Toolbox/CoordinateSystem3D.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/CoordinateSystem3D.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -241,4 +241,14 @@
     return s;
   }
 
+
+  CoordinateSystem3D CoordinateSystem3D::NormalizeCuttingPlane(const CoordinateSystem3D& plane)
+  {
+    double ox, oy;
+    plane.ProjectPoint(ox, oy, LinearAlgebra::CreateVector(0, 0, 0));
+
+    CoordinateSystem3D normalized(plane);
+    normalized.SetOrigin(plane.MapSliceToWorldCoordinates(ox, oy));
+    return normalized;
+  }
 }
--- a/Framework/Toolbox/CoordinateSystem3D.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/CoordinateSystem3D.h	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "LinearAlgebra.h"
+#include "../Scene2D/ScenePoint2D.h"
 
 #include <Plugins/Samples/Common/IDicomDataset.h>
 
@@ -95,12 +96,24 @@
     Vector MapSliceToWorldCoordinates(double x,
                                       double y) const;
     
+    Vector MapSliceToWorldCoordinates(const ScenePoint2D& p) const
+    {
+      return MapSliceToWorldCoordinates(p.GetX(), p.GetY());
+    }
+    
     double ProjectAlongNormal(const Vector& point) const;
 
     void ProjectPoint(double& offsetX,
                       double& offsetY,
                       const Vector& point) const;
 
+    ScenePoint2D ProjectPoint(const Vector& point) const
+    {
+      double x, y;
+      ProjectPoint(x, y, point);
+      return ScenePoint2D(x, y);
+    }
+
     /*
     Alternated faster implementation (untested yet)
     */
@@ -120,5 +133,9 @@
     static bool ComputeDistance(double& distance,
                                 const CoordinateSystem3D& a,
                                 const CoordinateSystem3D& b);
+
+    // Normalize a cutting plane so that the origin (0,0,0) of the 3D
+    // world is mapped to the origin of its (x,y) coordinate system
+    static CoordinateSystem3D NormalizeCuttingPlane(const CoordinateSystem3D& plane);
   };
 }
--- a/Framework/Toolbox/DicomInstanceParameters.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/DicomInstanceParameters.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -265,7 +265,7 @@
 
       
   void DicomInstanceParameters::Data::ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image,
-                                                   bool useDouble) const
+                                                                 bool useDouble) const
   {
     if (image.GetFormat() != Orthanc::PixelFormat_Float32)
     {
@@ -455,4 +455,48 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
   }
+
+
+  double DicomInstanceParameters::Data::ApplyRescale(double value) const
+  {
+    double factor = doseGridScaling_;
+    double offset = 0.0;
+
+    if (hasRescale_)
+    {
+      factor *= rescaleSlope_;
+      offset = rescaleIntercept_;
+    }
+
+    return (value * factor + offset);
+  }
+
+
+  bool DicomInstanceParameters::Data::ComputeRegularSpacing(double& spacing) const
+  {
+    if (frameOffsets_.size() == 0)  // Not a RT-DOSE
+    {
+      return false;
+    }
+    else if (frameOffsets_.size() == 1)
+    {
+      spacing = 1;   // Edge case: RT-DOSE with one single frame
+      return true;
+    }
+    else
+    {
+      spacing = std::abs(frameOffsets_[1] - frameOffsets_[0]);
+
+      for (size_t i = 1; i + 1 < frameOffsets_.size(); i++)
+      {
+        double s = frameOffsets_[i + 1] - frameOffsets_[i];
+        if (!LinearAlgebra::IsNear(spacing, s, 0.001))
+        {
+          return false;
+        }
+      }
+      
+      return true;
+    }
+  }
 }
--- a/Framework/Toolbox/DicomInstanceParameters.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/DicomInstanceParameters.h	Fri Nov 29 11:03:41 2019 +0100
@@ -71,8 +71,12 @@
       bool IsPlaneWithinSlice(unsigned int frame,
                               const CoordinateSystem3D& plane) const;
       
-      void ApplyRescaleAndDoseScaling(
-        Orthanc::ImageAccessor& image, bool useDouble) const;
+      void ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image,
+                                      bool useDouble) const;
+
+      double ApplyRescale(double value) const;
+
+      bool ComputeRegularSpacing(double& target) const;
     };
 
     
@@ -207,9 +211,25 @@
       return data_.doseUnits_;
     }
 
+    void SetDoseGridScaling(double value)
+    {
+      data_.doseGridScaling_ = value;
+    }
+
     double GetDoseGridScaling() const
     {
       return data_.doseGridScaling_;
     }
+
+    double ApplyRescale(double value) const
+    {
+      return data_.ApplyRescale(value);
+    }
+
+    // Required for RT-DOSE
+    bool ComputeRegularSpacing(double& target) const
+    {
+      return data_.ComputeRegularSpacing(target);
+    }
   };
 }
--- a/Framework/Toolbox/DicomStructureSet.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -27,12 +27,11 @@
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
-#include <Plugins/Samples/Common/FullOrthancDataset.h>
 #include <Plugins/Samples/Common/DicomDatasetReader.h>
 
 #if defined(_MSC_VER)
-#pragma warning(push)
-#pragma warning(disable:4244)
+#  pragma warning(push)
+#  pragma warning(disable:4244)
 #endif
 
 #include <limits>
@@ -43,9 +42,14 @@
 #include <boost/geometry/multi/geometries/multi_polygon.hpp>
 
 #if defined(_MSC_VER)
-#pragma warning(pop)
+#  pragma warning(pop)
 #endif
 
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "ParsedDicomDataset.h"
+#endif
+
+
 typedef boost::geometry::model::d2::point_xy<double> BoostPoint;
 typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon;
 typedef boost::geometry::model::multi_polygon<BoostPolygon>  BoostMultiPolygon;
@@ -81,7 +85,7 @@
   }
 }
 
-#ifdef USE_BOOST_UNION_FOR_POLYGONS
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
 
 static BoostPolygon CreateRectangle(float x1, float y1,
                                     float x2, float y2)
@@ -99,7 +103,7 @@
 namespace OrthancStone
 {
   static RtStructRectangleInSlab CreateRectangle(float x1, float y1,
-    float x2, float y2)
+                                                 float x2, float y2)
   {
     RtStructRectangleInSlab rect;
     rect.xmin = std::min(x1, x2);
@@ -174,15 +178,15 @@
       double magnitude =
         GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal());
       if(!LinearAlgebra::IsNear(
-        magnitude,
-        projectionAlongNormal_,
-        sliceThickness_ / 2.0 /* in mm */ ))
+           magnitude,
+           projectionAlongNormal_,
+           sliceThickness_ / 2.0 /* in mm */ ))
       {
         LOG(ERROR) << "This RT-STRUCT contains a point that is off the "
-          << "slice of its instance | "
-          << "magnitude = " << magnitude << " | "
-          << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
-          << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
+                   << "slice of its instance | "
+                   << "magnitude = " << magnitude << " | "
+                   << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
+                   << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
 
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
       }
@@ -202,10 +206,10 @@
       if (!onSlice)
       {
         LOG(WARNING) << "This RT-STRUCT contains a point that is off the "
-          << "slice of its instance | "
-          << "magnitude = " << magnitude << " | "
-          << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
-          << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
+                     << "slice of its instance | "
+                     << "magnitude = " << magnitude << " | "
+                     << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
+                     << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
       }
       return onSlice;
     }
@@ -373,12 +377,14 @@
     else if (GeometryToolbox::IsParallelOrOpposite
              (isOpposite, slice.GetNormal(), geometry_.GetAxisX()))
     {
-      // plane is constant X
+      // plane is constant X => Sagittal view (remember that in the
+      // sagittal projection, the normal must be swapped)
 
+      
       /*
-      Please read the comments in the section above, by taking into account
-      the fact that, in this case, the plane has a constant X, not Y (in 
-      polygon geometry_ coordinates)
+        Please read the comments in the section above, by taking into account
+        the fact that, in this case, the plane has a constant X, not Y (in 
+        polygon geometry_ coordinates)
       */
 
       if (x < extent_.GetX1() ||
@@ -427,10 +433,6 @@
         slice.ProjectPoint2(x1, y1, p1);
         slice.ProjectPoint2(x2, y2, p2);
 
-        // TODO WHY THIS???
-        y1 = -y1;
-        y2 = -y2;
-
         return true;
       }
     }
@@ -463,7 +465,7 @@
     return structures_[index];
   }
 
-  DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags)
+  void DicomStructureSet::Setup(const OrthancPlugins::IDicomDataset& tags)
   {
     OrthancPlugins::DicomDatasetReader reader(tags);
     
@@ -514,11 +516,11 @@
       }
 
       LOG(INFO) << "New RT structure: \"" << structures_[i].name_ 
-                   << "\" with interpretation \"" << structures_[i].interpretation_
-                   << "\" containing " << countSlices << " slices (color: " 
-                   << static_cast<int>(structures_[i].red_) << "," 
-                   << static_cast<int>(structures_[i].green_) << ","
-                   << static_cast<int>(structures_[i].blue_) << ")";
+                << "\" with interpretation \"" << structures_[i].interpretation_
+                << "\" containing " << countSlices << " slices (color: " 
+                << static_cast<int>(structures_[i].red_) << "," 
+                << static_cast<int>(structures_[i].green_) << ","
+                << static_cast<int>(structures_[i].blue_) << ")";
 
       // These temporary variables avoid allocating many vectors in the loop below
       OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
@@ -609,6 +611,15 @@
   }
 
 
+#if ORTHANC_ENABLE_DCMTK == 1
+  DicomStructureSet::DicomStructureSet(Orthanc::ParsedDicomFile& instance)
+  {
+    ParsedDicomDataset dataset(instance);
+    Setup(dataset);
+  }
+#endif
+  
+
   Vector DicomStructureSet::GetStructureCenter(size_t index) const
   {
     const Structure& structure = GetStructure(index);
@@ -773,14 +784,14 @@
           if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
           {
             LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): "
-              << " missing information about referenced instance "
-              << "(sopInstanceUid is empty!)";
+                       << " missing information about referenced instance "
+                       << "(sopInstanceUid is empty!)";
           }
           else
           {
             LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): "
-              << " missing information about referenced instance "
-              << "(sopInstanceUid = " << sopInstanceUid << ")";
+                       << " missing information about referenced instance "
+                       << "(sopInstanceUid = " << sopInstanceUid << ")";
           }
           //throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
         }
@@ -803,17 +814,18 @@
     }
   }
 
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
-  bool DicomStructureSet::ProjectStructure(std::vector< std::vector<Point2D> >& polygons,
-                                           const Structure& structure,
-                                           const CoordinateSystem3D& slice) const
+  bool DicomStructureSet::ProjectStructure(
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
+    std::vector< std::vector<Point2D> >& polygons,
 #else
-  bool DicomStructureSet::ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments,
+    std::vector< std::pair<Point2D, Point2D> >& segments,
+#endif
     const Structure& structure,
-    const CoordinateSystem3D& slice) const
-#endif
+    const CoordinateSystem3D& sourceSlice) const
   {
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
+    const CoordinateSystem3D slice = CoordinateSystem3D::NormalizeCuttingPlane(sourceSlice);
+    
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
     polygons.clear();
 #else
     segments.clear();
@@ -831,7 +843,7 @@
       {
         if (polygon->IsOnSlice(slice))
         {
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
           polygons.push_back(std::vector<Point2D>());
           
           for (Points::const_iterator p = polygon->GetPoints().begin();
@@ -882,15 +894,26 @@
 #if 1
       // Sagittal or coronal projection
 
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
       std::vector<BoostPolygon> projected;
+
+      for (Polygons::const_iterator polygon = structure.polygons_.begin();
+           polygon != structure.polygons_.end(); ++polygon)
+      {
+        double x1, y1, x2, y2;
+
+        if (polygon->Project(x1, y1, x2, y2, slice))
+        {
+          projected.push_back(CreateRectangle(x1, y1, x2, y2));
+        }
+      }
 #else
       // this will contain the intersection of the polygon slab with
       // the cutting plane, projected on the cutting plane coord system 
       // (that yields a rectangle) + the Z coordinate of the polygon 
       // (this is required to group polygons with the same Z later)
       std::vector<std::pair<RtStructRectangleInSlab, double> > projected;
-#endif
+
       for (Polygons::const_iterator polygon = structure.polygons_.begin();
            polygon != structure.polygons_.end(); ++polygon)
       {
@@ -903,13 +926,15 @@
           // x1,y1 and x2,y2 are in "slice" coordinates (the cutting plane 
           // geometry)
           projected.push_back(std::make_pair(CreateRectangle(
-            static_cast<float>(x1), 
-            static_cast<float>(y1), 
-            static_cast<float>(x2), 
-            static_cast<float>(y2)),curZ));
+                                               static_cast<float>(x1), 
+                                               static_cast<float>(y1), 
+                                               static_cast<float>(x2), 
+                                               static_cast<float>(y2)),curZ));
         }
       }
-#ifndef USE_BOOST_UNION_FOR_POLYGONS
+#endif
+
+#if USE_BOOST_UNION_FOR_POLYGONS != 1
       // projected contains a set of rectangles specified by two opposite
       // corners (x1,y1,x2,y2)
       // we need to merge them 
@@ -999,4 +1024,44 @@
       return false;
     }
   }
+
+
+  void DicomStructureSet::ProjectOntoLayer(PolylineSceneLayer& layer,
+                                           const CoordinateSystem3D& plane,
+                                           size_t structureIndex,
+                                           const Color& color) const
+  {
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
+    std::vector< std::vector<Point2D> > polygons;
+    if (ProjectStructure(polygons, structureIndex, plane))
+    {
+      for (size_t j = 0; j < polygons.size(); j++)
+      {
+        std::vector<ScenePoint2D> chain;
+        chain.reserve(polygons[j].size());
+
+        for (size_t k = 0; k < polygons[j].size(); k++)
+        {
+          chain.push_back(ScenePoint2D(polygons[j][k].x, polygons[j][k].y));
+        }
+
+        layer.AddChain(chain, true, color.GetRed(), color.GetGreen(), color.GetBlue());
+      }
+    }
+    
+#else
+    std::vector< std::pair<Point2D, Point2D> >  segments;
+
+    if (ProjectStructure(segments, structureIndex, plane))
+    {
+      for (size_t j = 0; j < segments.size(); j++)
+      {
+        std::vector<ScenePoint2D> chain(2);
+        chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y);
+        chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y);
+        layer.AddChain(chain, false, color.GetRed(), color.GetGreen(), color.GetBlue());
+      }
+    }
+#endif
+  }
 }
--- a/Framework/Toolbox/DicomStructureSet.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/DicomStructureSet.h	Fri Nov 29 11:03:41 2019 +0100
@@ -21,10 +21,19 @@
 
 #pragma once
 
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
 #include "DicomStructureSetUtils.h"
 #include "CoordinateSystem3D.h"
 #include "Extent2D.h"
 #include "../Scene2D/Color.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <Core/DicomParsing/ParsedDicomFile.h>
+#endif
 
 //#define USE_BOOST_UNION_FOR_POLYGONS 1
 
@@ -137,21 +146,30 @@
     Structures        structures_;
     ReferencedSlices  referencedSlices_;
 
+    void Setup(const OrthancPlugins::IDicomDataset& dataset);
+    
     const Structure& GetStructure(size_t index) const;
 
     Structure& GetStructure(size_t index);
   
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
-    bool ProjectStructure(std::vector< std::vector<Point2D> >& polygons,
-                          const Structure& structure,
-                          const CoordinateSystem3D& slice) const;
+    bool ProjectStructure(
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
+      std::vector< std::vector<Point2D> >& polygons,
 #else
-    bool ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments,
+      std::vector< std::pair<Point2D, Point2D> >& segments,
+#endif
       const Structure& structure,
       const CoordinateSystem3D& slice) const;
-#endif
+
   public:
-    DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance);
+    DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance)
+    {
+      Setup(instance);
+    }
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    DicomStructureSet(Orthanc::ParsedDicomFile& instance);
+#endif
 
     size_t GetStructuresCount() const
     {
@@ -185,20 +203,32 @@
 
     Vector GetNormal() const;
 
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
+#if USE_BOOST_UNION_FOR_POLYGONS == 1
     bool ProjectStructure(std::vector< std::vector<Point2D> >& polygons,
-      size_t index,
-      const CoordinateSystem3D& slice) const
+                          size_t index,
+                          const CoordinateSystem3D& slice) const
     {
       return ProjectStructure(polygons, GetStructure(index), slice);
     }
 #else
     bool ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments,
-      size_t index,
-      const CoordinateSystem3D& slice) const
+                          size_t index,
+                          const CoordinateSystem3D& slice) const
     {
       return ProjectStructure(segments, GetStructure(index), slice);
     }
 #endif
+
+    void ProjectOntoLayer(PolylineSceneLayer& layer,
+                          const CoordinateSystem3D& plane,
+                          size_t structureIndex,
+                          const Color& color) const;
+
+    void ProjectOntoLayer(PolylineSceneLayer& layer,
+                          const CoordinateSystem3D& plane,
+                          size_t structureIndex) const
+    {
+      ProjectOntoLayer(layer, plane, structureIndex, GetStructureColor(structureIndex));
+    }
   };
 }
--- a/Framework/Toolbox/LinearAlgebra.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/LinearAlgebra.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -22,6 +22,7 @@
 #include "LinearAlgebra.h"
 
 #include "../StoneException.h"
+#include "GenericToolbox.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -32,6 +33,7 @@
 
 #include <stdio.h>
 #include <iostream>
+#include <cstdlib>
 
 namespace OrthancStone
 {
@@ -74,13 +76,30 @@
       for (size_t i = 0; i < items.size(); i++)
       {
         /**
+         * SJO - 2019-11-19 - WARNING: I reverted from "std::stod()"
+         * to "boost::lexical_cast", as both "std::stod()" and
+         * "std::strtod()" are sensitive to locale settings, making
+         * this code non portable and very dangerous as it fails
+         * silently. A string such as "1.3671875\1.3671875" is
+         * interpreted as "1\1", because "std::stod()" expects a comma
+         * (",") instead of a point ("."). This problem is notably
+         * seen in Qt-based applications, that somehow set locales
+         * aggressively.
+         *
+         * "boost::lexical_cast<>" is also dependent on the locale
+         * settings, but apparently not in a way that makes this
+         * function fail with Qt. The Orthanc core defines macro
+         * "-DBOOST_LEXICAL_CAST_ASSUME_C_LOCALE" in static builds to
+         * this end.
+         **/
+        
+#if 0  // __cplusplus >= 201103L  // Is C++11 enabled?
+        /**
          * We try and avoid the use of "boost::lexical_cast<>" here,
-         * as it is very slow. As we are parsing many doubles, we
-         * prefer to use the standard "std::stod" function if
-         * available: http://www.cplusplus.com/reference/string/stod/
+         * as it is very slow, and as Stone has to parse many doubles.
+         * https://tinodidriksen.com/2011/05/cpp-convert-string-to-double-speed/
          **/
           
-#if __cplusplus >= 201103L  // Is C++11 enabled?
         try
         {
           target[i] = std::stod(items[i]);
@@ -90,7 +109,37 @@
           target.clear();
           return false;
         }
-#else  // Fallback implementation using Boost
+
+#elif 0
+        /**
+         * "std::strtod()" is the recommended alternative to
+         * "std::stod()". It is apparently as fast as plain-C
+         * "atof()", with more security.
+         **/
+        char* end = NULL;
+        target[i] = std::strtod(items[i].c_str(), &end);
+        if (end == NULL ||
+            end != items[i].c_str() + items[i].size())
+        {
+          return false;
+        }
+
+#elif 1
+        /**
+         * Use of our homemade implementation of
+         * "boost::lexical_cast<double>()". It is much faster than boost.
+         **/
+        if (!GenericToolbox::StringToDouble(target[i], items[i].c_str()))
+        {
+          return false;
+        }
+        
+#else
+        /**
+         * Fallback implementation using Boost (slower, but somehow
+         * independent to locale contrarily to "std::stod()", and
+         * generic as it does not use our custom implementation).
+         **/
         try
         {
           target[i] = boost::lexical_cast<double>(items[i]);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParsedDicomCache.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,149 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParsedDicomCache.h"
+
+namespace OrthancStone
+{
+  class ParsedDicomCache::Item : public Orthanc::ICacheable
+  {
+  private:
+    boost::mutex                             mutex_;
+    std::auto_ptr<Orthanc::ParsedDicomFile>  dicom_;
+    size_t                                   fileSize_;
+    bool                                     hasPixelData_;
+    
+  public:
+    Item(Orthanc::ParsedDicomFile* dicom,
+         size_t fileSize,
+         bool hasPixelData) :
+      dicom_(dicom),
+      fileSize_(fileSize),
+      hasPixelData_(hasPixelData)
+    {
+      if (dicom == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    boost::mutex& GetMutex()
+    {
+      return mutex_;
+    }
+           
+    virtual size_t GetMemoryUsage() const
+    {
+      return fileSize_;
+    }
+
+    Orthanc::ParsedDicomFile& GetDicom() const
+    {
+      assert(dicom_.get() != NULL);
+      return *dicom_;
+    }
+
+    bool HasPixelData() const
+    {
+      return hasPixelData_;
+    }
+  };
+    
+
+  std::string ParsedDicomCache::GetIndex(unsigned int bucket,
+                                         const std::string& bucketKey)
+  {
+    return boost::lexical_cast<std::string>(bucket) + "|" + bucketKey;
+  }
+  
+
+  void ParsedDicomCache::Acquire(unsigned int bucket,
+                                 const std::string& bucketKey,
+                                 Orthanc::ParsedDicomFile* dicom,
+                                 size_t fileSize,
+                                 bool hasPixelData)
+  {
+    LOG(TRACE) << "new item stored in cache: bucket " << bucket << ", key " << bucketKey;
+    cache_.Acquire(GetIndex(bucket, bucketKey), new Item(dicom, fileSize, hasPixelData));
+  }
+
+  
+  ParsedDicomCache::Reader::Reader(ParsedDicomCache& cache,
+                                   unsigned int bucket,
+                                   const std::string& bucketKey) :
+    /**
+     * The "DcmFileFormat" object cannot be accessed from multiple
+     * threads, even if using only getters. An unique lock (mutex) is
+     * mandatory.
+     **/
+    accessor_(cache.cache_, GetIndex(bucket, bucketKey), true /* unique */)
+  {
+    if (accessor_.IsValid())
+    {
+      LOG(TRACE) << "accessing item within cache: bucket " << bucket << ", key " << bucketKey;
+      item_ = &dynamic_cast<Item&>(accessor_.GetValue());
+    }
+    else
+    {
+      LOG(TRACE) << "missing item within cache: bucket " << bucket << ", key " << bucketKey;
+      item_ = NULL;
+    }
+  }
+
+
+  bool ParsedDicomCache::Reader::HasPixelData() const
+  {
+    if (item_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return item_->HasPixelData();
+    }
+  }
+
+  
+  Orthanc::ParsedDicomFile& ParsedDicomCache::Reader::GetDicom() const
+  {
+    if (item_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return item_->GetDicom();
+    }
+  }
+
+  
+  size_t ParsedDicomCache::Reader::GetFileSize() const
+  {
+    if (item_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return item_->GetMemoryUsage();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParsedDicomCache.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,80 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 <Core/Cache/MemoryObjectCache.h>
+#include <Core/DicomParsing/ParsedDicomFile.h>
+
+namespace OrthancStone
+{
+  class ParsedDicomCache : public boost::noncopyable
+  {
+  private:
+    class Item;
+
+    static std::string GetIndex(unsigned int bucket,
+                                const std::string& bucketKey);
+    
+    Orthanc::MemoryObjectCache  cache_;
+
+  public:
+    ParsedDicomCache(size_t size)
+    {
+      cache_.SetMaximumSize(size);
+    }
+
+    void Invalidate(unsigned int bucket,
+                    const std::string& bucketKey)
+    {
+      cache_.Invalidate(GetIndex(bucket, bucketKey));
+    }
+    
+    void Acquire(unsigned int bucket,
+                 const std::string& bucketKey,
+                 Orthanc::ParsedDicomFile* dicom,
+                 size_t fileSize,
+                 bool hasPixelData);
+
+    class Reader : public boost::noncopyable
+    {
+    private:
+      Orthanc::MemoryObjectCache::Accessor accessor_;
+      Item*                                item_;
+
+    public:
+      Reader(ParsedDicomCache& cache,
+             unsigned int bucket,
+             const std::string& bucketKey);
+
+      bool IsValid() const
+      {
+        return item_ != NULL;
+      }
+
+      bool HasPixelData() const;
+
+      Orthanc::ParsedDicomFile& GetDicom() const;
+
+      size_t GetFileSize() const;
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParsedDicomDataset.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,104 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ParsedDicomDataset.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+
+namespace OrthancStone
+{
+  static DcmItem* LookupPath(Orthanc::ParsedDicomFile& dicom,
+                             const OrthancPlugins::DicomPath& path)
+  {
+    DcmItem* node = dicom.GetDcmtkObject().getDataset();
+      
+    for (size_t i = 0; i < path.GetPrefixLength(); i++)
+    {
+      const OrthancPlugins::DicomTag& tmp = path.GetPrefixTag(i);
+      DcmTagKey tag(tmp.GetGroup(), tmp.GetElement());
+
+      DcmSequenceOfItems* sequence = NULL;
+      if (!node->findAndGetSequence(tag, sequence).good() ||
+          sequence == NULL)
+      {
+        return NULL;
+      }
+
+      unsigned long pos = path.GetPrefixIndex(i);
+      if (pos >= sequence->card())
+      {
+        return NULL;
+      }
+
+      node = sequence->getItem(pos);
+      if (node == NULL)
+      {
+        return NULL;
+      }
+    }
+
+    return node;
+  }
+
+    
+  bool ParsedDicomDataset::GetStringValue(std::string& result,
+                                          const OrthancPlugins::DicomPath& path) const
+  {
+    DcmItem* node = LookupPath(dicom_, path);
+      
+    if (node != NULL)
+    {
+      DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+
+      const char* s = NULL;
+      if (node->findAndGetString(tag, s).good() &&
+          s != NULL)
+      {
+        result.assign(s);
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  bool ParsedDicomDataset::GetSequenceSize(size_t& size,
+                                           const OrthancPlugins::DicomPath& path) const
+  {
+    DcmItem* node = LookupPath(dicom_, path);
+      
+    if (node != NULL)
+    {
+      DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
+
+      DcmSequenceOfItems* s = NULL;
+      if (node->findAndGetSequence(tag, s).good() &&
+          s != NULL)
+      {
+        size = s->card();
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ParsedDicomDataset.h	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,46 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 <Core/DicomParsing/ParsedDicomFile.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
+
+namespace OrthancStone
+{
+  class ParsedDicomDataset : public OrthancPlugins::IDicomDataset
+  {
+  private:
+    Orthanc::ParsedDicomFile&  dicom_;
+
+  public:
+    ParsedDicomDataset(Orthanc::ParsedDicomFile& dicom) :
+      dicom_(dicom)
+    {
+    }
+
+    virtual bool GetStringValue(std::string& result,
+                                const OrthancPlugins::DicomPath& path) const ORTHANC_OVERRIDE;
+
+    virtual bool GetSequenceSize(size_t& size,
+                                 const OrthancPlugins::DicomPath& path) const ORTHANC_OVERRIDE;
+  };
+}
--- a/Framework/Toolbox/SlicesSorter.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/SlicesSorter.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -289,13 +289,14 @@
   }
 
 
-  double SlicesSorter::ComputeSpacingBetweenSlices() const
+  bool SlicesSorter::ComputeSpacingBetweenSlices(double& spacing /* out */) const
   {
     if (GetSlicesCount() <= 1)
     {
       // This is a volume that is empty or that contains one single
       // slice: Choose a dummy z-dimension for voxels
-      return 1.0;
+      spacing = 1.0;
+      return true;
     }
     
     const OrthancStone::CoordinateSystem3D& reference = GetSliceGeometry(0);
@@ -303,28 +304,27 @@
     double referencePosition = reference.ProjectAlongNormal(reference.GetOrigin());
         
     double p = reference.ProjectAlongNormal(GetSliceGeometry(1).GetOrigin());
-    double spacingZ = p - referencePosition;
+    spacing = p - referencePosition;
 
-    if (spacingZ <= 0)
+    if (spacing <= 0)
     {
-      LOG(ERROR) << "SlicesSorter::ComputeSpacingBetweenSlices(): (spacingZ <= 0)";
+      LOG(ERROR) << "SlicesSorter::ComputeSpacingBetweenSlices(): (spacing <= 0)";
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
                                       "Please call the Sort() method before");
     }
 
     for (size_t i = 1; i < GetSlicesCount(); i++)
     {
-      OrthancStone::Vector p = reference.GetOrigin() + spacingZ * static_cast<double>(i) * reference.GetNormal();        
+      OrthancStone::Vector p = reference.GetOrigin() + spacing * static_cast<double>(i) * reference.GetNormal();
       double d = boost::numeric::ublas::norm_2(p - GetSliceGeometry(i).GetOrigin());
 
       if (!OrthancStone::LinearAlgebra::IsNear(d, 0, 0.001 /* tolerance expressed in mm */))
       {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "The origins of the slices of a volume image are not regularly spaced");
+        return false;
       }
     }
 
-    return spacingZ;
+    return true;
   }
 
 
--- a/Framework/Toolbox/SlicesSorter.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Toolbox/SlicesSorter.h	Fri Nov 29 11:03:41 2019 +0100
@@ -91,7 +91,7 @@
                             const CoordinateSystem3D& slice) const;
 
     // WARNING - The slices must have been sorted before calling this method
-    double ComputeSpacingBetweenSlices() const;
+    bool ComputeSpacingBetweenSlices(double& spacing /* out */) const;
 
     // WARNING - The slices must have been sorted before calling this method
     bool AreAllSlicesDistinct() const;
--- a/Framework/Viewport/IViewport.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Viewport/IViewport.h	Fri Nov 29 11:03:41 2019 +0100
@@ -51,4 +51,3 @@
     virtual const ICompositor& GetCompositor() const = 0;
   };
 }
-
--- a/Framework/Viewport/SdlWindow.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Viewport/SdlWindow.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -31,6 +31,8 @@
 #endif 
 // WIN32
 
+#include <SDL_render.h>
+#include <SDL_video.h>
 #include <SDL.h>
 
 namespace OrthancStone
--- a/Framework/Viewport/SdlWindow.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Viewport/SdlWindow.h	Fri Nov 29 11:03:41 2019 +0100
@@ -23,18 +23,22 @@
 
 #if ORTHANC_ENABLE_SDL == 1
 
-#include <SDL_render.h>
-#include <SDL_video.h>
 #include <boost/noncopyable.hpp>
 
+// Forward declaration of SDL type to avoid clashes with DCMTK headers
+// on "typedef Sint8", in "StoneInitialization.cpp"
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Surface;
+
 namespace OrthancStone
 {
   class SdlWindow : public boost::noncopyable
   {
   private:
-    SDL_Window    *window_;
-    SDL_Renderer  *renderer_;
-    bool           maximized_;
+    struct SDL_Window   *window_;
+    struct SDL_Renderer *renderer_;
+    bool                 maximized_;
 
   public:
     SdlWindow(const char* title,
@@ -54,7 +58,7 @@
 
     unsigned int GetHeight() const;
 
-    void Render(SDL_Surface* surface);
+    void Render(struct SDL_Surface* surface);
 
     void ToggleMaximize();
 
--- a/Framework/Viewport/ViewportBase.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Viewport/ViewportBase.h	Fri Nov 29 11:03:41 2019 +0100
@@ -49,6 +49,5 @@
         const_cast<IViewport*>(static_cast<const IViewport*>(this));
       return mutableThis->GetCompositor();
     }
-
   };
 }
--- a/Framework/Volumes/DicomVolumeImage.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Volumes/DicomVolumeImage.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,14 +28,6 @@
 
 namespace OrthancStone
 {
-  class IGeometryProvider
-  {
-  public:
-    virtual ~IGeometryProvider() {}
-    virtual bool HasGeometry() const = 0;
-    virtual const VolumeImageGeometry& GetImageGeometry() const = 0;
-  };
-
   /**
   This class combines a 3D image buffer, a 3D volume geometry and
   information about the DICOM parameters of the series.
@@ -44,6 +36,7 @@
   class DicomVolumeImage : public boost::noncopyable
   {
   public:
+    // TODO - Are these messages still useful?
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage);
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage);
 
@@ -67,8 +60,10 @@
     }
 
     void Initialize(const VolumeImageGeometry& geometry,
-                    Orthanc::PixelFormat format, bool computeRange = false);
+                    Orthanc::PixelFormat format, 
+                    bool computeRange = false);
 
+    // Used by volume slicers
     void SetDicomParameters(const DicomInstanceParameters& parameters);
     
     uint64_t GetRevision() const
--- a/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -45,8 +45,7 @@
     revision_(volume_.GetRevision())
   {
     valid_ = (volume_.HasDicomParameters() &&
-              volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, 
-                                                cuttingPlane));
+              volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane));
   }
 
 
@@ -82,21 +81,6 @@
       ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_);
       texture.reset(dynamic_cast<TextureBaseSceneLayer*>
                     (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters)));
-
-      // <DEBUG-BLOCK>
-#if 0
-      Orthanc::JpegWriter writer;
-      writer.SetQuality(60);
-      static int index = 0;
-      std::string filePath = "C:\\temp\\sliceReader_P";
-      filePath += boost::lexical_cast<std::string>(projection_);
-      filePath += "_I";
-      filePath += boost::lexical_cast<std::string>(index);
-      filePath += ".jpg";
-      index++;
-      writer.WriteToFile(filePath, reader.GetAccessor());
-#endif
-      // <END-OF-DEBUG-BLOCK>
     }
     
     const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_);
@@ -105,53 +89,11 @@
     cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin());
     cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX());
 
-    // <DEBUG-BLOCK>
-#if 0
     {
-      LOG(ERROR) << "+----------------------------------------------------+";
-      LOG(ERROR) << "| DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer |";
-      LOG(ERROR) << "+----------------------------------------------------+";
-      std::string projectionString;
-      switch (projection_)
-      {
-      case VolumeProjection_Coronal:
-        projectionString = "CORONAL";
-        break;
-      case VolumeProjection_Axial:
-        projectionString = "CORONAL";
-        break;
-      case VolumeProjection_Sagittal:
-        projectionString = "SAGITTAL";
-        break;
-      default:
-        ORTHANC_ASSERT(false);
-      }
-      if(volume_.GetGeometry().GetDepth() == 200)
-        LOG(ERROR) << "| CT     IMAGE 512x512 with projection " << projectionString;
-      else
-        LOG(ERROR) << "| RTDOSE IMAGE NNNxNNN with projection " << projectionString;
-      LOG(ERROR) << "+----------------------------------------------------+";
-      LOG(ERROR) << "| cuttingPlane = " << cuttingPlane;
-      LOG(ERROR) << "| point to project = " << system.GetOrigin();
-      LOG(ERROR) << "| result = x0: " << x0 << " y0: " << y0;
-      LOG(ERROR) << "+----------------------- END ------------------------+";
+      double xz, yz;
+      cuttingPlane.ProjectPoint(xz, yz, LinearAlgebra::CreateVector(0, 0, 0));
+      texture->SetOrigin(x0 - xz, y0 - yz);
     }
-#endif
-    // <END-OF-DEBUG-BLOCK>
-
-#if 1 // BGO 2019-08-13
-    // The sagittal coordinate system has a Y vector going down. The displayed
-    // image (scene coords) has a Y vector pointing upwards (towards the patient 
-    // coord Z index)
-    // we need to flip the Y local coordinates to get the scene-coord offset.
-    // TODO: this is quite ugly. Isn't there a better way?
-    if(projection_ == VolumeProjection_Sagittal)
-      texture->SetOrigin(x0, -y0);
-    else
-      texture->SetOrigin(x0, y0);
-#else
-    texture->SetOrigin(x0, y0);
-#endif
 
     double dx = x1 - x0;
     double dy = y1 - y0;
@@ -164,19 +106,6 @@
     Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_);
     texture->SetPixelSpacing(tmp[0], tmp[1]);
 
-    // <DEBUG-BLOCK>
-    {
-      //using std::endl;
-      //std::stringstream ss;
-      //ss << "DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer | cuttingPlane = " << cuttingPlane << " | projection_ = " << projection_ << endl;
-      //ss << "volume_.GetGeometry().GetProjectionGeometry(projection_) = " << system << endl;
-      //ss << "cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); --> | x0 = " << x0 << " | y0 = " << y0 << "| x1 = " << x1 << " | y1 = " << y1 << endl;
-      //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl;
-      //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl;
-      //LOG(ERROR) << ss.str();
-    }
-    // <END-OF-DEBUG-BLOCK>
-
     return texture.release();
   }
 
--- a/Framework/Volumes/IVolumeSlicer.cpp~	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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
-
-namespace OrthancStone
-{
-  /**
-  This interface is implemented by objects representing 3D volume data and 
-  that are able to return an object that:
-  - represent a slice of their data 
-  - are able to create the corresponding slice visual representation.
-  */
-  class IVolumeSlicer : public boost::noncopyable
-  {
-  public:
-    /**
-    This interface is implemented by objects representing a slice of 
-    volume data and that are able to create a 2D layer to display a this 
-    slice.
-
-    The CreateSceneLayer factory method is called with an optional
-    configurator that possibly impacts the ISceneLayer subclass that is 
-    created (for instance, if a LUT must be applied on the texture when
-    displaying it)
-    */
-    class IExtractedSlice : public boost::noncopyable
-    {
-    public:
-      virtual ~IExtractedSlice()
-      {
-      }
-
-      /**
-      Invalid slices are created when the data is not ready yet or if the
-      cut is outside of the available geometry.
-      */
-      virtual bool IsValid() = 0;
-
-      /**
-      This retrieves the *revision* that gets incremented every time the 
-      underlying object undergoes a mutable operation (that it, changes its 
-      state).
-      This **must** be a cheap call.
-      */
-      virtual uint64_t GetRevision() = 0;
-
-      /** Creates the slice visual representation */
-      virtual ISceneLayer* CreateSceneLayer(
-        const ILayerStyleConfigurator* configurator,  // possibly absent
-        const CoordinateSystem3D& cuttingPlane) = 0;
-    };
-
-    /**
-    See IExtractedSlice.IsValid()
-    */
-    class InvalidSlice : public IExtractedSlice
-    {
-    public:
-      virtual bool IsValid()
-      {
-        return false;
-      }
-
-      virtual uint64_t GetRevision()
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
-                                            const CoordinateSystem3D& cuttingPlane)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    };
-
-
-    virtual ~IVolumeSlicer()
-    {
-    }
-
-    /**
-    This method is implemented by the objects representing volumetric data
-    and must returns an IExtractedSlice subclass that contains all the data
-    needed to, later on, create its visual representation through
-    CreateSceneLayer.
-    Subclasses a.o.: 
-    - InvalidSlice, 
-    - DicomVolumeImageMPRSlicer::Slice, 
-    - DicomVolumeImageReslicer::Slice
-    - DicomStructureSetLoader::Slice 
-    */
-    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
-  };
-}
--- a/Framework/Volumes/IVolumeSlicer.h~	Thu Nov 28 18:28:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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
-
-namespace OrthancStone
-{
-  /**
-  This interface is implemented by objects representing 3D volume data and 
-  that are able to return an object that:
-  - represent a slice of their data 
-  - are able to create the corresponding slice visual representation.
-  */
-  class IVolumeSlicer : public boost::noncopyable
-  {
-  public:
-    /**
-    This interface is implemented by objects representing a slice of 
-    volume data and that are able to create a 2D layer to display a this 
-    slice.
-
-    The CreateSceneLayer factory method is called with an optional
-    configurator that possibly impacts the ISceneLayer subclass that is 
-    created (for instance, if a LUT must be applied on the texture when
-    displaying it)
-    */
-    class IExtractedSlice : public boost::noncopyable
-    {
-    public:
-      virtual ~IExtractedSlice()
-      {
-      }
-
-      /**
-      Invalid slices are created when the data is not ready yet or if the
-      cut is outside of the available geometry.
-      */
-      virtual bool IsValid() = 0;
-
-      /**
-      This retrieves the *revision* that gets incremented every time the 
-      underlying object undergoes a mutable operation (that it, changes its 
-      state).
-      This **must** be a cheap call.
-      */
-      virtual uint64_t GetRevision() = 0;
-
-      /** Creates the slice visual representation */
-      virtual ISceneLayer* CreateSceneLayer(
-        const ILayerStyleConfigurator* configurator,  // possibly absent
-        const CoordinateSystem3D& cuttingPlane) = 0;
-    };
-
-    /**
-    See IExtractedSlice.IsValid()
-    */
-    class InvalidSlice : public IExtractedSlice
-    {
-    public:
-      virtual bool IsValid()
-      {
-        return false;
-      }
-
-      virtual uint64_t GetRevision()
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
-                                            const CoordinateSystem3D& cuttingPlane)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    };
-
-
-    virtual ~IVolumeSlicer()
-    {
-    }
-
-    /**
-    This method is implemented by the objects representing volumetric data
-    and must returns an IExtractedSlice subclass that contains all the data
-    needed to, later on, create its visual representation through
-    CreateSceneLayer.
-    Subclasses a.o.: 
-    - InvalidSlice, 
-    - DicomVolumeImageMPRSlicer::Slice, 
-    - DicomVolumeImageReslicer::Slice
-    - DicomStructureSetLoader::Slice 
-    */
-    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
-  };
-}
--- a/Framework/Volumes/VolumeImageGeometry.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Framework/Volumes/VolumeImageGeometry.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -39,7 +39,7 @@
     
     sagittalGeometry_ = CoordinateSystem3D(p,
                                            axialGeometry_.GetAxisY(),
-                                           axialGeometry_.GetNormal());
+                                           -axialGeometry_.GetNormal());
 
     Vector origin = (
       axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0],
@@ -296,16 +296,18 @@
     {
       return false;
     }
-        
-    unsigned int d = static_cast<unsigned int>(std::floor(z));
-    if (d >= projectionDepth)
-    {
-      return false;
-    }
     else
     {
-      slice = d;
-      return true;
+      unsigned int d = static_cast<unsigned int>(std::floor(z));
+      if (d >= projectionDepth)
+      {
+        return false;
+      }
+      else
+      {
+        slice = d;
+        return true;
+      }
     }
   }
 
@@ -321,7 +323,18 @@
     Vector dim = GetVoxelDimensions(projection);
     CoordinateSystem3D plane = GetProjectionGeometry(projection);
 
-    plane.SetOrigin(plane.GetOrigin() + static_cast<double>(z) * plane.GetNormal() * dim[2]);
+    Vector normal = plane.GetNormal();
+    if (projection == VolumeProjection_Sagittal)
+    {
+      /**
+       * WARNING: In sagittal geometry, the normal points to REDUCING
+       * X-axis in the 3D world. This is necessary to keep the
+       * right-hand coordinate system. Hence the negation.
+       **/
+      normal = -normal;
+    }
+    
+    plane.SetOrigin(plane.GetOrigin() + static_cast<double>(z) * dim[2] * normal);
 
     return plane;
   }
--- a/Platforms/Generic/DelayedCallCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/DelayedCallCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -26,13 +26,11 @@
 
 namespace Deprecated
 {
-  DelayedCallCommand::DelayedCallCommand(OrthancStone::MessageBroker& broker,
-                                         OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,  // takes ownership
+  DelayedCallCommand::DelayedCallCommand(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,  // takes ownership
                                          unsigned int timeoutInMs,
                                          Orthanc::IDynamicObject* payload /* takes ownership */,
                                          OrthancStone::NativeStoneApplicationContext& context
                                          ) :
-    IObservable(broker),
     callback_(callback),
     payload_(payload),
     context_(context),
--- a/Platforms/Generic/DelayedCallCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/DelayedCallCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -42,8 +42,7 @@
     unsigned int                            timeoutInMs_;
 
   public:
-    DelayedCallCommand(OrthancStone::MessageBroker& broker,
-                       OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,  // takes ownership
+    DelayedCallCommand(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,  // takes ownership
                        unsigned int timeoutInMs,
                        Orthanc::IDynamicObject* payload /* takes ownership */,
                        OrthancStone::NativeStoneApplicationContext& context
--- a/Platforms/Generic/OracleDelayedCallExecutor.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/OracleDelayedCallExecutor.h	Fri Nov 29 11:03:41 2019 +0100
@@ -36,10 +36,8 @@
     OrthancStone::NativeStoneApplicationContext& context_;
 
   public:
-    OracleDelayedCallExecutor(OrthancStone::MessageBroker& broker,
-                              Oracle& oracle,
+    OracleDelayedCallExecutor(Oracle& oracle,
                               OrthancStone::NativeStoneApplicationContext& context) :
-      IDelayedCallExecutor(broker),
       oracle_(oracle),
       context_(context)
     {
@@ -48,7 +46,7 @@
     virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,
                           unsigned int timeoutInMs = 1000)
     {
-      oracle_.Submit(new DelayedCallCommand(broker_, callback, timeoutInMs, NULL, context_));
+      oracle_.Submit(new DelayedCallCommand(callback, timeoutInMs, NULL, context_));
     }
   };
 }
--- a/Platforms/Generic/OracleWebService.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/OracleWebService.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -35,13 +35,11 @@
     OrthancStone::NativeStoneApplicationContext&                                          context_;
 
   public:
-    WebServiceCachedGetCommand(OrthancStone::MessageBroker& broker,
-                               OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+    WebServiceCachedGetCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                                boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedMessage,
                                Orthanc::IDynamicObject* payload /* takes ownership */,
                                OrthancStone::NativeStoneApplicationContext& context
                                ) :
-      IObservable(broker),
       successCallback_(successCallback),
       payload_(payload),
       cachedMessage_(cachedMessage),
@@ -75,7 +73,7 @@
                                                 Orthanc::IDynamicObject* payload, // takes ownership
                                                 OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback)
   {
-    oracle_.Submit(new WebServiceCachedGetCommand(GetBroker(), successCallback, cachedMessage, payload, context_));
+    oracle_.Submit(new WebServiceCachedGetCommand(successCallback, cachedMessage, payload, context_));
   }
 
 
--- a/Platforms/Generic/OracleWebService.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/OracleWebService.h	Fri Nov 29 11:03:41 2019 +0100
@@ -43,11 +43,9 @@
     class WebServiceCachedGetCommand;
 
   public:
-    OracleWebService(OrthancStone::MessageBroker& broker,
-                     Oracle& oracle,
+    OracleWebService(Oracle& oracle,
                      const Orthanc::WebServiceParameters& parameters,
                      OrthancStone::NativeStoneApplicationContext& context) :
-      BaseWebService(broker),
       oracle_(oracle),
       context_(context),
       parameters_(parameters)
@@ -62,7 +60,7 @@
                            OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, // takes ownership
                            unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServicePostCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_));
+      oracle_.Submit(new WebServicePostCommand(successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_));
     }
 
     virtual void DeleteAsync(const std::string& uri,
@@ -72,7 +70,7 @@
                              OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
                              unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServiceDeleteCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
+      oracle_.Submit(new WebServiceDeleteCommand(successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
     }
 
   protected:
@@ -83,7 +81,7 @@
                                   OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,// takes ownership
                                   unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServiceGetCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
+      oracle_.Submit(new WebServiceGetCommand(successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
     }
 
     virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage,
--- a/Platforms/Generic/WebServiceCommandBase.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -25,8 +25,7 @@
 
 namespace Deprecated
 {
-  WebServiceCommandBase::WebServiceCommandBase(OrthancStone::MessageBroker& broker,
-                                               OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+  WebServiceCommandBase::WebServiceCommandBase(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
                                                OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& url,
@@ -34,7 +33,6 @@
                                                unsigned int timeoutInSeconds,
                                                Orthanc::IDynamicObject* payload /* takes ownership */,
                                                OrthancStone::NativeStoneApplicationContext& context) :
-    IObservable(broker),
     successCallback_(successCallback),
     failureCallback_(failureCallback),
     parameters_(parameters),
--- a/Platforms/Generic/WebServiceCommandBase.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServiceCommandBase.h	Fri Nov 29 11:03:41 2019 +0100
@@ -51,8 +51,7 @@
     unsigned int                            timeoutInSeconds_;
 
   public:
-    WebServiceCommandBase(OrthancStone::MessageBroker& broker,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+    WebServiceCommandBase(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                           OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& url,
--- a/Platforms/Generic/WebServiceDeleteCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServiceDeleteCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -25,8 +25,7 @@
 
 namespace Deprecated
 {
-  WebServiceDeleteCommand::WebServiceDeleteCommand(OrthancStone::MessageBroker& broker,
-                                                   OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+  WebServiceDeleteCommand::WebServiceDeleteCommand(OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                                                    OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                                                    const Orthanc::WebServiceParameters& parameters,
                                                    const std::string& url,
@@ -34,7 +33,7 @@
                                                    unsigned int timeoutInSeconds,
                                                    Orthanc::IDynamicObject* payload /* takes ownership */,
                                                    OrthancStone::NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context)
+    WebServiceCommandBase(successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context)
   {
   }
 
--- a/Platforms/Generic/WebServiceDeleteCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServiceDeleteCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,8 +28,7 @@
   class WebServiceDeleteCommand : public WebServiceCommandBase
   {
   public:
-    WebServiceDeleteCommand(OrthancStone::MessageBroker& broker,
-                            OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+    WebServiceDeleteCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                             OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                             const Orthanc::WebServiceParameters& parameters,
                             const std::string& url,
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -25,9 +25,7 @@
 
 namespace Deprecated
 {
-
-  WebServiceGetCommand::WebServiceGetCommand(OrthancStone::MessageBroker& broker,
-                                             OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+  WebServiceGetCommand::WebServiceGetCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                                              OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& url,
@@ -35,7 +33,7 @@
                                              unsigned int timeoutInSeconds,
                                              Orthanc::IDynamicObject* payload /* takes ownership */,
                                              OrthancStone::NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context)
+    WebServiceCommandBase(successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context)
   {
   }
 
--- a/Platforms/Generic/WebServiceGetCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServiceGetCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -28,8 +28,7 @@
   class WebServiceGetCommand : public WebServiceCommandBase
   {
   public:
-    WebServiceGetCommand(OrthancStone::MessageBroker& broker,
-                         OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+    WebServiceGetCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& url,
--- a/Platforms/Generic/WebServicePostCommand.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -25,8 +25,7 @@
 
 namespace Deprecated
 {
-  WebServicePostCommand::WebServicePostCommand(OrthancStone::MessageBroker& broker,
-                                               OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+  WebServicePostCommand::WebServicePostCommand(OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                                                OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& url,
@@ -35,7 +34,7 @@
                                                const std::string& body,
                                                Orthanc::IDynamicObject* payload /* takes ownership */,
                                                OrthancStone::NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context),
+    WebServiceCommandBase(successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context),
     body_(body)
   {
   }
--- a/Platforms/Generic/WebServicePostCommand.h	Thu Nov 28 18:28:15 2019 +0100
+++ b/Platforms/Generic/WebServicePostCommand.h	Fri Nov 29 11:03:41 2019 +0100
@@ -31,8 +31,7 @@
     std::string  body_;
 
   public:
-    WebServicePostCommand(OrthancStone::MessageBroker& broker,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+    WebServicePostCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
                           OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& url,
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Nov 28 18:28:15 2019 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Nov 29 11:03:41 2019 +0100
@@ -250,10 +250,6 @@
 ## All the source files required to build Stone of Orthanc
 #####################################################################
 
-set(APPLICATIONS_SOURCES
-  ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h
-  )
-
 if (NOT ORTHANC_SANDBOXED)
   set(PLATFORM_SOURCES
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp
@@ -265,46 +261,21 @@
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h
     )
 
-  if (ENABLE_STONE_DEPRECATED)
-    list(APPEND PLATFORM_SOURCES
-      ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.cpp
-      ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp
+  if (ENABLE_SDL)
+    list(APPEND ORTHANC_STONE_SOURCES
+      ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlWindow.cpp
       )
   endif()
 
   if (ENABLE_SDL OR ENABLE_QT)
-    if (ENABLE_STONE_DEPRECATED)
-      list(APPEND APPLICATIONS_SOURCES
-        ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp
-        ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationContext.cpp
-        )
-    endif()
-      
-    if (ENABLE_SDL)
+    if (ENABLE_OPENGL)
       list(APPEND ORTHANC_STONE_SOURCES
-        ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlWindow.cpp
+        ${ORTHANC_STONE_ROOT}/Framework/OpenGL/SdlOpenGLContext.cpp
+        ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlViewport.cpp
         )
-
-      list(APPEND APPLICATIONS_SOURCES
-        ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp
-        ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp
-        ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp
-        ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp
-        )
-
-      if (ENABLE_OPENGL)
-        list(APPEND ORTHANC_STONE_SOURCES
-          ${ORTHANC_STONE_ROOT}/Framework/OpenGL/SdlOpenGLContext.cpp
-          ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlViewport.cpp
-          )
-      endif()
     endif()
   endif()
 elseif (ENABLE_WASM)
-  list(APPEND APPLICATIONS_SOURCES
-    ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp
-    )
-
   set(STONE_WASM_SOURCES
     ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmDelayedCallExecutor.cpp
@@ -331,15 +302,45 @@
     DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js")
 endif()
 
-if (ENABLE_SDL OR ENABLE_WASM)
-  list(APPEND APPLICATIONS_SOURCES
-    ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.cpp
-    ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.h
-    )
-endif()
+if (ENABLE_STONE_DEPRECATED)
+  if (NOT ORTHANC_SANDBOXED)
+    list(APPEND PLATFORM_SOURCES
+      ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.cpp
+      ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp
+      )
+  endif()
+
+  if (ENABLE_SDL OR ENABLE_WASM)
+    list(APPEND APPLICATIONS_SOURCES
+      ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.h
+      )
+  endif()
 
-if (ENABLE_STONE_DEPRECATED)
+  if (ENABLE_SDL OR ENABLE_QT)
+    list(APPEND APPLICATIONS_SOURCES
+      ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationContext.cpp
+      )
+  endif()
+
+  if (ENABLE_SDL)
+    list(APPEND APPLICATIONS_SOURCES
+      ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp
+      ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp
+      )
+  endif()
+
+  if (ENABLE_WASM)
+    list(APPEND APPLICATIONS_SOURCES
+      ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp
+      )
+  endif()
+
   list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h
     ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/CircleMeasureTracker.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ColorFrameRenderer.cpp
@@ -405,10 +406,20 @@
 endif()
 
 
+if (ENABLE_DCMTK)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomSuccessMessage.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParsedDicomCache.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParsedDicomDataset.cpp
+    )
+endif()
+
 if (ENABLE_THREADS)
   list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h
     ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/GenericOracleRunner.cpp
     )
 endif()
 
@@ -459,15 +470,14 @@
   ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancImageCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandWithPayload.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/HttpCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Oracle/OrthancRestApiCommand.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Oracle/HttpCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromFileCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromWadoCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp
@@ -564,6 +574,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.h
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.h
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.h
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.h
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp
@@ -580,8 +592,6 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/TextRenderer.h
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.h
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.h
   
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/ViewportBase.h
--- a/Resources/CMake/QtConfiguration.cmake	Thu Nov 28 18:28:15 2019 +0100
+++ b/Resources/CMake/QtConfiguration.cmake	Fri Nov 29 11:03:41 2019 +0100
@@ -20,26 +20,12 @@
 set(CMAKE_AUTOMOC OFF)
 set(CMAKE_AUTOUIC OFF)
 
-if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
-  # Linux Standard Base version 5 ships Qt 4.2.3
+
+## Note that these set of macros MUST be defined as a "function()",
+## otherwise it fails
+function(DEFINE_QT_MACROS)
   include(Qt4Macros)
 
-  # The script "LinuxStandardBaseUic.py" is just a wrapper around the
-  # "uic" compiler from LSB that does not support the "<?xml ...?>"
-  # header that is automatically added by Qt Creator
-  set(QT_UIC_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/LinuxStandardBaseUic.py)
-
-  set(QT_MOC_EXECUTABLE ${LSB_PATH}/bin/moc)
-
-  include_directories(
-    ${LSB_PATH}/include/QtCore
-    ${LSB_PATH}/include/QtGui
-    ${LSB_PATH}/include/QtOpenGL
-    )
-
-  link_libraries(QtCore QtGui QtOpenGL)
-
-
   ##
   ## This part is adapted from file "Qt4Macros.cmake" shipped with
   ## CMake 3.5.1, released under the following license:
@@ -86,9 +72,91 @@
   ##
   ## End of "Qt4Macros.cmake" adaptation.
   ##
+endfunction()
 
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # Linux Standard Base version 5 ships Qt 4.2.3
+  DEFINE_QT_MACROS()
+ 
+  # The script "LinuxStandardBaseUic.py" is just a wrapper around the
+  # "uic" compiler from LSB that does not support the "<?xml ...?>"
+  # header that is automatically added by Qt Creator
+  set(QT_UIC_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/LinuxStandardBaseUic.py)
+
+  set(QT_MOC_EXECUTABLE ${LSB_PATH}/bin/moc)
+
+  include_directories(
+    ${LSB_PATH}/include/QtCore
+    ${LSB_PATH}/include/QtGui
+    ${LSB_PATH}/include/QtOpenGL
+    )
+
+  link_libraries(QtCore QtGui QtOpenGL)
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  DEFINE_QT_MACROS()
+  
+  include_directories(${QT5_INSTALL_ROOT}/include)
+  link_directories(${QT5_INSTALL_ROOT}/lib)
+
+  if (OFF) #CMAKE_CROSSCOMPILING)
+    set(QT_UIC_EXECUTABLE wine ${QT5_INSTALL_ROOT}/bin/uic.exe)
+    set(QT_MOC_EXECUTABLE wine ${QT5_INSTALL_ROOT}/bin/moc.exe)
+  else()
+    set(QT_UIC_EXECUTABLE ${QT5_INSTALL_ROOT}/bin/uic)
+    set(QT_MOC_EXECUTABLE ${QT5_INSTALL_ROOT}/bin/moc)
+  endif()
+
+  include_directories(
+    ${QT5_INSTALL_ROOT}/include/QtCore
+    ${QT5_INSTALL_ROOT}/include/QtGui
+    ${QT5_INSTALL_ROOT}/include/QtOpenGL
+    ${QT5_INSTALL_ROOT}/include/QtWidgets
+    )
+
+  if (OFF)
+    # Dynamic Qt
+    link_libraries(Qt5Core Qt5Gui Qt5OpenGL Qt5Widgets)
+
+    file(COPY
+      ${QT5_INSTALL_ROOT}/bin/Qt5Core.dll
+      ${QT5_INSTALL_ROOT}/bin/Qt5Gui.dll
+      ${QT5_INSTALL_ROOT}/bin/Qt5OpenGL.dll
+      ${QT5_INSTALL_ROOT}/bin/Qt5Widgets.dll
+      ${QT5_INSTALL_ROOT}/bin/libstdc++-6.dll
+      ${QT5_INSTALL_ROOT}/bin/libgcc_s_dw2-1.dll
+      ${QT5_INSTALL_ROOT}/bin/libwinpthread-1.dll
+      DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+    file(COPY
+      ${QT5_INSTALL_ROOT}/plugins/platforms/qwindows.dll
+      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/platforms)
+
+  else()
+    # Static Qt
+    link_libraries(
+      ${QT5_INSTALL_ROOT}/lib/libQt5Widgets.a
+      ${QT5_INSTALL_ROOT}/lib/libQt5Gui.a
+      ${QT5_INSTALL_ROOT}/lib/libQt5OpenGL.a
+      ${QT5_INSTALL_ROOT}/lib/libQt5Core.a
+      ${QT5_INSTALL_ROOT}/lib/libqtharfbuzz.a
+      ${QT5_INSTALL_ROOT}/lib/libqtpcre2.a
+      ${QT5_INSTALL_ROOT}/lib/libQt5FontDatabaseSupport.a
+      ${QT5_INSTALL_ROOT}/lib/libQt5EventDispatcherSupport.a
+      ${QT5_INSTALL_ROOT}/lib/libQt5ThemeSupport.a
+      ${QT5_INSTALL_ROOT}/plugins/platforms/libqwindows.a
+      winmm
+      version
+      ws2_32
+      uxtheme
+      imm32
+      dwmapi
+      )
+  endif()
+  
 else()
-  # Not using Linux Standard Base
+  # Not using Windows, not using Linux Standard Base, 
   # Find the QtWidgets library
   find_package(Qt5Widgets QUIET)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Conventions.txt	Fri Nov 29 11:03:41 2019 +0100
@@ -0,0 +1,87 @@
+
+Some notes about the lifetime of objects
+========================================
+
+Stone applications
+------------------
+
+A typical Stone application can be split in 3 parts:
+
+1- The "loaders part" and the associated "IOracle", that communicate
+   through "IMessage" objects. The lifetime of these objects is
+   governed by the "IStoneContext".
+
+2- The "data part" holds the data loaded by the "loaders part". The
+   related objects must not be aware of the oracle, neither of the
+   messages. It is up to the user application to store these objects.
+
+3- The "viewport part" is based upon the "Scene2D" class.
+
+
+Multithreading
+--------------
+
+* Stone makes the hypothesis that its objects live in a single thread.
+  All the content of the "Framework" folder (with the exception of
+  the "Oracle" stuff) must not use "boost::thread".
+
+* The "IOracleCommand" classes represent commands that must be
+  executed asynchronously from the Stone thread. Their actual
+  execution is done by the "IOracle".
+
+* In WebAssembly, the "IOracle" corresponds to the "html5.h"
+  facilities (notably for the Fetch API). There is no mutex here, as
+  JavaScript is inherently single-threaded.
+
+* In plain C++ applications, the "IOracle" corresponds to a FIFO queue
+  of commands that are executed by a pool of threads. The Stone
+  context holds a global mutex, that must be properly locked by the
+  user application, and by the "IOracle" when it sends back messages
+  to the Stone loaders (cf. class "IMessageEmitter").
+
+* Multithreading is thus achieved by defining new oracle commands by
+  subclassing "IOracleCommand", then by defining a way to execute them
+  (cf. class "GenericCommandRunner").
+
+
+References between objects
+--------------------------
+
+* An object allocated on the heap must never store a reference/pointer
+  to another object.
+
+* A class designed to be allocated only on the stack can store a
+  reference/pointer to another object. Here is the list of
+  such classes:
+
+  - IMessage and its derived classes: All the messages are allocated
+    on the stack.
+
+
+Pointers
+--------
+
+* As we are targeting C++03 (for VS2008 and LSB compatibility), use
+  "std::auto_ptr<>" and "boost::shared_ptr<>" (*not*
+  "std::shared_ptr<>").
+
+* The fact of transfering the ownership of one object to another must
+  be tagged by naming the method "Acquire...()", and by providing a
+  raw pointer.
+
+* Use "std::auto_ptr<>" if the goal is to internally store a pointer
+  whose lifetime corresponds to the host object.
+
+* The use of "boost::weak_ptr<>" should be restricted to
+  oracle/message handling.
+
+* The use of "boost::shared_ptr<>" should be minimized to avoid
+  clutter. The "loaders" and "data parts" objects must however
+  be created as "boost::shared_ptr<>".
+
+
+Global context
+--------------
+
+* As the global Stone context can be created/destroyed by other
+  languages than C++, we don't use a "boost:shared_ptr<>".
--- a/UnitTestsSources/GenericToolboxTests.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/UnitTestsSources/GenericToolboxTests.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -20,7 +20,7 @@
 
 #include <Framework/Toolbox/GenericToolbox.h>
 
-#include <boost/chrono.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/lexical_cast.hpp>
 
 #include "gtest/gtest.h"
@@ -3878,19 +3878,17 @@
     bool ok = true;
 
     {
-      boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now();
+      boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
       for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i)
       {
         ok = StringToDouble(r, txt);
       }
-      boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now();
-      boost::chrono::microseconds elapsed =
-        boost::chrono::duration_cast<boost::chrono::microseconds>(end - start);
-      total_us_StringToDouble += elapsed.count();
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      total_us_StringToDouble += (end - start).total_microseconds();
     }
 
     {
-      boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now();
+      boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
       for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i)
       {
         try
@@ -3903,10 +3901,8 @@
           ok = false;
         }
       }
-      boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now();
-      boost::chrono::microseconds elapsed =
-        boost::chrono::duration_cast<boost::chrono::microseconds>(end - start);
-      total_us_lexical_cast += elapsed.count();
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      total_us_lexical_cast += (end - start).total_microseconds();
     }
     numConversions += NUM_TIMINGS_CONVS;
 
@@ -4095,19 +4091,17 @@
     bool ok = true;
 
     {
-      boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now();
+      boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
       for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i)
       {
         ok = StringToDouble(r, txt);
       }
-      boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now();
-      boost::chrono::microseconds elapsed =
-        boost::chrono::duration_cast<boost::chrono::microseconds>(end - start);
-      total_us_StringToDouble += elapsed.count();
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      total_us_StringToDouble += (end - start).total_microseconds();
     }
 
     {
-      boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now();
+      boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
       for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i)
       {
         try
@@ -4120,10 +4114,8 @@
           ok = false;
         }
       }
-      boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now();
-      boost::chrono::microseconds elapsed =
-        boost::chrono::duration_cast<boost::chrono::microseconds>(end - start);
-      total_us_lexical_cast += elapsed.count();
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      total_us_lexical_cast += (end - start).total_microseconds();
     }
     numConversions += NUM_TIMINGS_CONVS;
 
--- a/UnitTestsSources/TestMessageBroker.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/UnitTestsSources/TestMessageBroker.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -21,10 +21,8 @@
 
 #include "gtest/gtest.h"
 
-#include "../Framework/Messages/MessageBroker.h"
-#include "../Framework/Messages/IObservable.h"
-#include "../Framework/Messages/IObserver.h"
-#include "../Framework/Messages/MessageForwarder.h"
+#include "Framework/Messages/IObservable.h"
+#include "Framework/Messages/ObserverBase.h"
 
 
 int testCounter = 0;
@@ -47,51 +45,26 @@
       {
       }
     };
-
-    MyObservable(MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
   };
 
-  class MyObserver : public IObserver
+  class MyObserver : public ObserverBase<MyObserver>
   {
   public:
-    MyObserver(MessageBroker& broker)
-      : IObserver(broker)
-    {}
-
     void HandleCompletedMessage(const MyObservable::MyCustomMessage& message)
     {
       testCounter += message.payload_;
     }
-
-  };
-
-
-  class MyIntermediate : public IObserver, public IObservable
-  {
-    IObservable& observedObject_;
-  public:
-    MyIntermediate(MessageBroker& broker, IObservable& observedObject)
-      : IObserver(broker),
-        IObservable(broker),
-        observedObject_(observedObject)
-    {
-      observedObject_.RegisterObserverCallback(new MessageForwarder<MyObservable::MyCustomMessage>(broker, *this));
-    }
   };
 }
 
 
 TEST(MessageBroker, TestPermanentConnectionSimpleUseCase)
 {
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver    observer(broker);
+  MyObservable  observable;
+  boost::shared_ptr<MyObserver>  observer(new MyObserver);
 
   // create a permanent connection between an observable and an observer
-  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+  observer->Register<MyObservable::MyCustomMessage>(observable, &MyObserver::HandleCompletedMessage);
 
   testCounter = 0;
   observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
@@ -103,155 +76,29 @@
   ASSERT_EQ(20, testCounter);
 
   // Unregister the observer; make sure it's not called anymore
-  observable.Unregister(&observer);
+  observer.reset();
   testCounter = 0;
   observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
   ASSERT_EQ(0, testCounter);
 }
 
-TEST(MessageBroker, TestMessageForwarderSimpleUseCase)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyIntermediate intermediate(broker, observable);
-  MyObserver    observer(broker);
-
-  // let the observer observers the intermediate that is actually forwarding the messages from the observable
-  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(20, testCounter);
-}
-
 TEST(MessageBroker, TestPermanentConnectionDeleteObserver)
 {
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver*   observer = new MyObserver(broker);
+  MyObservable  observable;
+  boost::shared_ptr<MyObserver>  observer(new MyObserver);
 
   // create a permanent connection between an observable and an observer
-  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage));
+  observer->Register<MyObservable::MyCustomMessage>(observable, &MyObserver::HandleCompletedMessage);
 
   testCounter = 0;
   observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
   ASSERT_EQ(12, testCounter);
 
   // delete the observer and check that the callback is not called anymore
-  delete observer;
+  observer.reset();
 
   // the connection is permanent; if we emit the same message again, the observer will be notified again
   testCounter = 0;
   observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
   ASSERT_EQ(0, testCounter);
 }
-
-TEST(MessageBroker, TestMessageForwarderDeleteIntermediate)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyIntermediate* intermediate = new MyIntermediate(broker, observable);
-  MyObserver    observer(broker);
-
-  // let the observer observers the intermediate that is actually forwarding the messages from the observable
-  intermediate->RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  delete intermediate;
-
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(12, testCounter);
-}
-
-TEST(MessageBroker, TestCustomMessage)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyIntermediate intermediate(broker, observable);
-  MyObserver    observer(broker);
-
-  // let the observer observers the intermediate that is actually forwarding the messages from the observable
-  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(20, testCounter);
-}
-
-
-#if 0 /* __cplusplus >= 201103L*/
-
-TEST(MessageBroker, TestLambdaSimpleUseCase)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver*   observer = new MyObserver(broker);
-
-  // create a permanent connection between an observable and an observer
-  observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;}));
-
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(24, testCounter);
-
-  // delete the observer and check that the callback is not called anymore
-  delete observer;
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(0, testCounter);
-}
-
-namespace {
-  class MyObserverWithLambda : public IObserver {
-  private:
-    int multiplier_;  // this is a private variable we want to access in a lambda
-
-  public:
-    MyObserverWithLambda(MessageBroker& broker, int multiplier, MyObservable& observable)
-      : IObserver(broker),
-        multiplier_(multiplier)
-    {
-      // register a callable to a lambda that access private members
-      observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*this, [this](const MyObservable::MyCustomMessage& message) {
-        testCounter += multiplier_ * message.payload_;
-      }));
-
-    }
-  };
-}
-
-TEST(MessageBroker, TestLambdaCaptureThisAndAccessPrivateMembers)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserverWithLambda*   observer = new MyObserverWithLambda(broker, 3, observable);
-
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(36, testCounter);
-
-  // delete the observer and check that the callback is not called anymore
-  delete observer;
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.BroadcastMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(0, testCounter);
-}
-
-#endif // C++ 11
--- a/UnitTestsSources/UnitTestsMain.cpp	Thu Nov 28 18:28:15 2019 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Fri Nov 29 11:03:41 2019 +0100
@@ -25,6 +25,7 @@
 #include "../Framework/Deprecated/Toolbox/DownloadStack.h"
 #include "../Framework/Deprecated/Toolbox/MessagingToolbox.h"
 #include "../Framework/Deprecated/Toolbox/OrthancSlicesLoader.h"
+#include "../Framework/StoneInitialization.h"
 #include "../Framework/Toolbox/FiniteProjectiveCamera.h"
 #include "../Framework/Toolbox/GeometryToolbox.h"
 #include "../Framework/Volumes/ImageBuffer3D.h"
@@ -566,20 +567,20 @@
 }
 
 
-static bool IsEqualVector(OrthancStone::Vector a,
-                          OrthancStone::Vector b)
+static bool IsEqualRotationVector(OrthancStone::Vector a,
+                                  OrthancStone::Vector b)
 {
-  if (a.size() == 3 &&
-      b.size() == 3)
+  if (a.size() != b.size() ||
+      a.size() != 3)
+  {
+    return false;
+  }
+  else
   {
     OrthancStone::LinearAlgebra::NormalizeVector(a);
     OrthancStone::LinearAlgebra::NormalizeVector(b);
     return OrthancStone::LinearAlgebra::IsCloseToZero(boost::numeric::ublas::norm_2(a - b));
   }
-  else
-  {
-    return false;
-  } 
 }
 
 
@@ -593,29 +594,29 @@
 
   OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
   ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
-  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+  ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b));
 
   OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, b, a);
   ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
-  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, b), a));
+  ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, b), a));
 
   OrthancStone::LinearAlgebra::AssignVector(a, 1, 0, 0);
   OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
   OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
   ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
-  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+  ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b));
 
   OrthancStone::LinearAlgebra::AssignVector(a, 0, 1, 0);
   OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
   OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
   ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
-  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+  ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b));
 
   OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, 1);
   OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
   OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
   ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
-  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+  ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b));
 
   OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, 0);
   OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
@@ -624,11 +625,11 @@
   // TODO: Deal with opposite vectors
 
   /*
-  OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, -1);
-  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
-  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
-  OrthancStone::LinearAlgebra::Print(r);
-  OrthancStone::LinearAlgebra::Print(boost::numeric::ublas::prod(r, a));
+    OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, -1);
+    OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+    OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
+    OrthancStone::LinearAlgebra::Print(r);
+    OrthancStone::LinearAlgebra::Print(boost::numeric::ublas::prod(r, a));
   */
 }
 
@@ -639,13 +640,39 @@
   ASSERT_TRUE(Deprecated::MessagingToolbox::ParseJson(response, source.c_str(), source.size()));
 }
 
+
+
+static bool IsEqualVectorL1(OrthancStone::Vector a,
+                            OrthancStone::Vector b)
+{
+  if (a.size() != b.size())
+  {
+    return false;
+  }
+  else
+  {
+    for (size_t i = 0; i < a.size(); i++)
+    {
+      if (!OrthancStone::LinearAlgebra::IsNear(a[i], b[i], 0.0001))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
+
+
 TEST(VolumeImageGeometry, Basic)
 {
-  OrthancStone::VolumeImageGeometry g;
+  using namespace OrthancStone;
+  
+  VolumeImageGeometry g;
   g.SetSizeInVoxels(10, 20, 30);
   g.SetVoxelDimensions(1, 2, 3);
 
-  OrthancStone::Vector p = g.GetCoordinates(0, 0, 0);
+  Vector p = g.GetCoordinates(0, 0, 0);
   ASSERT_EQ(3u, p.size());
   ASSERT_DOUBLE_EQ(-1.0 / 2.0, p[0]);
   ASSERT_DOUBLE_EQ(-2.0 / 2.0, p[1]);
@@ -656,69 +683,148 @@
   ASSERT_DOUBLE_EQ(-2.0 / 2.0 + 20.0 * 2.0, p[1]);
   ASSERT_DOUBLE_EQ(-3.0 / 2.0 + 30.0 * 3.0, p[2]);
 
-  OrthancStone::VolumeProjection proj;
+  VolumeProjection proj;
   ASSERT_TRUE(g.DetectProjection(proj, g.GetAxialGeometry().GetNormal()));
-  ASSERT_EQ(OrthancStone::VolumeProjection_Axial, proj);
+  ASSERT_EQ(VolumeProjection_Axial, proj);
   ASSERT_TRUE(g.DetectProjection(proj, g.GetCoronalGeometry().GetNormal()));
-  ASSERT_EQ(OrthancStone::VolumeProjection_Coronal, proj);
+  ASSERT_EQ(VolumeProjection_Coronal, proj);
   ASSERT_TRUE(g.DetectProjection(proj, g.GetSagittalGeometry().GetNormal()));
-  ASSERT_EQ(OrthancStone::VolumeProjection_Sagittal, proj);
+  ASSERT_EQ(VolumeProjection_Sagittal, proj);
 
-  ASSERT_EQ(10u, g.GetProjectionWidth(OrthancStone::VolumeProjection_Axial));
-  ASSERT_EQ(20u, g.GetProjectionHeight(OrthancStone::VolumeProjection_Axial));
-  ASSERT_EQ(30u, g.GetProjectionDepth(OrthancStone::VolumeProjection_Axial));
-  ASSERT_EQ(10u, g.GetProjectionWidth(OrthancStone::VolumeProjection_Coronal));
-  ASSERT_EQ(30u, g.GetProjectionHeight(OrthancStone::VolumeProjection_Coronal));
-  ASSERT_EQ(20u, g.GetProjectionDepth(OrthancStone::VolumeProjection_Coronal));
-  ASSERT_EQ(20u, g.GetProjectionWidth(OrthancStone::VolumeProjection_Sagittal));
-  ASSERT_EQ(30u, g.GetProjectionHeight(OrthancStone::VolumeProjection_Sagittal));
-  ASSERT_EQ(10u, g.GetProjectionDepth(OrthancStone::VolumeProjection_Sagittal));
+  ASSERT_EQ(10u, g.GetProjectionWidth(VolumeProjection_Axial));
+  ASSERT_EQ(20u, g.GetProjectionHeight(VolumeProjection_Axial));
+  ASSERT_EQ(30u, g.GetProjectionDepth(VolumeProjection_Axial));
+  ASSERT_EQ(10u, g.GetProjectionWidth(VolumeProjection_Coronal));
+  ASSERT_EQ(30u, g.GetProjectionHeight(VolumeProjection_Coronal));
+  ASSERT_EQ(20u, g.GetProjectionDepth(VolumeProjection_Coronal));
+  ASSERT_EQ(20u, g.GetProjectionWidth(VolumeProjection_Sagittal));
+  ASSERT_EQ(30u, g.GetProjectionHeight(VolumeProjection_Sagittal));
+  ASSERT_EQ(10u, g.GetProjectionDepth(VolumeProjection_Sagittal));
 
-  p = g.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
+  p = g.GetVoxelDimensions(VolumeProjection_Axial);
   ASSERT_EQ(3u, p.size());
   ASSERT_DOUBLE_EQ(1, p[0]);
   ASSERT_DOUBLE_EQ(2, p[1]);
   ASSERT_DOUBLE_EQ(3, p[2]);
-  p = g.GetVoxelDimensions(OrthancStone::VolumeProjection_Coronal);
+  p = g.GetVoxelDimensions(VolumeProjection_Coronal);
   ASSERT_EQ(3u, p.size());
   ASSERT_DOUBLE_EQ(1, p[0]);
   ASSERT_DOUBLE_EQ(3, p[1]);
   ASSERT_DOUBLE_EQ(2, p[2]);
-  p = g.GetVoxelDimensions(OrthancStone::VolumeProjection_Sagittal);
+  p = g.GetVoxelDimensions(VolumeProjection_Sagittal);
   ASSERT_EQ(3u, p.size());
   ASSERT_DOUBLE_EQ(2, p[0]);
   ASSERT_DOUBLE_EQ(3, p[1]);
   ASSERT_DOUBLE_EQ(1, p[2]);
 
-  ASSERT_EQ(0, (int) OrthancStone::VolumeProjection_Axial);
-  ASSERT_EQ(1, (int) OrthancStone::VolumeProjection_Coronal);
-  ASSERT_EQ(2, (int) OrthancStone::VolumeProjection_Sagittal);
+  // Loop over all the voxels of the volume
+  for (unsigned int z = 0; z < g.GetDepth(); z++)
+  {
+    const float zz = (0.5f + static_cast<float>(z)) / static_cast<float>(g.GetDepth());  // Z-center of the voxel
+    
+    for (unsigned int y = 0; y < g.GetHeight(); y++)
+    {
+      const float yy = (0.5f + static_cast<float>(y)) / static_cast<float>(g.GetHeight());  // Y-center of the voxel
+
+      for (unsigned int x = 0; x < g.GetWidth(); x++)
+      {
+        const float xx = (0.5f + static_cast<float>(x)) / static_cast<float>(g.GetWidth());  // X-center of the voxel
+
+        const float sx = 1.0f;
+        const float sy = 2.0f;
+        const float sz = 3.0f;
+        
+        Vector p = g.GetCoordinates(xx, yy, zz);
+
+        Vector q = (g.GetAxialGeometry().MapSliceToWorldCoordinates(
+                      static_cast<double>(x) * sx,
+                      static_cast<double>(y) * sy) +
+                    z * sz * g.GetAxialGeometry().GetNormal());
+        ASSERT_TRUE(IsEqualVectorL1(p, q));
+        
+        q = (g.GetCoronalGeometry().MapSliceToWorldCoordinates(
+               static_cast<double>(x) * sx,
+               static_cast<double>(g.GetDepth() - 1 - z) * sz) +
+             y * sy * g.GetCoronalGeometry().GetNormal());
+        ASSERT_TRUE(IsEqualVectorL1(p, q));
+
+        /**
+         * WARNING: In sagittal geometry, the normal points to
+         * REDUCING X-axis in the 3D world. This is necessary to keep
+         * the right-hand coordinate system. Hence the "-".
+         **/
+        q = (g.GetSagittalGeometry().MapSliceToWorldCoordinates(
+               static_cast<double>(y) * sy,
+               static_cast<double>(g.GetDepth() - 1 - z) * sz) +
+             x * sx * (-g.GetSagittalGeometry().GetNormal()));
+        ASSERT_TRUE(IsEqualVectorL1(p, q));
+      }
+    }
+  }
+
+  ASSERT_EQ(0, (int) VolumeProjection_Axial);
+  ASSERT_EQ(1, (int) VolumeProjection_Coronal);
+  ASSERT_EQ(2, (int) VolumeProjection_Sagittal);
   
   for (int p = 0; p < 3; p++)
   {
-    OrthancStone::VolumeProjection projection = (OrthancStone::VolumeProjection) p;
-    const OrthancStone::CoordinateSystem3D& s = g.GetProjectionGeometry(projection);
+    VolumeProjection projection = (VolumeProjection) p;
+    const CoordinateSystem3D& s = g.GetProjectionGeometry(projection);
     
     ASSERT_THROW(g.GetProjectionSlice(projection, g.GetProjectionDepth(projection)), Orthanc::OrthancException);
 
     for (unsigned int i = 0; i < g.GetProjectionDepth(projection); i++)
     {
-      OrthancStone::CoordinateSystem3D plane = g.GetProjectionSlice(projection, i);
+      CoordinateSystem3D plane = g.GetProjectionSlice(projection, i);
 
-      ASSERT_TRUE(IsEqualVector(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(i) * 
-                                s.GetNormal() * g.GetVoxelDimensions(projection)[2]));
-      ASSERT_TRUE(IsEqualVector(plane.GetAxisX(), s.GetAxisX()));
-      ASSERT_TRUE(IsEqualVector(plane.GetAxisY(), s.GetAxisY()));
+      if (projection == VolumeProjection_Sagittal)
+      {
+        ASSERT_TRUE(IsEqualVectorL1(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(i) * 
+                                    (-s.GetNormal()) * g.GetVoxelDimensions(projection)[2]));
+      }
+      else
+      {
+        ASSERT_TRUE(IsEqualVectorL1(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(i) * 
+                                    s.GetNormal() * g.GetVoxelDimensions(projection)[2]));
+      }
+      
+      ASSERT_TRUE(IsEqualVectorL1(plane.GetAxisX(), s.GetAxisX()));
+      ASSERT_TRUE(IsEqualVectorL1(plane.GetAxisY(), s.GetAxisY()));
 
       unsigned int slice;
-      OrthancStone::VolumeProjection q;
+      VolumeProjection q;
       ASSERT_TRUE(g.DetectSlice(q, slice, plane));
       ASSERT_EQ(projection, q);
-      ASSERT_EQ(i, slice);     
+      ASSERT_EQ(i, slice);
     }
   }
 }
 
+
+TEST(LinearAlgebra, ParseVectorLocale)
+{
+  OrthancStone::Vector v;
+
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "1.2"));
+  ASSERT_EQ(1u, v.size());
+  ASSERT_FLOAT_EQ(1.2f, v[0]);
+
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "-1.2e+2"));
+  ASSERT_EQ(1u, v.size());
+  ASSERT_FLOAT_EQ(-120.0f, v[0]);
+
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "-1e-2\\2"));
+  ASSERT_EQ(2u, v.size());
+  ASSERT_FLOAT_EQ(-0.01f, v[0]);
+  ASSERT_FLOAT_EQ(2.0f, v[1]);
+
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "1.3671875\\1.3671875"));
+  ASSERT_EQ(2u, v.size());
+  ASSERT_FLOAT_EQ(1.3671875, v[0]);
+  ASSERT_FLOAT_EQ(1.3671875, v[1]); 
+}
+
+
 int main(int argc, char **argv)
 {
   Orthanc::Logging::Initialize();