changeset 266:c9cf95b49a86 am-2

removed OrthancSlicesLoader::ISliceLoaderObserver; now using standard messages instead
author am@osimis.io
date Tue, 21 Aug 2018 18:14:22 +0200
parents 30dc6e383b0b
children 89d02de83c03
files Applications/Samples/SimpleViewerApplication.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Messages/IObservable.h Framework/Messages/IObserver.h Framework/Messages/MessageBroker.cpp Framework/Messages/MessageBroker.h Framework/SmartLoader.cpp Framework/SmartLoader.h Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Framework/dev.h Resources/CMake/OrthancStoneConfiguration.cmake UnitTestsSources/TestMessageBroker.cpp
diffstat 17 files changed, 148 insertions(+), 170 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewerApplication.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Tue Aug 21 18:14:22 2018 +0200
@@ -175,7 +175,7 @@
       //      }
 
 
-      virtual void HandleMessage(IObservable& from, const IMessage& message) {
+      virtual void HandleMessage(const IObservable& from, const IMessage& message) {
         switch (message.GetType()) {
         case MessageType_GeometryReady:
           mainLayout_->SetDefaultView();
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Aug 21 18:14:22 2018 +0200
@@ -31,49 +31,55 @@
 
 namespace OrthancStone
 {
-  void OrthancFrameLayerSource::OnSliceGeometryReady(const OrthancSlicesLoader& loader)
+  void OrthancFrameLayerSource::HandleMessage(const IObservable& from, const IMessage& message)
   {
-    if (loader.GetSliceCount() > 0)
+    switch (message.GetType())
     {
-      LayerSourceBase::NotifyGeometryReady();
-    }
-    else
+    case MessageType_SliceGeometryReady:
     {
-      LayerSourceBase::NotifyGeometryError();
-    }
-  }
-
-  void OrthancFrameLayerSource::OnSliceGeometryError(const OrthancSlicesLoader& loader)
-  {
-    LayerSourceBase::NotifyGeometryError();
-  }
+      const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
+      if (loader.GetSliceCount() > 0)
+      {
+        LayerSourceBase::NotifyGeometryReady();
+      }
+      else
+      {
+        LayerSourceBase::NotifyGeometryError();
+      }
 
-  void OrthancFrameLayerSource::OnSliceImageReady(const OrthancSlicesLoader& loader,
-                                                      unsigned int sliceIndex,
-                                                      const Slice& slice,
-                                                      std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                                      SliceImageQuality quality)
-  {
-    bool isFull = (quality == SliceImageQuality_FullPng || quality == SliceImageQuality_FullPam);
-    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull),
-                                      slice.GetGeometry(), false);
-  }
+    }; break;
+    case MessageType_SliceGeometryError:
+    {
+      const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
+      LayerSourceBase::NotifyGeometryError();
+    }; break;
+    case MessageType_SliceImageReady:
+    {
+      const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
+      bool isFull = (msg.effectiveQuality_ == SliceImageQuality_FullPng || msg.effectiveQuality_ == SliceImageQuality_FullPam);
+      LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(msg.image_.release(), msg.slice_, isFull),
+                                        msg.slice_.GetGeometry(), false);
 
-  void OrthancFrameLayerSource::OnSliceImageError(const OrthancSlicesLoader& loader,
-                                                      unsigned int sliceIndex,
-                                                      const Slice& slice,
-                                                      SliceImageQuality quality)
-  {
-    LayerSourceBase::NotifyLayerReady(NULL, slice.GetGeometry(), true);
+    }; break;
+    case MessageType_SliceImageError:
+    {
+      const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
+      LayerSourceBase::NotifyLayerReady(NULL, msg.slice_.GetGeometry(), true);
+    }; break;
+    default:
+      VLOG("unhandled message type" << message.GetType());
+    }
   }
 
 
   OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
     LayerSourceBase(broker),
-    OrthancSlicesLoader::ISliceLoaderObserver(broker),
-    loader_(broker, *this, orthanc),
+    IObserver(broker),
+    //OrthancSlicesLoader::ISliceLoaderObserver(broker),
+    loader_(broker, orthanc),
     quality_(SliceImageQuality_FullPng)
   {
+    loader_.RegisterObserver(*this);
   }
 
   
--- a/Framework/Layers/OrthancFrameLayerSource.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Tue Aug 21 18:14:22 2018 +0200
@@ -27,29 +27,18 @@
 
 namespace OrthancStone
 {  
+  // this class is in charge of loading a Frame.
+  // once it's been loaded (first the geometry and then the image),
+  // messages are sent to observers so they can use it
   class OrthancFrameLayerSource :
     public LayerSourceBase,
-    private OrthancSlicesLoader::ISliceLoaderObserver
+    public IObserver
+    //private OrthancSlicesLoader::ISliceLoaderObserver
   {
   private:
     OrthancSlicesLoader  loader_;
     SliceImageQuality    quality_;
 
-    virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader);
-
-    virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader);
-
-    virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                       SliceImageQuality quality);
-
-    virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       SliceImageQuality quality);
-
   public:
     OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc);
 
@@ -79,5 +68,7 @@
                            const CoordinateSystem3D& viewportSlice);
 
     virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice);
+
+    virtual void HandleMessage(const IObservable& from, const IMessage& message);
   };
 }
--- a/Framework/Messages/IObservable.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Messages/IObservable.h	Tue Aug 21 18:14:22 2018 +0200
@@ -43,7 +43,7 @@
     {
     }
 
-    void EmitMessage(const IMessage& message)
+    void EmitMessage(const IMessage& message) const
     {
       broker_.EmitMessage(*this, observers_, message);
     }
--- a/Framework/Messages/IObserver.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Messages/IObserver.h	Tue Aug 21 18:14:22 2018 +0200
@@ -46,7 +46,7 @@
       broker_.Unregister(*this);
     }
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
+    virtual void HandleMessage(const IObservable& from, const IMessage& message) = 0;
   };
 
 }
--- a/Framework/Messages/MessageBroker.cpp	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Messages/MessageBroker.cpp	Tue Aug 21 18:14:22 2018 +0200
@@ -30,7 +30,7 @@
 
 namespace OrthancStone {
 
-  void MessageBroker::EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message)
+  void MessageBroker::EmitMessage(const IObservable& from, std::set<IObserver*> observers, const IMessage& message)
   {
     std::vector<IObserver*> activeObservers;
     std::set_intersection(observers.begin(),
--- a/Framework/Messages/MessageBroker.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Messages/MessageBroker.h	Tue Aug 21 18:14:22 2018 +0200
@@ -56,7 +56,7 @@
       activeObservers_.erase(&observer);
     }
 
-    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
+    void EmitMessage(const IObservable& from, std::set<IObserver*> observers, const IMessage& message);
   };
 
 }
--- a/Framework/SmartLoader.cpp	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/SmartLoader.cpp	Tue Aug 21 18:14:22 2018 +0200
@@ -21,6 +21,7 @@
 
 #include "SmartLoader.h"
 #include "Layers/OrthancFrameLayerSource.h"
+#include "Layers/OrthancFrameLayer.h"
 
 namespace OrthancStone
 {
@@ -31,15 +32,19 @@
     webService_(webService)
   {}
 
-  void SmartLoader::HandleMessage(IObservable& from, const IMessage& message)
+  void SmartLoader::HandleMessage(const IObservable& from, const IMessage& message)
   {
     switch (message.GetType()) {
     case MessageType_SliceGeometryReady:
+    {
+      const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
       // TODO keep track of objects that have been loaded already
-      break;
+    }; break;
     case MessageType_SliceImageReady:
+    {
+      const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
       // TODO keep track of objects that have been loaded already
-      break;
+    }; break;
     default:
       VLOG("unhandled message type" << message.GetType());
     }
@@ -65,4 +70,15 @@
   }
 
 
+  void PreloadStudy(const std::string studyId)
+  {
+    /* TODO */
+  }
+
+  void PreloadSeries(const std::string seriesId)
+  {
+    /* TODO */
+  }
+
+
 }
--- a/Framework/SmartLoader.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/SmartLoader.h	Tue Aug 21 18:14:22 2018 +0200
@@ -32,13 +32,14 @@
     SliceImageQuality imageQuality_;
     IWebService& webService_;
 
+
   public:
     SmartLoader(MessageBroker& broker, IWebService& webService);  // TODO: add maxPreloadStorageSizeInBytes
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
+    virtual void HandleMessage(const IObservable& from, const IMessage& message);
 
-    void PreloadStudy(const std::string studyId) {/* TODO */}
-    void PreloadSeries(const std::string seriesId) {/* TODO */}
+    void PreloadStudy(const std::string studyId);
+    void PreloadSeries(const std::string seriesId);
 
     void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
 
--- a/Framework/Toolbox/IWebService.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Tue Aug 21 18:14:22 2018 +0200
@@ -75,7 +75,7 @@
             {
             }
 
-            virtual void HandleMessage(IObservable& from, const IMessage& message)
+            virtual void HandleMessage(const IObservable& from, const IMessage& message)
             {
                 switch(message.GetType())
                 {
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Aug 21 18:14:22 2018 +0200
@@ -253,15 +253,17 @@
       {
       case Mode_FrameGeometry:
       case Mode_SeriesGeometry:
-        that_.userCallback_.OnSliceGeometryError(that_);
+        that_.EmitMessage(IMessage(MessageType_SliceGeometryError));
         that_.state_ = State_Error;
         break;
         
       case Mode_LoadImage:
-        that_.userCallback_.OnSliceImageError(that_, operation->GetSliceIndex(),
-                                              operation->GetSlice(),
-                                              operation->GetQuality());
-        break;
+      {
+        OrthancSlicesLoader::SliceImageErrorMessage msg(operation->GetSliceIndex(),
+                                   operation->GetSlice(),
+                                   operation->GetQuality());
+        that_.EmitMessage(msg);
+      }; break;
 
       default:
         throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
@@ -269,36 +271,10 @@
     }
   };
   
-  void OrthancSlicesLoader::ISliceLoaderObserver::HandleMessage(IObservable& from, const IMessage& message)
+  void OrthancSlicesLoader::HandleMessage(const IObservable& from, const IMessage& message)
   {
-    switch (message.GetType())
-    {
-    case MessageType_SliceGeometryReady:
-      OnSliceGeometryReady(dynamic_cast<OrthancSlicesLoader&>(from));
-      break;
-    case MessageType_SliceGeometryError:
-      OnSliceGeometryError(dynamic_cast<OrthancSlicesLoader&>(from));
-      break;
-    case MessageType_SliceImageReady:
-    {
-      const SliceImageReadyMessage& msg = dynamic_cast<const SliceImageReadyMessage&>(message);
-      OnSliceImageReady(dynamic_cast<OrthancSlicesLoader&>(from),
-                        msg.sliceIndex_,
-                        msg.slice_,
-                        msg.image_,
-                        msg.effectiveQuality_);
-    }; break;
-    case MessageType_SliceImageError:
-    {
-      const SliceImageErrorMessage& msg = dynamic_cast<const SliceImageErrorMessage&>(message);
-      OnSliceImageError(dynamic_cast<OrthancSlicesLoader&>(from),
-                        msg.sliceIndex_,
-                        msg.slice_,
-                        msg.effectiveQuality_);
-    }; break;
-    default:
-      VLOG("unhandled message type" << message.GetType());
-    }
+    // forward messages to its own observers
+    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
   }
 
   
@@ -311,16 +287,16 @@
     }
     else
     {
-      userCallback_.OnSliceImageReady
-          (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+      OrthancSlicesLoader::SliceImageReadyMessage msg(operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+      EmitMessage(msg);
     }
   }
   
   
   void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
   {
-    userCallback_.OnSliceImageError
-        (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    OrthancSlicesLoader::SliceImageErrorMessage msg(operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    EmitMessage(msg);
   }
   
   
@@ -345,12 +321,12 @@
     if (ok)
     {
       LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-      userCallback_.OnSliceGeometryReady(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryReady));
     }
     else
     {
       LOG(ERROR) << "This series is empty";
-      userCallback_.OnSliceGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryError));
     }
   }
   
@@ -362,7 +338,7 @@
     if (!MessagingToolbox::ParseJson(series, answer, size) ||
         series.type() != Json::objectValue)
     {
-      userCallback_.OnSliceGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryError));
       return;
     }
     
@@ -409,7 +385,7 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.OnSliceGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryError));
       return;
     }
     
@@ -436,7 +412,7 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        userCallback_.OnSliceGeometryError(*this);
+        EmitMessage(IMessage(MessageType_SliceGeometryError));
         return;
       }
     }
@@ -454,7 +430,7 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.OnSliceGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryError));
       return;
     }
     
@@ -468,14 +444,14 @@
     std::auto_ptr<Slice> slice(new Slice);
     if (slice->ParseOrthancFrame(dicom, instanceId, frame))
     {
-      LOG(INFO) << "Loaded instance " << instanceId;
+      LOG(INFO) << "Loaded instance geometry " << instanceId;
       slices_.AddSlice(slice.release());
-      userCallback_.OnSliceGeometryReady(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryReady));
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      userCallback_.OnSliceGeometryError(*this);
+      EmitMessage(IMessage(MessageType_SliceGeometryError));
     }
   }
   
@@ -800,10 +776,11 @@
   
   
   OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker,
-                                           ISliceLoaderObserver& callback,
+                                           //ISliceLoaderObserver& callback,
                                            IWebService& orthanc) :
+    IObservable(broker),
     webCallback_(new WebCallback(broker, *this)),
-    userCallback_(callback),
+    //userCallback_(callback),
     orthanc_(orthanc),
     state_(State_Initialization)
   {
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Aug 21 18:14:22 2018 +0200
@@ -29,7 +29,7 @@
 
 namespace OrthancStone
 {
-  class OrthancSlicesLoader : public boost::noncopyable
+  class OrthancSlicesLoader : public IObservable
   {
   public:
     struct SliceImageReadyMessage : public IMessage
@@ -68,38 +68,6 @@
       {
       }
     };
-
-  public:
-    class ISliceLoaderObserver : public IObserver
-    {
-    public:
-
-      ISliceLoaderObserver(MessageBroker& broker)
-        : IObserver(broker)
-      {
-      }
-
-      virtual ~ISliceLoaderObserver()
-      {
-      }
-
-      virtual void HandleMessage(IObservable& from, const IMessage& message);
-
-      virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader) = 0;
-
-      virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader) = 0;
-
-      virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
-                                         unsigned int sliceIndex,
-                                         const Slice& slice,
-                                         std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                         SliceImageQuality effectiveQuality) = 0;
-
-      virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
-                                         unsigned int sliceIndex,
-                                         const Slice& slice,
-                                         SliceImageQuality quality) = 0;
-    };
     
   private:
     enum State
@@ -125,7 +93,6 @@
 
     boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
 
-    ISliceLoaderObserver&    userCallback_; // TODO: instead of passing a userCallback, use the generic messages
     IWebService&  orthanc_;
     State         state_;
     SlicesSorter  slices_;
@@ -177,7 +144,7 @@
     
   public:
     OrthancSlicesLoader(MessageBroker& broker,
-                        ISliceLoaderObserver& callback,
+                        //ISliceLoaderObserver& callback,
                         IWebService& orthanc);
 
     void ScheduleLoadSeries(const std::string& seriesId);
@@ -198,5 +165,7 @@
 
     void ScheduleLoadSliceImage(size_t index,
                                 SliceImageQuality requestedQuality);
+
+    virtual void HandleMessage(const IObservable& from, const IMessage& message);
   };
 }
--- a/Framework/Widgets/LayerWidget.cpp	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Tue Aug 21 18:14:22 2018 +0200
@@ -480,26 +480,26 @@
     }
   }
 
-  void LayerWidget::HandleMessage(IObservable& from, const IMessage& message)
+  void LayerWidget::HandleMessage(const IObservable& from, const IMessage& message)
   {
     switch (message.GetType()) {
     case MessageType_GeometryReady:
-      OnGeometryReady(dynamic_cast<ILayerSource&>(from));
+      OnGeometryReady(dynamic_cast<const ILayerSource&>(from));
       break;
     case MessageType_GeometryError:
       LOG(ERROR) << "Cannot get geometry";
       break;
     case MessageType_ContentChanged:
-      OnContentChanged(dynamic_cast<ILayerSource&>(from));
+      OnContentChanged(dynamic_cast<const ILayerSource&>(from));
       break;
     case MessageType_SliceChanged:
-      OnSliceChanged(dynamic_cast<ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
+      OnSliceChanged(dynamic_cast<const ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
       break;
     case MessageType_LayerReady:
     {
       const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
       OnLayerReady(layerReadyMessage.layer_,
-                   dynamic_cast<ILayerSource&>(from),
+                   dynamic_cast<const ILayerSource&>(from),
                    layerReadyMessage.slice_,
                    layerReadyMessage.isError_);
     }; break;
--- a/Framework/Widgets/LayerWidget.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Tue Aug 21 18:14:22 2018 +0200
@@ -72,7 +72,7 @@
   public:
     LayerWidget(MessageBroker& broker);
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
+    virtual void HandleMessage(const IObservable& from, const IMessage& message);
 
     virtual Extent2D GetSceneExtent();
  
--- a/Framework/dev.h	Tue Aug 21 16:48:25 2018 +0200
+++ b/Framework/dev.h	Tue Aug 21 18:14:22 2018 +0200
@@ -43,7 +43,7 @@
   // TODO: Handle errors while loading
   class OrthancVolumeImage : 
     public SlicedVolumeBase,
-    private OrthancSlicesLoader::ISliceLoaderObserver
+    public OrthancStone::IObserver
   { 
   private:
     OrthancSlicesLoader           loader_;
@@ -106,7 +106,7 @@
     }
 
 
-    virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader)
+    void OnSliceGeometryReady(const OrthancSlicesLoader& loader)
     {
       if (loader.GetSliceCount() == 0)
       {
@@ -151,15 +151,15 @@
       unsigned int width = loader.GetSlice(0).GetWidth();
       unsigned int height = loader.GetSlice(0).GetHeight();
       Orthanc::PixelFormat format = loader.GetSlice(0).GetConverter().GetExpectedPixelFormat();
-      LOG(INFO) << "Creating a volume image of size " << width << "x" << height 
+      LOG(INFO) << "Creating a volume image of size " << width << "x" << height
                 << "x" << loader.GetSliceCount() << " in " << Orthanc::EnumerationToString(format);
 
       image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount(), computeRange_));
       image_->SetAxialGeometry(loader.GetSlice(0).GetGeometry());
-      image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), 
+      image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(),
                                  loader.GetSlice(0).GetPixelSpacingY(), spacingZ);
       image_->Clear();
-      
+
       downloadStack_.reset(new DownloadStack(loader.GetSliceCount()));
       pendingSlices_ = loader.GetSliceCount();
 
@@ -173,12 +173,6 @@
       SlicedVolumeBase::NotifyGeometryReady();
     }
 
-    virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader)
-    {
-      LOG(ERROR) << "Unable to download a volume image";
-      SlicedVolumeBase::NotifyGeometryError();
-    }
-
     virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
@@ -190,7 +184,7 @@
         Orthanc::ImageProcessing::Copy(writer.GetAccessor(), *image);
       }
 
-      SlicedVolumeBase::NotifySliceChange(sliceIndex, slice);     
+      SlicedVolumeBase::NotifySliceChange(sliceIndex, slice);
 
       if (pendingSlices_ == 1)
       {
@@ -205,24 +199,47 @@
       ScheduleSliceDownload();
     }
 
-    virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       SliceImageQuality quality)
+    virtual void HandleMessage(const IObservable& from, const IMessage& message)
     {
-      LOG(ERROR) << "Cannot download slice " << sliceIndex << " in a volume image";
-      ScheduleSliceDownload();
+      switch (message.GetType())
+      {
+      case MessageType_SliceGeometryReady:
+        OnSliceGeometryReady(dynamic_cast<const OrthancSlicesLoader&>(from));
+      case MessageType_SliceGeometryError:
+      {
+        LOG(ERROR) << "Unable to download a volume image";
+        SlicedVolumeBase::NotifyGeometryError();
+      }; break;
+      case MessageType_SliceImageReady:
+      {
+        const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
+        OnSliceImageReady(dynamic_cast<const OrthancSlicesLoader&>(from),
+                          msg.sliceIndex_,
+                          msg.slice_,
+                          msg.image_,
+                          msg.effectiveQuality_);
+      }; break;
+      case MessageType_SliceImageError:
+      {
+          const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
+          LOG(ERROR) << "Cannot download slice " << msg.sliceIndex_ << " in a volume image";
+          ScheduleSliceDownload();
+      }; break;
+      default:
+        VLOG("unhandled message type" << message.GetType());
+      }
     }
 
   public:
     OrthancVolumeImage(MessageBroker& broker,
                        IWebService& orthanc,
                        bool computeRange) : 
-      OrthancSlicesLoader::ISliceLoaderObserver(broker),
-      loader_(broker, *this, orthanc),
+      OrthancStone::IObserver(broker),
+      loader_(broker, orthanc),
       computeRange_(computeRange),
       pendingSlices_(0)
     {
+        loader_.RegisterObserver(*this);
     }
 
     void ScheduleLoadSeries(const std::string& seriesId)
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Aug 21 16:48:25 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Aug 21 18:14:22 2018 +0200
@@ -190,6 +190,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LayerSourceBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineLayerRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Layers/OrthancFrameLayer.h
   ${ORTHANC_STONE_ROOT}/Framework/Layers/OrthancFrameLayerSource.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp
--- a/UnitTestsSources/TestMessageBroker.cpp	Tue Aug 21 16:48:25 2018 +0200
+++ b/UnitTestsSources/TestMessageBroker.cpp	Tue Aug 21 18:14:22 2018 +0200
@@ -38,7 +38,7 @@
   {}
 
 
-  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+  void HandleMessage(const OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
     if (message.GetType() == OrthancStone::MessageType_Generic) {
       globalCounter++;
     }