changeset 252:40b21c1f8b8d am-2

more usage of IObservable/IObserver
author am@osimis.io
date Tue, 03 Jul 2018 10:26:56 +0200
parents 192e6e349e69
children 8ff70c04c6df
files Framework/Layers/ILayerSource.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Messages/MessageType.h Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Widgets/LayerWidget.cpp Framework/dev.h
diffstat 9 files changed, 353 insertions(+), 267 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Layers/ILayerSource.h	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Tue Jul 03 10:26:56 2018 +0200
@@ -28,32 +28,32 @@
 
 namespace OrthancStone
 {
-  class ILayerSource : public boost::noncopyable, public IObservable
+  class ILayerSource : public IObservable
   {
   public:
     struct SliceChangedMessage : public IMessage
     {
-      const Slice& slice;
+      const Slice& slice_;
       SliceChangedMessage(const Slice& slice)
         : IMessage(MessageType_SliceChanged),
-          slice(slice)
+          slice_(slice)
       {
       }
     };
 
     struct LayerReadyMessage : public IMessage
     {
-      std::auto_ptr<ILayerRenderer>& layer;
-      const CoordinateSystem3D& slice;
-      bool isError;
+      std::auto_ptr<ILayerRenderer>& layer_;
+      const CoordinateSystem3D& slice_;
+      bool isError_;
 
       LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer,
                         const CoordinateSystem3D& slice,
                         bool isError)  // TODO Shouldn't this be separate as NotifyLayerError?
         : IMessage(MessageType_LayerReady),
-          layer(layer),
-          slice(slice),
-          isError(isError)
+          layer_(layer),
+          slice_(slice),
+          isError_(isError)
       {
       }
     };
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Jul 03 10:26:56 2018 +0200
@@ -31,7 +31,7 @@
 
 namespace OrthancStone
 {
-  void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader)
+  void OrthancFrameLayerSource::OnSliceGeometryReady(const OrthancSlicesLoader& loader)
   {
     if (loader.GetSliceCount() > 0)
     {
@@ -43,12 +43,12 @@
     }
   }
 
-  void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader)
+  void OrthancFrameLayerSource::OnSliceGeometryError(const OrthancSlicesLoader& loader)
   {
     LayerSourceBase::NotifyGeometryError();
   }
 
-  void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader,
+  void OrthancFrameLayerSource::OnSliceImageReady(const OrthancSlicesLoader& loader,
                                                       unsigned int sliceIndex,
                                                       const Slice& slice,
                                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
@@ -59,7 +59,7 @@
                                       slice.GetGeometry(), false);
   }
 
-  void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader,
+  void OrthancFrameLayerSource::OnSliceImageError(const OrthancSlicesLoader& loader,
                                                       unsigned int sliceIndex,
                                                       const Slice& slice,
                                                       SliceImageQuality quality)
@@ -70,6 +70,7 @@
 
   OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
     LayerSourceBase(broker),
+    OrthancSlicesLoader::ISliceLoaderObserver(broker),
     loader_(broker, *this, orthanc),
     quality_(SliceImageQuality_Full)
   {
--- a/Framework/Layers/OrthancFrameLayerSource.h	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Tue Jul 03 10:26:56 2018 +0200
@@ -29,23 +29,23 @@
 {  
   class OrthancFrameLayerSource :
     public LayerSourceBase,
-    private OrthancSlicesLoader::ICallback
+    private OrthancSlicesLoader::ISliceLoaderObserver
   {
   private:
     OrthancSlicesLoader  loader_;
     SliceImageQuality    quality_;
 
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader);
+    virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader);
 
-    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader);
+    virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader);
 
-    virtual void NotifySliceImageReady(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 NotifySliceImageError(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        SliceImageQuality quality);
--- a/Framework/Messages/MessageType.h	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Messages/MessageType.h	Tue Jul 03 10:26:56 2018 +0200
@@ -32,6 +32,11 @@
     MessageType_SliceChanged,
     MessageType_LayerReady,
 
+    MessageType_SliceGeometryReady,
+    MessageType_SliceGeometryError,
+    MessageType_SliceImageReady,
+    MessageType_SliceImageError,
+
     MessageType_HttpRequestSuccess,
     MessageType_HttpRequestError
 
--- a/Framework/Toolbox/IWebService.h	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Tue Jul 03 10:26:56 2018 +0200
@@ -24,6 +24,7 @@
 #include <Core/IDynamicObject.h>
 #include "../../Framework/Messages/IObserver.h"
 #include <string>
+#include <Core/Logging.h>
 
 namespace OrthancStone
 {
@@ -90,7 +91,8 @@
                                          msg.AnswerSize,
                                          msg.Payload);
                 }; break;
-
+                default:
+                  VLOG("unhandled message type" << message.GetType());
                 }
             }
 
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Jul 03 10:26:56 2018 +0200
@@ -13,7 +13,7 @@
  * 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/>.
  **/
@@ -47,10 +47,10 @@
 static std::string base64_decode(const std::string &in)
 {
   std::string out;
-
+  
   std::vector<int> T(256,-1);
-  for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; 
-
+  for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
+  
   int val=0, valb=-8;
   for (size_t i = 0; i < in.size(); i++) {
     unsigned char c = in[i];
@@ -78,32 +78,32 @@
     const Slice*       slice_;
     std::string        instanceId_;
     SliceImageQuality  quality_;
-
+    
     Operation(Mode mode) :
       mode_(mode)
     {
     }
-
+    
   public:
     Mode GetMode() const
     {
       return mode_;
     }
-
+    
     SliceImageQuality GetQuality() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return quality_;
     }
-
+    
     unsigned int GetSliceIndex() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return sliceIndex_;
     }
-
+    
     const Slice& GetSlice() const
     {
       assert(mode_ == Mode_LoadImage ||
@@ -111,32 +111,32 @@
       assert(slice_ != NULL);
       return *slice_;
     }
-
+    
     unsigned int GetFrame() const
     {
       assert(mode_ == Mode_FrameGeometry);
       return frame_;
     }
-      
+
     const std::string& GetInstanceId() const
     {
       assert(mode_ == Mode_FrameGeometry ||
              mode_ == Mode_InstanceGeometry);
       return instanceId_;
     }
-      
+
     static Operation* DownloadSeriesGeometry()
     {
       return new Operation(Mode_SeriesGeometry);
     }
-
+    
     static Operation* DownloadInstanceGeometry(const std::string& instanceId)
     {
       std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
       operation->instanceId_ = instanceId;
       return operation.release();
     }
-
+    
     static Operation* DownloadFrameGeometry(const std::string& instanceId,
                                             unsigned int frame)
     {
@@ -145,7 +145,7 @@
       operation->frame_ = frame;
       return operation.release();
     }
-
+    
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
                                          const Slice&  slice,
                                          SliceImageQuality quality)
@@ -156,7 +156,7 @@
       tmp->quality_ = quality;
       return tmp.release();
     }
-
+    
     static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
                                             const Slice&  slice)
     {
@@ -167,96 +167,127 @@
       return tmp.release();
     }
   };
-    
 
+  
   class OrthancSlicesLoader::WebCallback : public IWebService::ICallback
   {
   private:
     OrthancSlicesLoader&  that_;
-
+    
   public:
     WebCallback(MessageBroker& broker, OrthancSlicesLoader&  that) :
       IWebService::ICallback(broker),
       that_(that)
     {
     }
-
+    
     virtual void OnHttpRequestSuccess(const std::string& uri,
-                               const void* answer,
-                               size_t answerSize,
-                               Orthanc::IDynamicObject* payload)
+                                      const void* answer,
+                                      size_t answerSize,
+                                      Orthanc::IDynamicObject* payload)
     {
       std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-
+      
       switch (operation->GetMode())
       {
-        case Mode_SeriesGeometry:
-          that_.ParseSeriesGeometry(answer, answerSize);
-          break;
-
-        case Mode_InstanceGeometry:
-          that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
-          break;
-
-        case Mode_FrameGeometry:
-          that_.ParseFrameGeometry(operation->GetInstanceId(),
-                                   operation->GetFrame(), answer, answerSize);
+      case Mode_SeriesGeometry:
+        that_.ParseSeriesGeometry(answer, answerSize);
+        break;
+        
+      case Mode_InstanceGeometry:
+        that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
+        break;
+        
+      case Mode_FrameGeometry:
+        that_.ParseFrameGeometry(operation->GetInstanceId(),
+                                 operation->GetFrame(), answer, answerSize);
+        break;
+        
+      case Mode_LoadImage:
+        switch (operation->GetQuality())
+        {
+        case SliceImageQuality_Full:
+          that_.ParseSliceImagePng(*operation, answer, answerSize);
           break;
-
-        case Mode_LoadImage:
-          switch (operation->GetQuality())
-          {
-            case SliceImageQuality_Full:
-              that_.ParseSliceImagePng(*operation, answer, answerSize);
-              break;
-
-            case SliceImageQuality_Jpeg50:
-            case SliceImageQuality_Jpeg90:
-            case SliceImageQuality_Jpeg95:
-              that_.ParseSliceImageJpeg(*operation, answer, answerSize);
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-                
-          break;
-
-        case Mode_LoadRawImage:
-          that_.ParseSliceRawImage(*operation, answer, answerSize);
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-    virtual void OnHttpRequestError(const std::string& uri,
-                             Orthanc::IDynamicObject* payload)
-    {
-      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-      LOG(ERROR) << "Cannot download " << uri;
-
-      switch (operation->GetMode())
-      {
-        case Mode_FrameGeometry:
-        case Mode_SeriesGeometry:
-          that_.userCallback_.NotifyGeometryError(that_);
-          that_.state_ = State_Error;
-          break;
-
-        case Mode_LoadImage:
-          that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
-                                                    operation->GetSlice(),
-                                                    operation->GetQuality());
+          
+        case SliceImageQuality_Jpeg50:
+        case SliceImageQuality_Jpeg90:
+        case SliceImageQuality_Jpeg95:
+          that_.ParseSliceImageJpeg(*operation, answer, answerSize);
           break;
           
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        break;
+        
+      case Mode_LoadRawImage:
+        that_.ParseSliceRawImage(*operation, answer, answerSize);
+        break;
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
-    }     
+    }
+    
+    virtual void OnHttpRequestError(const std::string& uri,
+                                    Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
+      LOG(ERROR) << "Cannot download " << uri;
+      
+      switch (operation->GetMode())
+      {
+      case Mode_FrameGeometry:
+      case Mode_SeriesGeometry:
+        that_.userCallback_.OnSliceGeometryError(that_);
+        that_.state_ = State_Error;
+        break;
+        
+      case Mode_LoadImage:
+        that_.userCallback_.OnSliceImageError(that_, operation->GetSliceIndex(),
+                                              operation->GetSlice(),
+                                              operation->GetQuality());
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
   };
-
+  
+  void OrthancSlicesLoader::ISliceLoaderObserver::HandleMessage(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());
+    }
+  }
 
   
   void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
@@ -268,19 +299,19 @@
     }
     else
     {
-      userCallback_.NotifySliceImageReady
-        (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+      userCallback_.OnSliceImageReady
+          (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
     }
   }
-
+  
   
   void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
   {
-    userCallback_.NotifySliceImageError
-      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    userCallback_.OnSliceImageError
+        (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
   }
-
-
+  
+  
   void OrthancSlicesLoader::SortAndFinalizeSlices()
   {
     bool ok = false;
@@ -296,21 +327,21 @@
         ok = true;
       }
     }
-
+    
     state_ = State_GeometryReady;
-
+    
     if (ok)
     {
       LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-      userCallback_.NotifyGeometryReady(*this);
+      userCallback_.OnSliceGeometryReady(*this);
     }
     else
     {
       LOG(ERROR) << "This series is empty";
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
     }
   }
-
+  
   
   void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
                                                 size_t size)
@@ -319,18 +350,18 @@
     if (!MessagingToolbox::ParseJson(series, answer, size) ||
         series.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
       return;
     }
-
+    
     Json::Value::Members instances = series.getMemberNames();
-
+    
     slices_.Reserve(instances.size());
-
+    
     for (size_t i = 0; i < instances.size(); i++)
     {
       OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
-
+      
       Orthanc::DicomMap dicom;
       MessagingToolbox::ConvertDataset(dicom, dataset);
       
@@ -339,7 +370,7 @@
       {
         frames = 1;
       }
-
+      
       for (unsigned int frame = 0; frame < frames; frame++)
       {
         std::auto_ptr<Slice> slice(new Slice);
@@ -353,11 +384,11 @@
         }
       }
     }
-
+    
     SortAndFinalizeSlices();
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
                                                   const void* answer,
                                                   size_t size)
@@ -366,15 +397,15 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
       return;
     }
-
+    
     OrthancPlugins::FullOrthancDataset dataset(tags);
-
+    
     Orthanc::DicomMap dicom;
     MessagingToolbox::ConvertDataset(dicom, dataset);
-      
+
     unsigned int frames;
     if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
     {
@@ -382,7 +413,7 @@
     }
     
     LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)";
-
+    
     for (unsigned int frame = 0; frame < frames; frame++)
     {
       std::auto_ptr<Slice> slice(new Slice);
@@ -393,15 +424,15 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        userCallback_.NotifyGeometryError(*this);
+        userCallback_.OnSliceGeometryError(*this);
         return;
       }
     }
-
+    
     SortAndFinalizeSlices();
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId,
                                                unsigned int frame,
                                                const void* answer,
@@ -411,38 +442,38 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
       return;
     }
-
+    
     OrthancPlugins::FullOrthancDataset dataset(tags);
-
+    
     state_ = State_GeometryReady;
-
+    
     Orthanc::DicomMap dicom;
     MessagingToolbox::ConvertDataset(dicom, dataset);
-
+    
     std::auto_ptr<Slice> slice(new Slice);
     if (slice->ParseOrthancFrame(dicom, instanceId, frame))
     {
       LOG(INFO) << "Loaded instance " << instanceId;
       slices_.AddSlice(slice.release());
-      userCallback_.NotifyGeometryReady(*this);
+      userCallback_.OnSliceGeometryReady(*this);
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      userCallback_.NotifyGeometryError(*this);
+      userCallback_.OnSliceGeometryError(*this);
     }
   }
-
-
+  
+  
   void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
                                                const void* answer,
                                                size_t size)
   {
     std::auto_ptr<Orthanc::ImageAccessor>  image;
-
+    
     try
     {
       image.reset(new Orthanc::PngReader);
@@ -453,14 +484,14 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     if (image->GetWidth() != operation.GetSlice().GetWidth() ||
         image->GetHeight() != operation.GetSlice().GetHeight())
     {
       NotifySliceImageError(operation);
       return;
     }
-      
+
     if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
         Orthanc::PixelFormat_SignedGrayscale16)
     {
@@ -474,10 +505,10 @@
         return;
       }
     }
-
+    
     NotifySliceImageSuccess(operation, image);
-  } 
-
+  }
+  
   
   void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
                                                 const void* answer,
@@ -492,7 +523,7 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     Json::Value& info = encoded["Orthanc"];
     if (!info.isMember("PixelData") ||
         !info.isMember("Stretched") ||
@@ -505,30 +536,30 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     bool isSigned = false;
     bool isStretched = info["Stretched"].asBool();
-
+    
     if (info.isMember("IsSigned"))
     {
       if (info["IsSigned"].type() != Json::booleanValue)
       {
         NotifySliceImageError(operation);
         return;
-      }          
+      }
       else
       {
         isSigned = info["IsSigned"].asBool();
       }
     }
-
+    
     std::auto_ptr<Orthanc::ImageAccessor> reader;
-
+    
     {
       std::string jpeg;
       //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
       jpeg = base64_decode(info["PixelData"].asString());
-
+      
       try
       {
         reader.reset(new Orthanc::JpegReader);
@@ -540,10 +571,10 @@
         return;
       }
     }
-
+    
     Orthanc::PixelFormat expectedFormat =
-      operation.GetSlice().GetConverter().GetExpectedPixelFormat();
-
+        operation.GetSlice().GetConverter().GetExpectedPixelFormat();
+    
     if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
     {
       if (expectedFormat != Orthanc::PixelFormat_RGB24)
@@ -551,7 +582,7 @@
         NotifySliceImageError(operation);
         return;
       }
-
+      
       if (isSigned || isStretched)
       {
         NotifySliceImageError(operation);
@@ -563,13 +594,13 @@
         return;
       }
     }
-
+    
     if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
     {
       NotifySliceImageError(operation);
       return;
     }
-
+    
     if (!isStretched)
     {
       if (expectedFormat != reader->GetFormat())
@@ -583,10 +614,10 @@
         return;
       }
     }
-
+    
     int32_t stretchLow = 0;
     int32_t stretchHigh = 0;
-
+    
     if (!info.isMember("StretchLow") ||
         !info.isMember("StretchHigh") ||
         info["StretchLow"].type() != Json::intValue ||
@@ -595,10 +626,10 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     stretchLow = info["StretchLow"].asInt();
     stretchHigh = info["StretchHigh"].asInt();
-
+    
     if (stretchLow < -32768 ||
         stretchHigh > 65535 ||
         (stretchLow < 0 && stretchHigh > 32767))
@@ -607,29 +638,29 @@
       NotifySliceImageError(operation);
       return;
     }
-
+    
     // Decode a grayscale JPEG 8bpp image coming from the Web viewer
     std::auto_ptr<Orthanc::ImageAccessor> image
-      (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
-     
+        (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
+
     Orthanc::ImageProcessing::Convert(*image, *reader);
     reader.reset(NULL);
-
+    
     float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-
+    
     if (!LinearAlgebra::IsCloseToZero(scaling))
     {
       float offset = static_cast<float>(stretchLow) / scaling;
       Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
     }
-
+    
     NotifySliceImageSuccess(operation, image);
   }
-
-
+  
+  
   class StringImage :
-    public Orthanc::ImageAccessor,
-    public boost::noncopyable
+      public Orthanc::ImageAccessor,
+      public boost::noncopyable
   {
   private:
     std::string  buffer_;
@@ -644,23 +675,23 @@
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
       }
-
+      
       buffer_.swap(buffer);  // The source buffer is now empty
-
+      
       void* data = (buffer_.empty() ? NULL : &buffer_[0]);
-
+      
       AssignWritable(format, width, height,
                      Orthanc::GetBytesPerPixel(format) * width, data);
     }
   };
-
+  
   
   void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation,
                                                const void* answer,
                                                size_t size)
   {
     Orthanc::GzipCompressor compressor;
-
+    
     std::string raw;
     compressor.Uncompress(raw, answer, size);
     
@@ -677,9 +708,9 @@
       // This is the case of RT-DOSE (uint32_t values)
       
       std::auto_ptr<Orthanc::ImageAccessor> image
-        (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
-                         info.GetHeight(), raw));
-
+          (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
+                           info.GetHeight(), raw));
+      
       // TODO - Only for big endian
       for (unsigned int y = 0; y < image->GetHeight(); y++)
       {
@@ -689,7 +720,7 @@
           *p = le32toh(*p);
         }
       }
-
+      
       NotifySliceImageSuccess(operation, image);
     }
     else if (info.GetBitsAllocated() == 16 &&
@@ -701,23 +732,23 @@
              raw.size() == info.GetWidth() * info.GetHeight() * 2)
     {
       std::auto_ptr<Orthanc::ImageAccessor> image
-        (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
-                         info.GetHeight(), raw));
-
+          (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
+                           info.GetHeight(), raw));
+      
       // TODO - Big endian ?
-
+      
       NotifySliceImageSuccess(operation, image);
     }
     else
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
-        
+
   }
-
-
+  
+  
   OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker,
-                                           ICallback& callback,
+                                           ISliceLoaderObserver& callback,
                                            IWebService& orthanc) :
     webCallback_(new WebCallback(broker, *this)),
     userCallback_(callback),
@@ -725,7 +756,7 @@
     state_(State_Initialization)
   {
   }
-
+  
   
   void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
   {
@@ -740,8 +771,8 @@
       orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry());
     }
   }
-
-
+  
+  
   void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
   {
     if (state_ != State_Initialization)
@@ -751,16 +782,16 @@
     else
     {
       state_ = State_LoadingGeometry;
-
+      
       // Tag "3004-000c" is "Grid Frame Offset Vector", which is
       // mandatory to read RT DOSE, but is too long to be returned by default
       std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c";
       orthanc_.ScheduleGetRequest
-        (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId));
+          (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId));
     }
   }
   
-
+  
   void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId,
                                               unsigned int frame)
   {
@@ -773,27 +804,27 @@
       state_ = State_LoadingGeometry;
       std::string uri = "/instances/" + instanceId + "/tags";
       orthanc_.ScheduleGetRequest
-        (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame));
+          (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame));
     }
   }
   
-
+  
   bool OrthancSlicesLoader::IsGeometryReady() const
   {
     return state_ == State_GeometryReady;
   }
-
-
+  
+  
   size_t OrthancSlicesLoader::GetSliceCount() const
   {
     if (state_ != State_GeometryReady)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.GetSliceCount();
   }
-
+  
   
   const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
   {
@@ -801,11 +832,11 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.GetSlice(index);
   }
   
-
+  
   bool OrthancSlicesLoader::LookupSlice(size_t& index,
                                         const CoordinateSystem3D& plane) const
   {
@@ -813,76 +844,76 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     return slices_.LookupSlice(index, plane);
   }
   
-
+  
   void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
                                                   size_t index)
   {
-    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
-
+    
     switch (slice.GetConverter().GetExpectedPixelFormat())
     {
-      case Orthanc::PixelFormat_RGB24:
-        uri += "/preview";
-        break;
-
-      case Orthanc::PixelFormat_Grayscale16:
-        uri += "/image-uint16";
-        break;
-
-      case Orthanc::PixelFormat_SignedGrayscale16:
-        uri += "/image-int16";
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    case Orthanc::PixelFormat_RGB24:
+      uri += "/preview";
+      break;
+      
+    case Orthanc::PixelFormat_Grayscale16:
+      uri += "/image-uint16";
+      break;
+      
+    case Orthanc::PixelFormat_SignedGrayscale16:
+      uri += "/image-int16";
+      break;
+      
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
-
+    
     orthanc_.ScheduleGetRequest(*webCallback_, uri,
                                 Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
   }
-
-
+  
+  
   void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
                                                    size_t index,
                                                    SliceImageQuality quality)
   {
     unsigned int value;
-
+    
     switch (quality)
     {
-      case SliceImageQuality_Jpeg50:
-        value = 50;
-        break;
-    
-      case SliceImageQuality_Jpeg90:
-        value = 90;
-        break;
-    
-      case SliceImageQuality_Jpeg95:
-        value = 95;
-        break;
+    case SliceImageQuality_Jpeg50:
+      value = 50;
+      break;
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    case SliceImageQuality_Jpeg90:
+      value = 90;
+      break;
+
+    case SliceImageQuality_Jpeg95:
+      value = 95;
+      break;
+      
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
     
     // This requires the official Web viewer plugin to be installed!
-    std::string uri = ("/web-viewer/instances/jpeg" + 
-                       boost::lexical_cast<std::string>(value) + 
-                       "-" + slice.GetOrthancInstanceId() + "_" + 
+    std::string uri = ("/web-viewer/instances/jpeg" +
+                       boost::lexical_cast<std::string>(value) +
+                       "-" + slice.GetOrthancInstanceId() + "_" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
-      
+
     orthanc_.ScheduleGetRequest(*webCallback_, uri,
                                 Operation::DownloadSliceImage(index, slice, quality));
   }
-
-
-
+  
+  
+  
   void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
                                                    SliceImageQuality quality)
   {
@@ -890,9 +921,9 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-
+    
     const Slice& slice = GetSlice(index);
-
+    
     if (slice.HasOrthancDecoding())
     {
       if (quality == SliceImageQuality_Full)
@@ -906,7 +937,7 @@
     }
     else
     {
-      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                          boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
       orthanc_.ScheduleGetRequest(*webCallback_, uri,
                                   Operation::DownloadSliceRawImage(index, slice));
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Jul 03 10:26:56 2018 +0200
@@ -32,24 +32,70 @@
   class OrthancSlicesLoader : public boost::noncopyable
   {
   public:
-    class ICallback : public boost::noncopyable
+    struct SliceImageReadyMessage : public IMessage
+    {
+      unsigned int sliceIndex_;
+      const Slice& slice_;
+      std::auto_ptr<Orthanc::ImageAccessor>& image_;
+      SliceImageQuality effectiveQuality_;
+
+      SliceImageReadyMessage(unsigned int sliceIndex,
+                        const Slice& slice,
+                        std::auto_ptr<Orthanc::ImageAccessor>& image,
+                        SliceImageQuality effectiveQuality)
+        : IMessage(MessageType_SliceImageReady),
+          sliceIndex_(sliceIndex),
+          slice_(slice),
+          image_(image),
+          effectiveQuality_(effectiveQuality)
+      {
+      }
+    };
+
+    struct SliceImageErrorMessage : public IMessage
+    {
+      const Slice& slice_;
+      unsigned int sliceIndex_;
+      SliceImageQuality effectiveQuality_;
+
+      SliceImageErrorMessage(unsigned int sliceIndex,
+                        const Slice& slice,
+                        SliceImageQuality effectiveQuality)
+        : IMessage(MessageType_SliceImageError),
+          slice_(slice),
+          sliceIndex_(sliceIndex),
+          effectiveQuality_(effectiveQuality)
+      {
+      }
+    };
+
+  public:
+    class ISliceLoaderObserver : public IObserver
     {
     public:
-      virtual ~ICallback()
+
+      ISliceLoaderObserver(MessageBroker& broker)
+        : IObserver(broker)
       {
       }
 
-      virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0;
+      virtual ~ISliceLoaderObserver()
+      {
+      }
+
+      virtual void HandleMessage(IObservable& from, const IMessage& message);
 
-      virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0;
+      virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader) = 0;
 
-      virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+      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 NotifySliceImageError(const OrthancSlicesLoader& loader,
+      virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
                                          unsigned int sliceIndex,
                                          const Slice& slice,
                                          SliceImageQuality quality) = 0;
@@ -78,7 +124,7 @@
 
     boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
 
-    ICallback&    userCallback_;
+    ISliceLoaderObserver&    userCallback_;
     IWebService&  orthanc_;
     State         state_;
     SlicesSorter  slices_;
@@ -123,7 +169,7 @@
     
   public:
     OrthancSlicesLoader(MessageBroker& broker,
-                        ICallback& callback,
+                        ISliceLoaderObserver& callback,
                         IWebService& orthanc);
 
     void ScheduleLoadSeries(const std::string& seriesId);
--- a/Framework/Widgets/LayerWidget.cpp	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Tue Jul 03 10:26:56 2018 +0200
@@ -493,15 +493,15 @@
       OnContentChanged(dynamic_cast<ILayerSource&>(from));
       break;
     case MessageType_SliceChanged:
-      OnSliceChanged(dynamic_cast<ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice);
+      OnSliceChanged(dynamic_cast<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,
+      OnLayerReady(layerReadyMessage.layer_,
                    dynamic_cast<ILayerSource&>(from),
-                   layerReadyMessage.slice,
-                   layerReadyMessage.isError);
+                   layerReadyMessage.slice_,
+                   layerReadyMessage.isError_);
     }; break;
     default:
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
--- a/Framework/dev.h	Mon Jul 02 18:13:46 2018 +0200
+++ b/Framework/dev.h	Tue Jul 03 10:26:56 2018 +0200
@@ -43,7 +43,7 @@
   // TODO: Handle errors while loading
   class OrthancVolumeImage : 
     public SlicedVolumeBase,
-    private OrthancSlicesLoader::ICallback
+    private OrthancSlicesLoader::ISliceLoaderObserver
   { 
   private:
     OrthancSlicesLoader           loader_;
@@ -106,7 +106,7 @@
     }
 
 
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader)
+    virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader)
     {
       if (loader.GetSliceCount() == 0)
       {
@@ -173,13 +173,13 @@
       SlicedVolumeBase::NotifyGeometryReady();
     }
 
-    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader)
+    virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader)
     {
       LOG(ERROR) << "Unable to download a volume image";
       SlicedVolumeBase::NotifyGeometryError();
     }
 
-    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        std::auto_ptr<Orthanc::ImageAccessor>& image,
@@ -205,7 +205,7 @@
       ScheduleSliceDownload();
     }
 
-    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+    virtual void OnSliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
                                        SliceImageQuality quality)
@@ -218,6 +218,7 @@
     OrthancVolumeImage(MessageBroker& broker,
                        IWebService& orthanc,
                        bool computeRange) : 
+      OrthancSlicesLoader::ISliceLoaderObserver(broker),
       loader_(broker, *this, orthanc),
       computeRange_(computeRange),
       pendingSlices_(0)