changeset 646:b4fe9642e83b

Merge from default
author Benjamin Golinvaux <bgo@osimis.io>
date Mon, 13 May 2019 15:22:08 +0200
parents 1e9ed656318e (current diff) f0008c55e5f7 (diff)
children 6af3099ed8da f1bfe3d1759f
files Framework/Scene2D/Scene2D.cpp Samples/Sdl/Loader.cpp
diffstat 21 files changed, 897 insertions(+), 307 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Layers/DicomSeriesVolumeSlicer.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Layers/DicomSeriesVolumeSlicer.h	Mon May 13 15:22:08 2019 +0200
@@ -38,8 +38,10 @@
   {
   public:
     // TODO: Add "frame" and "instanceId"
-    class FrameReadyMessage : public OriginMessage<MessageType_DicomSeriesVolumeSlicer_FrameReady, DicomSeriesVolumeSlicer>
+    class FrameReadyMessage : public OriginMessage<DicomSeriesVolumeSlicer>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       const Orthanc::ImageAccessor&  frame_;
       SliceImageQuality              imageQuality_;
--- a/Framework/Layers/IVolumeSlicer.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Layers/IVolumeSlicer.h	Mon May 13 15:22:08 2019 +0200
@@ -33,18 +33,20 @@
   class IVolumeSlicer : public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_VolumeSlicer_GeometryReady, IVolumeSlicer>  GeometryReadyMessage;
-    typedef OriginMessage<MessageType_VolumeSlicer_GeometryError, IVolumeSlicer>  GeometryErrorMessage;
-    typedef OriginMessage<MessageType_VolumeSlicer_ContentChanged, IVolumeSlicer> ContentChangedMessage;
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeSlicer);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeSlicer);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeSlicer);
 
-    class SliceContentChangedMessage : public OriginMessage<MessageType_VolumeSlicer_SliceChanged, IVolumeSlicer>
+    class SliceContentChangedMessage : public OriginMessage<IVolumeSlicer>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       const Slice& slice_;
 
     public:
       SliceContentChangedMessage(IVolumeSlicer& origin,
-                          const Slice& slice) :
+                                 const Slice& slice) :
         OriginMessage(origin),
         slice_(slice)
       {
@@ -57,8 +59,10 @@
     };
     
 
-    class LayerReadyMessage : public OriginMessage<MessageType_VolumeSlicer_LayerReady, IVolumeSlicer>
+    class LayerReadyMessage : public OriginMessage<IVolumeSlicer>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     public:
       class IRendererFactory : public boost::noncopyable
       {
@@ -96,8 +100,10 @@
     };
 
 
-    class LayerErrorMessage : public OriginMessage<MessageType_VolumeSlicer_LayerError, IVolumeSlicer>
+    class LayerErrorMessage : public OriginMessage<IVolumeSlicer>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       const CoordinateSystem3D&  slice_;
 
--- a/Framework/Messages/ICallable.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Messages/ICallable.h	Mon May 13 15:22:08 2019 +0200
@@ -41,7 +41,8 @@
 
     virtual void Apply(const IMessage& message) = 0;
 
-    virtual MessageType GetMessageType() const = 0;
+    virtual const MessageIdentifier& GetMessageIdentifier() = 0;
+
     virtual IObserver* GetObserver() const = 0;
   };
 
@@ -58,8 +59,8 @@
   private:
     typedef void (TObserver::* MemberFunction) (const TMessage&);
 
-    TObserver&      observer_;
-    MemberFunction  function_;
+    TObserver&         observer_;
+    MemberFunction     function_;
 
   public:
     Callable(TObserver& observer,
@@ -79,9 +80,9 @@
       ApplyInternal(dynamic_cast<const TMessage&>(message));
     }
 
-    virtual MessageType GetMessageType() const
+    virtual const MessageIdentifier& GetMessageIdentifier()
     {
-      return static_cast<MessageType>(TMessage::Type);
+      return TMessage::GetStaticIdentifier();
     }
 
     virtual IObserver* GetObserver() const
@@ -115,11 +116,6 @@
       lambda_(dynamic_cast<const TMessage&>(message));
     }
 
-    virtual MessageType GetMessageType() const
-    {
-      return static_cast<MessageType>(TMessage::Type);
-    }
-
     virtual IObserver* GetObserver() const
     {
       return &observer_;
--- a/Framework/Messages/IMessage.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Messages/IMessage.h	Mon May 13 15:22:08 2019 +0200
@@ -21,79 +21,79 @@
 
 #pragma once
 
-#include "../StoneEnumerations.h"
+#include <boost/noncopyable.hpp>
 
-#include <boost/noncopyable.hpp>
+#include <string.h>
 
 namespace OrthancStone 
 {
-  // base message that are exchanged between IObservable and IObserver
-  class IMessage : public boost::noncopyable
+  class MessageIdentifier
   {
   private:
-    MessageType messageType_;
-    
-  protected:
-    IMessage(MessageType messageType) :
-      messageType_(messageType)
+    const char*  file_;
+    int          line_;
+
+  public:
+    MessageIdentifier(const char* file,
+                      int line) :
+      file_(file),
+      line_(line)
+    {
+    }
+
+    MessageIdentifier() :
+      file_(NULL),
+      line_(0)
     {
     }
+
+    bool operator< (const MessageIdentifier& other) const
+    {
+      if (file_ == NULL)
+      {
+        return false;
+      }
+      else if (line_ != other.line_)
+      {
+        return line_ < other.line_;
+      }
+      else
+      {
+        return strcmp(file_, other.file_) < 0;
+      }
+    }
+  };
+
     
+  /**
+   * Base messages that are exchanged between IObservable and
+   * IObserver. Messages are distinguished by the "__FILE__" and
+   * "__LINE__" macro, as in "Orthanc::SQLite::StatementId".
+   **/
+  class IMessage : public boost::noncopyable
+  {
   public:
     virtual ~IMessage()
     {
     }
 
-    virtual MessageType GetType() const
-    {
-      return messageType_;
-    }
+    virtual const MessageIdentifier& GetIdentifier() const = 0;
   };
 
 
-  // base class to derive from to implement your own messages
-  // it handles the message type for you
-  template <MessageType type>
-  class BaseMessage : public IMessage
-  {
-  public:
-    enum
-    {
-      Type = type
-    };
-
-    BaseMessage() :
-      IMessage(static_cast<MessageType>(Type))
-    {
-    }
-  };
-  
-
-  // simple message implementation when no payload is needed
-  // sample usage:
-  // typedef NoPayloadMessage<MessageType_VolumeSlicer_GeometryReady> GeometryReadyMessage;
-  template <MessageType type>
-  class NoPayloadMessage : public BaseMessage<type>
-  {
-  public:
-    NoPayloadMessage() :
-      BaseMessage<type>()
-    {
-    }
-  };
-
-  // simple message implementation when no payload is needed but the origin is required
-  // sample usage:
-  // typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage;
-  template <MessageType type, typename TOrigin>
-  class OriginMessage : public BaseMessage<type>
+  /**
+   * Simple message implementation when no payload is needed but the
+   * origin is required. Sample usage:
+   * typedef OriginMessage<OrthancSlicesLoader> SliceGeometryErrorMessage;
+   **/
+  template <typename TOrigin>
+  class OriginMessage : public IMessage
   {
   private:
-    const TOrigin& origin_;
+    const TOrigin&  origin_;
 
   public:
     OriginMessage(const TOrigin& origin) :
-      BaseMessage<type>(),
       origin_(origin)
     {
     }
@@ -104,3 +104,36 @@
     }
   };
 }
+
+
+#define ORTHANC_STONE_MESSAGE(FILE, LINE)                               \
+  public:                                                               \
+  static const ::OrthancStone::MessageIdentifier& GetStaticIdentifier() \
+  {                                                                     \
+    static const ::OrthancStone::MessageIdentifier id(FILE, LINE);      \
+    return id;                                                          \
+  }                                                                     \
+                                                                        \
+  virtual const ::OrthancStone::MessageIdentifier& GetIdentifier() const \
+  {                                                                     \
+    return GetStaticIdentifier();                                       \
+  }
+
+
+#define ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(FILE, LINE, NAME, ORIGIN)   \
+  class NAME : public ::OrthancStone::OriginMessage<ORIGIN>             \
+  {                                                                     \
+    ORTHANC_STONE_MESSAGE(FILE, LINE);                                  \
+                                                                        \
+    NAME(const ORIGIN& origin) :                                        \
+      OriginMessage(origin)                                             \
+    {                                                                   \
+    }                                                                   \
+  };
+
+
+#define ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(FILE, LINE, NAME)            \
+  class NAME : public ::OrthancStone::IMessage                          \
+  {                                                                     \
+    ORTHANC_STONE_MESSAGE(FILE, LINE);                                  \
+  };
--- a/Framework/Messages/IObservable.cpp	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Messages/IObservable.cpp	Mon May 13 15:22:08 2019 +0200
@@ -59,9 +59,8 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
     
-    MessageType messageType = callable->GetMessageType();
-
-    callables_[messageType].insert(callable);
+    const MessageIdentifier& id = callable->GetMessageIdentifier();
+    callables_[id].insert(callable);
   }
 
   void IObservable::Unregister(IObserver *observer)
@@ -87,7 +86,7 @@
   void IObservable::EmitMessageInternal(const IObserver* receiver,
                                         const IMessage& message)
   {
-    Callables::const_iterator found = callables_.find(message.GetType());
+    Callables::const_iterator found = callables_.find(message.GetIdentifier());
 
     if (found != callables_.end())
     {
--- a/Framework/Messages/IObservable.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Messages/IObservable.h	Mon May 13 15:22:08 2019 +0200
@@ -35,8 +35,9 @@
   class IObservable : public boost::noncopyable
   {
   private:
-    typedef std::map<int, std::set<ICallable*> >  Callables;
-    typedef std::set<IMessageForwarder*>          Forwarders;
+    typedef std::map<MessageIdentifier, std::set<ICallable*> >  Callables;
+
+    typedef std::set<IMessageForwarder*>     Forwarders;
 
     MessageBroker&  broker_;
     Callables       callables_;
--- a/Framework/Radiography/RadiographyLayer.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Radiography/RadiographyLayer.h	Mon May 13 15:22:08 2019 +0200
@@ -55,18 +55,7 @@
     friend class RadiographyScene;
 
   public:
-    class LayerEditedMessage :
-        public OriginMessage<MessageType_RadiographyLayer_Edited, RadiographyLayer>
-    {
-    private:
-
-    public:
-      LayerEditedMessage(const RadiographyLayer& origin) :
-        OriginMessage(origin)
-      {
-      }
-    };
-
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, LayerEditedMessage, RadiographyLayer);
 
     class Geometry
     {
--- a/Framework/Radiography/RadiographyScene.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.h	Mon May 13 15:22:08 2019 +0200
@@ -37,9 +37,10 @@
       public IObservable
   {
   public:
-    class GeometryChangedMessage :
-        public OriginMessage<MessageType_RadiographyScene_GeometryChanged, RadiographyScene>
+    class GeometryChangedMessage : public OriginMessage<RadiographyScene>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       RadiographyLayer&        layer_;
 
@@ -57,9 +58,10 @@
       }
     };
 
-    class ContentChangedMessage :
-        public OriginMessage<MessageType_RadiographyScene_ContentChanged, RadiographyScene>
+    class ContentChangedMessage : public OriginMessage<RadiographyScene>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       RadiographyLayer&        layer_;
 
@@ -77,9 +79,10 @@
       }
     };
 
-    class LayerEditedMessage :
-        public OriginMessage<MessageType_RadiographyScene_LayerEdited, RadiographyScene>
+    class LayerEditedMessage : public OriginMessage<RadiographyScene>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       const RadiographyLayer&        layer_;
 
@@ -95,20 +98,12 @@
       {
         return layer_;
       }
-
     };
 
-    class WindowingChangedMessage :
-        public OriginMessage<MessageType_RadiographyScene_WindowingChanged, RadiographyScene>
-    {
 
-    public:
-      WindowingChangedMessage(const RadiographyScene& origin) :
-        OriginMessage(origin)
-      {
-      }
-    };
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, WindowingChangedMessage, RadiographyScene);
 
+    
     class LayerAccessor : public boost::noncopyable
     {
     private:
--- a/Framework/Scene2D/ColorSceneLayer.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Scene2D/ColorSceneLayer.h	Mon May 13 15:22:08 2019 +0200
@@ -35,17 +35,20 @@
     uint8_t  green_;
     uint8_t  blue_;
     uint64_t revision_;
+
   protected:
     void BumpRevision()
     {
-      // this is *not* thread-safe!!!
+      // this is *not* thread-safe!!!  => (SJO) no problem, Stone assumes mono-threading
       revision_++;
     }
+
   public:
     ColorSceneLayer() :
       red_(255),
       green_(255),
-      blue_(255)
+      blue_(255),
+      revision_(0)
     {
     }
 
--- a/Framework/Scene2D/Scene2D.cpp	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Scene2D/Scene2D.cpp	Mon May 13 15:22:08 2019 +0200
@@ -102,8 +102,8 @@
   void Scene2D::SetLayer(int depth,
                          ISceneLayer* layer)  // Takes ownership
   {
-    //LOG(INFO) << "SetLayer(" << depth << ", " <<
-    //  reinterpret_cast<intptr_t>(layer) << ")";
+    LOG(INFO) << "SetLayer(" << depth << ", " <<
+      reinterpret_cast<intptr_t>(layer) << ")";
     std::auto_ptr<Item> item(new Item(layer, layerCounter_++));
 
     if (layer == NULL)
--- a/Framework/StoneEnumerations.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/StoneEnumerations.h	Mon May 13 15:22:08 2019 +0200
@@ -115,74 +115,6 @@
     BitmapAnchor_TopRight
   };
 
-  enum MessageType
-  {
-    MessageType_Widget_GeometryChanged,
-    MessageType_Widget_ContentChanged,
-
-    MessageType_VolumeSlicer_GeometryReady,   // instance tags have been loaded
-    MessageType_VolumeSlicer_GeometryError,
-    MessageType_VolumeSlicer_ContentChanged,
-    MessageType_VolumeSlicer_SliceChanged,
-    MessageType_VolumeSlicer_LayerReady,      // layer is ready to be rendered
-    MessageType_VolumeSlicer_LayerError,
-
-    MessageType_DicomSeriesVolumeSlicer_FrameReady,      // pixels data of the frame have been loaded
-
-    MessageType_SliceViewerWidget_DisplayedSlice,  // The displayed slice has changed
-
-    MessageType_SliceLoader_GeometryReady,
-    MessageType_SliceLoader_GeometryError,
-    MessageType_SliceLoader_ImageReady,
-    MessageType_SliceLoader_ImageError,
-
-    MessageType_VolumeLoader_GeometryReady,
-    MessageType_VolumeLoader_GeometryError,
-    MessageType_VolumeLoader_ContentChanged,  // Content of several slices in the loader has changed
-
-    MessageType_SlicedVolume_GeometryReady,
-    MessageType_SlicedVolume_GeometryError,
-    MessageType_SlicedVolume_VolumeReady,
-    MessageType_SlicedVolume_ContentChanged,
-    MessageType_SlicedVolume_SliceContentChanged,
-
-    MessageType_HttpRequestSuccess,
-    MessageType_HttpRequestError,
-
-    MessageType_OrthancApi_InternalGetJsonResponseReady,
-    MessageType_OrthancApi_InternalGetJsonResponseError,
-
-    MessageType_OrthancApi_GenericGetJson_Ready,
-    MessageType_OrthancApi_GenericGetBinary_Ready,
-    MessageType_OrthancApi_GenericHttpError_Ready,
-    MessageType_OrthancApi_GenericEmptyResponse_Ready,
-
-    MessageType_RadiographyScene_GeometryChanged,
-    MessageType_RadiographyScene_ContentChanged,
-    MessageType_RadiographyScene_LayerEdited,
-    MessageType_RadiographyScene_WindowingChanged,
-
-    MessageType_RadiographyLayer_Edited,
-
-    MessageType_ViewportChanged,
-
-    MessageType_Timeout,
-
-    // used in unit tests only
-    MessageType_Test1,
-    MessageType_Test2,
-
-
-
-    MessageType_OrthancRestApiCommand,
-    MessageType_GetOrthancImageCommand,
-    MessageType_GetOrthancWebViewerJpegCommand,
-    MessageType_OracleCommandExceptionMessage,
-    
-    MessageType_CustomMessage // Custom messages ids ust be greater than this (this one must remain in last position)
-  };
-
-  
   enum ControlPointType
   {
     ControlPoint_TopLeftCorner = 0,
--- a/Framework/Toolbox/IDelayedCallExecutor.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Toolbox/IDelayedCallExecutor.h	Mon May 13 15:22:08 2019 +0200
@@ -39,8 +39,7 @@
     MessageBroker& broker_;
     
   public:
-
-    typedef NoPayloadMessage<MessageType_Timeout> TimeoutMessage;
+    ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage);
 
     IDelayedCallExecutor(MessageBroker& broker) :
       broker_(broker)
--- a/Framework/Toolbox/IWebService.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Toolbox/IWebService.h	Mon May 13 15:22:08 2019 +0200
@@ -45,8 +45,10 @@
   public:
     typedef std::map<std::string, std::string> HttpHeaders;
 
-    class HttpRequestSuccessMessage : public BaseMessage<MessageType_HttpRequestSuccess>
+    class HttpRequestSuccessMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       const std::string&             uri_;
       const void*                    answer_;
@@ -97,8 +99,10 @@
     };
     
 
-    class HttpRequestErrorMessage : public BaseMessage<MessageType_HttpRequestError>
+    class HttpRequestErrorMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       const std::string&              uri_;
       const Orthanc::IDynamicObject*  payload_;
--- a/Framework/Toolbox/OrthancApiClient.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Toolbox/OrthancApiClient.h	Mon May 13 15:22:08 2019 +0200
@@ -35,9 +35,10 @@
       public IObserver
   {
   public:
-    class JsonResponseReadyMessage :
-        public BaseMessage<MessageType_OrthancApi_GenericGetJson_Ready>
+    class JsonResponseReadyMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       const std::string&              uri_;
       const Json::Value&              json_;
@@ -72,9 +73,10 @@
     };
     
 
-    class BinaryResponseReadyMessage :
-        public BaseMessage<MessageType_OrthancApi_GenericGetBinary_Ready>
+    class BinaryResponseReadyMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       const std::string&              uri_;
       const void*                     answer_;
@@ -117,9 +119,10 @@
     };
 
 
-    class EmptyResponseReadyMessage :
-        public BaseMessage<MessageType_OrthancApi_GenericEmptyResponse_Ready>
+    class EmptyResponseReadyMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
     private:
       const std::string&              uri_;
       const Orthanc::IDynamicObject*  payload_;
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Mon May 13 15:22:08 2019 +0200
@@ -35,13 +35,14 @@
   class OrthancSlicesLoader : public IObservable, public IObserver
   {
   public:
-
-    typedef OriginMessage<MessageType_SliceLoader_GeometryReady, OrthancSlicesLoader> SliceGeometryReadyMessage;
-    typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage;
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryErrorMessage, OrthancSlicesLoader);
 
-    class SliceImageReadyMessage :
-      public OriginMessage<MessageType_SliceLoader_ImageReady, OrthancSlicesLoader>
+    
+    class SliceImageReadyMessage : public OriginMessage<OrthancSlicesLoader>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       unsigned int                   sliceIndex_;
       const Slice&                   slice_;
@@ -84,9 +85,10 @@
     };
     
 
-    class SliceImageErrorMessage : 
-      public OriginMessage<MessageType_SliceLoader_ImageError, OrthancSlicesLoader>
+    class SliceImageErrorMessage : public OriginMessage<OrthancSlicesLoader>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       const Slice&       slice_;
       unsigned int       sliceIndex_;
--- a/Framework/Viewport/IViewport.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Viewport/IViewport.h	Mon May 13 15:22:08 2019 +0200
@@ -35,7 +35,7 @@
   class IViewport : public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_ViewportChanged, IViewport> ViewportChangedMessage;
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport);
 
     IViewport(MessageBroker& broker) :
       IObservable(broker)
--- a/Framework/Volumes/ISlicedVolume.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Volumes/ISlicedVolume.h	Mon May 13 15:22:08 2019 +0200
@@ -29,14 +29,16 @@
   class ISlicedVolume : public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_SlicedVolume_ContentChanged, ISlicedVolume> ContentChangedMessage;
-    typedef OriginMessage<MessageType_SlicedVolume_GeometryError, ISlicedVolume> GeometryErrorMessage;
-    typedef OriginMessage<MessageType_SlicedVolume_GeometryReady, ISlicedVolume> GeometryReadyMessage;
-    typedef OriginMessage<MessageType_SlicedVolume_VolumeReady, ISlicedVolume> VolumeReadyMessage;
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, ISlicedVolume);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, ISlicedVolume);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, ISlicedVolume);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeReadyMessage, ISlicedVolume);
 
-    class SliceContentChangedMessage :
-      public OriginMessage<MessageType_SlicedVolume_SliceContentChanged, ISlicedVolume>
+
+    class SliceContentChangedMessage : public OriginMessage<ISlicedVolume>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       size_t        sliceIndex_;
       const Slice&  slice_;
--- a/Framework/Volumes/IVolumeLoader.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Volumes/IVolumeLoader.h	Mon May 13 15:22:08 2019 +0200
@@ -28,9 +28,9 @@
   class IVolumeLoader : public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_VolumeLoader_GeometryReady, IVolumeLoader> GeometryReadyMessage;
-    typedef OriginMessage<MessageType_VolumeLoader_GeometryError, IVolumeLoader> GeometryErrorMessage;
-    typedef OriginMessage<MessageType_VolumeLoader_ContentChanged, IVolumeLoader> ContentChangedMessage;
+    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(MessageBroker& broker) :
       IObservable(broker)
--- a/Framework/Widgets/SliceViewerWidget.h	Mon May 13 15:12:56 2019 +0200
+++ b/Framework/Widgets/SliceViewerWidget.h	Mon May 13 15:22:08 2019 +0200
@@ -36,12 +36,15 @@
     public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_Widget_GeometryChanged, SliceViewerWidget> GeometryChangedMessage;
-    typedef OriginMessage<MessageType_Widget_ContentChanged, SliceViewerWidget> ContentChangedMessage;
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryChangedMessage, SliceViewerWidget);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, SliceViewerWidget);
+
 
     // TODO - Use this message in ReferenceLineSource
-    class DisplayedSliceMessage : public OriginMessage<MessageType_SliceViewerWidget_DisplayedSlice, SliceViewerWidget>
+    class DisplayedSliceMessage : public OriginMessage<SliceViewerWidget>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       const Slice& slice_;
 
--- a/Samples/Sdl/Loader.cpp	Mon May 13 15:12:56 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Mon May 13 15:22:08 2019 +0200
@@ -28,17 +28,23 @@
 #include "../../Framework/Volumes/ImageBuffer3D.h"
 
 // From Orthanc framework
+#include <Core/Compression/GzipCompressor.h>
+#include <Core/Compression/ZlibCompressor.h>
 #include <Core/DicomFormat/DicomArray.h>
 #include <Core/DicomFormat/DicomImageInformation.h>
 #include <Core/HttpClient.h>
 #include <Core/IDynamicObject.h>
 #include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PamReader.h>
+#include <Core/Images/PngReader.h>
 #include <Core/Images/PngWriter.h>
 #include <Core/Logging.h>
 #include <Core/MultiThreading/SharedMessageQueue.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
+#include <Core/SystemToolbox.h>
 
 #include <json/reader.h>
 #include <json/value.h>
@@ -56,7 +62,9 @@
   public:
     enum Type
     {
-      Type_OrthancApi
+      Type_OrthancRestApi,
+      Type_GetOrthancImage,
+      Type_GetOrthancWebViewerJpeg
     };
 
     virtual ~IOracleCommand()
@@ -131,20 +139,56 @@
 
 
 
+  class OracleCommandExceptionMessage : public OrthancStone::IMessage
+  {
+    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+  private:
+    const IOracleCommand&       command_;
+    Orthanc::OrthancException   exception_;
+
+  public:
+    OracleCommandExceptionMessage(const IOracleCommand& command,
+                                  const Orthanc::OrthancException& exception) :
+      command_(command),
+      exception_(exception)
+    {
+    }
+
+    OracleCommandExceptionMessage(const IOracleCommand& command,
+                                  const Orthanc::ErrorCode& error) :
+      command_(command),
+      exception_(error)
+    {
+    }
+
+    const IOracleCommand& GetCommand() const
+    {
+      return command_;
+    }
+    
+    const Orthanc::OrthancException& GetException() const
+    {
+      return exception_;
+    }
+  };
+  
+
   typedef std::map<std::string, std::string>  HttpHeaders;
 
-  class OrthancApiOracleCommand : public OracleCommandWithPayload
+  class OrthancRestApiCommand : public OracleCommandWithPayload
   {
   public:
-    class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess,   // TODO
-                                                              OrthancApiOracleCommand>
+    class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       HttpHeaders   headers_;
       std::string   answer_;
 
     public:
-      SuccessMessage(const OrthancApiOracleCommand& command,
+      SuccessMessage(const OrthancRestApiCommand& command,
                      const HttpHeaders& answerHeaders,
                      std::string& answer  /* will be swapped to avoid a memcpy() */) :
         OriginMessage(command),
@@ -174,27 +218,6 @@
     };
 
 
-    class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError,   // TODO
-                                                              OrthancApiOracleCommand>
-    {
-    private:
-      Orthanc::HttpStatus  status_;
-
-    public:
-      FailureMessage(const OrthancApiOracleCommand& command,
-                     Orthanc::HttpStatus status) :
-        OriginMessage(command),
-        status_(status)
-      {
-      }
-
-      Orthanc::HttpStatus GetHttpStatus() const
-      {
-        return status_;
-      }
-    };
-
-
   private:
     Orthanc::HttpMethod  method_;
     std::string          uri_;
@@ -203,10 +226,10 @@
     unsigned int         timeout_;
 
     std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> >  failureCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
 
   public:
-    OrthancApiOracleCommand() :
+    OrthancRestApiCommand() :
       method_(Orthanc::HttpMethod_Get),
       uri_("/"),
       timeout_(10)
@@ -215,7 +238,7 @@
 
     virtual Type GetType() const
     {
-      return Type_OrthancApi;
+      return Type_OrthancRestApi;
     }
 
     void SetMethod(Orthanc::HttpMethod method)
@@ -239,6 +262,11 @@
       body_ = writer.write(json);
     }
 
+    void SetHttpHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
     void SetHttpHeader(const std::string& key,
                        const std::string& value)
     {
@@ -286,6 +314,425 @@
 
 
 
+
+  class GetOrthancImageCommand : public OracleCommandWithPayload
+  {
+  public:
+    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+      Orthanc::MimeType                      mime_;
+
+    public:
+      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);
+        }
+      }
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+
+      Orthanc::MimeType GetMimeType() const
+      {
+        return mime_;
+      }
+    };
+
+
+  private:
+    std::string    uri_;
+    HttpHeaders    headers_;
+    unsigned int   timeout_;
+
+    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
+
+  public:
+    GetOrthancImageCommand() :
+      uri_("/"),
+      timeout_(10)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancImage;
+    }
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const OrthancStone::IObserver& receiver,
+                           const std::string& answer,
+                           const HttpHeaders& answerHeaders) const
+    {
+      Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
+
+      for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+           it != answerHeaders.end(); ++it)
+      {
+        std::string s;
+        Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+        if (s == "content-type")
+        {
+          contentType = Orthanc::StringToMimeType(it->second);
+          break;
+        }
+      }
+
+      std::auto_ptr<Orthanc::ImageAccessor> image;
+
+      switch (contentType)
+      {
+        case Orthanc::MimeType_Png:
+        {
+          image.reset(new Orthanc::PngReader);
+          dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
+          break;
+        }
+
+        case Orthanc::MimeType_Pam:
+        {
+          image.reset(new Orthanc::PamReader);
+          dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
+          break;
+        }
+
+        case Orthanc::MimeType_Jpeg:
+        {
+          image.reset(new Orthanc::JpegReader);
+          dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                          "Unsupported HTTP Content-Type for an image: " + 
+                                          std::string(Orthanc::EnumerationToString(contentType)));
+      }
+
+      SuccessMessage message(*this, image.release(), contentType);
+      emitter.EmitMessage(receiver, message);
+    }
+  };
+
+
+
+  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
+  {
+  public:
+    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+
+    public:
+      SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
+                     Orthanc::ImageAccessor* image) :   // Takes ownership
+        OriginMessage(command),
+        image_(image)
+      {
+        if (image == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+      }
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+    };
+
+  private:
+    std::string           instanceId_;
+    unsigned int          frame_;
+    unsigned int          quality_;
+    HttpHeaders           headers_;
+    unsigned int          timeout_;
+    Orthanc::PixelFormat  expectedFormat_;
+
+    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
+
+  public:
+    GetOrthancWebViewerJpegCommand() :
+      frame_(0),
+      quality_(95),
+      timeout_(10),
+      expectedFormat_(Orthanc::PixelFormat_Grayscale8)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancWebViewerJpeg;
+    }
+
+    void SetExpectedFormat(Orthanc::PixelFormat format)
+    {
+      expectedFormat_ = format;
+    }
+
+    void SetInstance(const std::string& instanceId)
+    {
+      instanceId_ = instanceId;
+    }
+
+    void SetFrame(unsigned int frame)
+    {
+      frame_ = frame;
+    }
+
+    void SetQuality(unsigned int quality)
+    {
+      if (quality <= 0 ||
+          quality > 100)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        quality_ = quality;
+      }
+    }
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::PixelFormat GetExpectedFormat() const
+    {
+      return expectedFormat_;
+    }
+
+    const std::string& GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      return frame_;
+    }
+
+    unsigned int GetQuality() const
+    {
+      return quality_;
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    std::string GetUri() const
+    {
+      return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
+              "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
+    }
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const OrthancStone::IObserver& receiver,
+                           const std::string& answer) const
+    {
+      // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
+      
+      Json::Value encoded;
+
+      {
+        Json::Reader reader;
+        if (!reader.parse(answer, encoded))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      }
+
+      if (encoded.type() != Json::objectValue ||
+          !encoded.isMember("Orthanc") ||
+          encoded["Orthanc"].type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      const Json::Value& info = encoded["Orthanc"];
+      if (!info.isMember("PixelData") ||
+          !info.isMember("Stretched") ||
+          !info.isMember("Compression") ||
+          info["Compression"].type() != Json::stringValue ||
+          info["PixelData"].type() != Json::stringValue ||
+          info["Stretched"].type() != Json::booleanValue ||
+          info["Compression"].asString() != "Jpeg")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      bool isSigned = false;
+      bool isStretched = info["Stretched"].asBool();
+    
+      if (info.isMember("IsSigned"))
+      {
+        if (info["IsSigned"].type() != Json::booleanValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          isSigned = info["IsSigned"].asBool();
+        }
+      }
+    
+      std::auto_ptr<Orthanc::ImageAccessor> reader;
+    
+      {
+        std::string jpeg;
+        Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+      
+        reader.reset(new Orthanc::JpegReader);
+        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
+      }
+    
+      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+      {
+        if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      
+        if (isSigned || isStretched)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          SuccessMessage message(*this, reader.release());
+          emitter.EmitMessage(receiver, message);
+          return;
+        }
+      }
+    
+      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      if (!isStretched)
+      {
+        if (expectedFormat_ != reader->GetFormat())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          SuccessMessage message(*this, reader.release());
+          emitter.EmitMessage(receiver, message);
+          return;
+        }
+      }
+    
+      int32_t stretchLow = 0;
+      int32_t stretchHigh = 0;
+    
+      if (!info.isMember("StretchLow") ||
+          !info.isMember("StretchHigh") ||
+          info["StretchLow"].type() != Json::intValue ||
+          info["StretchHigh"].type() != Json::intValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      stretchLow = info["StretchLow"].asInt();
+      stretchHigh = info["StretchHigh"].asInt();
+    
+      if (stretchLow < -32768 ||
+          stretchHigh > 65535 ||
+          (stretchLow < 0 && stretchHigh > 32767))
+      {
+        // This range cannot be represented with a uint16_t or an int16_t
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      // 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));
+
+      Orthanc::ImageProcessing::Convert(*image, *reader);
+      reader.reset();
+    
+      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    
+      if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
+      {
+        float offset = static_cast<float>(stretchLow) / scaling;
+        Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+      }
+    
+      SuccessMessage message(*this, image.release());
+      emitter.EmitMessage(receiver, message);
+    }
+  };
+
+
+
+
+
   class NativeOracle : public IOracle
   {
   private:
@@ -336,53 +783,115 @@
     std::vector<boost::thread*>    workers_;
 
 
+    void CopyHttpHeaders(Orthanc::HttpClient& client,
+                         const HttpHeaders& headers)
+    {
+      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
+      {
+        client.AddHeader(it->first, it->second);
+      }
+    }
+
+
+    void DecodeAnswer(std::string& answer,
+                      const HttpHeaders& headers)
+    {
+      Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
+
+      for (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());
+      }
+    }
+
+
     void Execute(const OrthancStone::IObserver& receiver,
-                 const OrthancApiOracleCommand& command)
+                 const OrthancRestApiCommand& command)
     {
-      Orthanc::HttpClient  client(orthanc_, command.GetUri());
+      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());
       }
-      
-      {
-        const HttpHeaders& headers = command.GetHttpHeaders();
-        for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
-        {
-          client.AddHeader(it->first, it->second);
-        }
-      }
+
+      std::string answer;
+      HttpHeaders answerHeaders;
+      client.ApplyAndThrowException(answer, answerHeaders);
+
+      DecodeAnswer(answer, answerHeaders);
+
+      OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
+      emitter_.EmitMessage(receiver, message);
+    }
+
+
+    void Execute(const OrthancStone::IObserver& receiver,
+                 const GetOrthancImageCommand& command)
+    {
+      Orthanc::HttpClient client(orthanc_, command.GetUri());
+      client.SetTimeout(command.GetTimeout());
+
+      CopyHttpHeaders(client, command.GetHttpHeaders());
 
       std::string answer;
       HttpHeaders answerHeaders;
-
-      bool success;
-      try
-      {
-        success = client.Apply(answer, answerHeaders);
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        success = false;
-      }
+      client.ApplyAndThrowException(answer, answerHeaders);
 
-      if (success)
-      {
-        OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer);
-        emitter_.EmitMessage(receiver, message);
-      }
-      else
-      {
-        OrthancApiOracleCommand::FailureMessage message(command, client.GetLastStatus());
-        emitter_.EmitMessage(receiver, message);
-      }
+      DecodeAnswer(answer, answerHeaders);
+
+      command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders);
     }
 
 
+    void Execute(const OrthancStone::IObserver& receiver,
+                 const GetOrthancWebViewerJpegCommand& command)
+    {
+      Orthanc::HttpClient client(orthanc_, command.GetUri());
+      client.SetTimeout(command.GetTimeout());
+
+      CopyHttpHeaders(client, command.GetHttpHeaders());
+
+      std::string answer;
+      HttpHeaders answerHeaders;
+      client.ApplyAndThrowException(answer, answerHeaders);
+
+      DecodeAnswer(answer, answerHeaders);
+
+      command.ProcessHttpAnswer(emitter_, receiver, answer);
+    }
+
 
     void Step()
     {
@@ -390,15 +899,27 @@
 
       if (object.get() != NULL)
       {
+        printf("===========================> REQUEST\n");
+        
         const Item& item = dynamic_cast<Item&>(*object);
 
         try
         {
           switch (item.GetCommand().GetType())
           {
-            case IOracleCommand::Type_OrthancApi:
+            case IOracleCommand::Type_OrthancRestApi:
+              Execute(item.GetReceiver(), 
+                      dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
+              break;
+
+            case IOracleCommand::Type_GetOrthancImage:
               Execute(item.GetReceiver(), 
-                      dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand()));
+                      dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
+              break;
+
+            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+              Execute(item.GetReceiver(), 
+                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
               break;
 
             default:
@@ -408,10 +929,13 @@
         catch (Orthanc::OrthancException& e)
         {
           LOG(ERROR) << "Exception within the oracle: " << e.What();
+          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
         }
         catch (...)
         {
           LOG(ERROR) << "Native exception within the oracle";
+          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
+                               (item.GetCommand(), Orthanc::ErrorCode_InternalError));
         }
       }
     }
@@ -562,8 +1086,15 @@
     virtual void EmitMessage(const OrthancStone::IObserver& observer,
                              const OrthancStone::IMessage& message)
     {
-      boost::unique_lock<boost::shared_mutex>  lock(mutex_);
-      oracleObservable_.EmitMessage(observer, 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();
+      }
     }
 
 
@@ -802,12 +1333,15 @@
 
     OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
     {
-      if (frame >= imageInformation_.GetNumberOfFrames())
+      if (frame == 0)
+      {
+        return geometry_;
+      }
+      else if (frame >= imageInformation_.GetNumberOfFrames())
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
-
-      if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
+      else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
       {
         if (frame >= frameOffsets_.size())
         {
@@ -819,6 +1353,10 @@
           geometry_.GetAxisX(),
           geometry_.GetAxisY());
       }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
     }
 
     bool FrameContainsPlane(unsigned int frame,
@@ -921,10 +1459,10 @@
     class MessageHandler : public Orthanc::IDynamicObject
     {
     public:
-      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0;
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const = 0;
     };
 
-    void Handle(const OrthancApiOracleCommand::SuccessMessage& message)
+    void Handle(const OrthancRestApiCommand::SuccessMessage& message)
     {
       dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message);
     }
@@ -941,7 +1479,7 @@
       {
       }
 
-      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
         Json::Value value;
         message.ParseJsonBody(value);
@@ -975,7 +1513,7 @@
       {
       }
 
-      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
         Json::Value value;
         message.ParseJsonBody(value);
@@ -1003,7 +1541,7 @@
       active_(false)
     {
       oracle.RegisterObserverCallback(
-        new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage>
+        new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancRestApiCommand::SuccessMessage>
         (*this, &AxialVolumeOrthancLoader::Handle));
     }
 
@@ -1017,7 +1555,7 @@
 
       active_ = true;
 
-      std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
+      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
       command->SetUri("/series/" + seriesId + "/instances-tags");
       command->SetPayload(new LoadSeriesGeometryHandler(*this));
 
@@ -1039,7 +1577,7 @@
 
       // TODO => Should be part of a second call if needed
 
-      std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
+      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
       command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
       command->SetPayload(new LoadInstanceGeometryHandler(*this));
 
@@ -1054,7 +1592,7 @@
 class Toto : public OrthancStone::IObserver
 {
 private:
-  void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message)
+  void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message)
   {
     Json::Value v;
     message.ParseJsonBody(v);
@@ -1062,9 +1600,30 @@
     printf("ICI [%s]\n", v.toStyledString().c_str());
   }
 
-  void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message)
+  void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
+  {
+    printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
+  }
+
+  void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  {
+    printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
+  }
+
+  void Handle(const Refactoring::OracleCommandExceptionMessage& message)
   {
-    printf("ERROR %d\n", message.GetHttpStatus());
+    printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());
+
+    switch (message.GetCommand().GetType())
+    {
+      case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg:
+        printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&>
+               (message.GetCommand()).GetUri().c_str());
+        break;
+      
+      default:
+        break;
+    }
   }
 
 public:
@@ -1073,7 +1632,19 @@
   {
     oracle.RegisterObserverCallback
       (new OrthancStone::Callable
-       <Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracle.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracle.RegisterObserverCallback
+      (new OrthancStone::Callable
+      <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracle.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle));
   }
 };
 
@@ -1101,12 +1672,13 @@
 
   oracle.Start();
 
+  if (1)
   {
     Json::Value v = Json::objectValue;
     v["Level"] = "Series";
     v["Query"] = Json::objectValue;
 
-    std::auto_ptr<Refactoring::OrthancApiOracleCommand>  command(new Refactoring::OrthancApiOracleCommand);
+    std::auto_ptr<Refactoring::OrthancRestApiCommand>  command(new Refactoring::OrthancRestApiCommand);
     command->SetMethod(Orthanc::HttpMethod_Post);
     command->SetUri("/tools/find");
     command->SetBody(v);
@@ -1114,11 +1686,64 @@
     oracle.Schedule(*toto, command.release());
   }
   
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept-Encoding", "gzip");
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+    oracle.Schedule(*toto, command.release());
+  }
+
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand>  command(new Refactoring::GetOrthancWebViewerJpegCommand);
+    command->SetHttpHeader("Accept-Encoding", "gzip");
+    command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
+    command->SetQuality(90);
+    oracle.Schedule(*toto, command.release());
+  }
+
+
   // 2017-11-17-Anonymized
   loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
   loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
 
-  boost::this_thread::sleep(boost::posix_time::seconds(1));
+  LOG(WARNING) << "...Waiting for Ctrl-C...";
+  Orthanc::SystemToolbox::ServerBarrier();
+  //boost::this_thread::sleep(boost::posix_time::seconds(1));
 
   oracle.Stop();
 }
--- a/UnitTestsSources/TestMessageBroker.cpp	Mon May 13 15:12:56 2019 +0200
+++ b/UnitTestsSources/TestMessageBroker.cpp	Mon May 13 15:22:08 2019 +0200
@@ -34,32 +34,25 @@
   using namespace OrthancStone;
 
 
-  enum CustomMessageType
-  {
-    CustomMessageType_First = MessageType_CustomMessage + 1,
-
-    CustomMessageType_Completed,
-    CustomMessageType_Increment
-  };
-
-
   class MyObservable : public IObservable
   {
   public:
-    struct MyCustomMessage: public BaseMessage<(MessageType) CustomMessageType_Completed>
+    struct MyCustomMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
       int payload_;
 
-      MyCustomMessage(int payload)
-        : BaseMessage(),
-          payload_(payload)
-      {}
+      MyCustomMessage(int payload) :
+        payload_(payload)
+      {
+      }
     };
 
-    MyObservable(MessageBroker& broker)
-      : IObservable(broker)
-    {}
-
+    MyObservable(MessageBroker& broker) :
+      IObservable(broker)
+    {
+    }
   };
 
   class MyObserver : public IObserver
@@ -94,15 +87,18 @@
   class MyPromiseSource : public IObservable
   {
     Promise* currentPromise_;
+
   public:
-    struct MyPromiseMessage: public BaseMessage<MessageType_Test1>
+    struct MyPromiseMessage: public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
       int increment;
 
-      MyPromiseMessage(int increment)
-        : BaseMessage(),
-          increment(increment)
-      {}
+      MyPromiseMessage(int increment) :
+        increment(increment)
+      {
+      }
     };
 
     MyPromiseSource(MessageBroker& broker)