changeset 430:b85f635f1eb5 am-vsol-upgrade

added serialization for RadiographyScene
author am@osimis.io
date Thu, 29 Nov 2018 15:11:19 +0100
parents c7fb700a7d12 (diff) 3f9017db1738 (current diff)
children 26b90b110719
files Applications/Samples/SingleFrameEditorApplication.h Framework/Radiography/RadiographyAlphaLayer.cpp Framework/Radiography/RadiographyAlphaLayer.h Framework/Radiography/RadiographyDicomLayer.cpp Framework/Radiography/RadiographyDicomLayer.h Framework/Radiography/RadiographyLayer.cpp Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyLayerCropTracker.cpp Framework/Radiography/RadiographyLayerMoveTracker.cpp Framework/Radiography/RadiographyLayerResizeTracker.cpp Framework/Radiography/RadiographyLayerRotateTracker.cpp Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/Radiography/RadiographySceneReader.cpp Framework/Radiography/RadiographySceneReader.h Framework/Radiography/RadiographySceneWriter.cpp Framework/Radiography/RadiographySceneWriter.h Framework/Radiography/RadiographyTextLayer.cpp Framework/Radiography/RadiographyTextLayer.h Framework/StoneEnumerations.h Framework/Toolbox/OrthancApiClient.cpp Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 32 files changed, 1833 insertions(+), 1901 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/CMakeLists.txt	Fri Nov 23 16:06:23 2018 +0100
+++ b/Applications/Samples/CMakeLists.txt	Thu Nov 29 15:11:19 2018 +0100
@@ -193,7 +193,6 @@
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
-    ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker2.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
     )
 
--- a/Applications/Samples/SingleFrameEditorApplication.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Thu Nov 29 15:11:19 2018 +0100
@@ -31,6 +31,8 @@
 #include "../../Framework/Radiography/RadiographySceneCommand.h"
 #include "../../Framework/Radiography/RadiographyWidget.h"
 #include "../../Framework/Radiography/RadiographyWindowingTracker.h"
+#include "../../Framework/Radiography/RadiographySceneWriter.h"
+#include "../../Framework/Radiography/RadiographySceneReader.h"
 
 #include <Core/HttpClient.h>
 #include <Core/Images/FontRegistry.h>
@@ -48,8 +50,8 @@
   namespace Samples
   {
     class RadiographyEditorInteractor :
-      public IWorldSceneInteractor,
-      public IObserver
+        public IWorldSceneInteractor,
+        public IObserver
     {
     private:
       enum Tool
@@ -60,7 +62,7 @@
         Tool_Resize,
         Tool_Windowing
       };
-        
+
 
       StoneApplicationContext*  context_;
       UndoRedoStack             undoRedoStack_;
@@ -71,8 +73,8 @@
       {
         return 10.0;
       }
-    
-         
+
+
     public:
       RadiographyEditorInteractor(MessageBroker& broker) :
         IObserver(broker),
@@ -85,7 +87,7 @@
       {
         context_ = &context;
       }
-    
+
       virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget,
                                                           const ViewportGeometry& view,
                                                           MouseButton button,
@@ -101,16 +103,16 @@
         if (button == MouseButton_Left)
         {
           size_t selected;
-        
+
           if (tool_ == Tool_Windowing)
           {
             return new RadiographyWindowingTracker(
-              undoRedoStack_, widget.GetScene(),
-              viewportX, viewportY,
-              RadiographyWindowingTracker::Action_DecreaseWidth,
-              RadiographyWindowingTracker::Action_IncreaseWidth,
-              RadiographyWindowingTracker::Action_DecreaseCenter,
-              RadiographyWindowingTracker::Action_IncreaseCenter);
+                  undoRedoStack_, widget.GetScene(),
+                  viewportX, viewportY,
+                  RadiographyWindowingTracker::Action_DecreaseWidth,
+                  RadiographyWindowingTracker::Action_IncreaseWidth,
+                  RadiographyWindowingTracker::Action_DecreaseCenter,
+                  RadiographyWindowingTracker::Action_IncreaseCenter);
           }
           else if (!widget.LookupSelectedLayer(selected))
           {
@@ -133,23 +135,23 @@
             {
               switch (tool_)
               {
-                case Tool_Crop:
-                  return new RadiographyLayerCropTracker
+              case Tool_Crop:
+                return new RadiographyLayerCropTracker
                     (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner);
 
-                case Tool_Resize:
-                  return new RadiographyLayerResizeTracker
+              case Tool_Resize:
+                return new RadiographyLayerResizeTracker
                     (undoRedoStack_, widget.GetScene(), selected, x, y, corner,
                      (modifiers & KeyboardModifiers_Shift));
 
-                default:
-                  throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+              default:
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
               }
             }
             else
             {
               size_t layer;
-            
+
               if (widget.GetScene().LookupLayer(layer, x, y))
               {
                 widget.Select(layer);
@@ -158,7 +160,7 @@
               {
                 widget.Unselect();
               }
-            
+
               return NULL;
             }
           }
@@ -172,18 +174,18 @@
               {
                 switch (tool_)
                 {
-                  case Tool_Move:
-                    return new RadiographyLayerMoveTracker
+                case Tool_Move:
+                  return new RadiographyLayerMoveTracker
                       (undoRedoStack_, widget.GetScene(), layer, x, y,
                        (modifiers & KeyboardModifiers_Shift));
 
-                  case Tool_Rotate:
-                    return new RadiographyLayerRotateTracker
+                case Tool_Rotate:
+                  return new RadiographyLayerRotateTracker
                       (undoRedoStack_, widget.GetScene(), view, layer, x, y,
                        (modifiers & KeyboardModifiers_Shift));
-                
-                  default:
-                    break;
+
+                default:
+                  break;
                 }
 
                 return NULL;
@@ -232,14 +234,14 @@
              tool_ == Tool_Resize))
         {
           RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected);
-        
+
           Corner corner;
           if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
           {
             accessor.GetLayer().GetCorner(x, y, corner);
-          
+
             double z = 1.0 / view.GetZoom();
-          
+
             context.SetSourceColor(255, 0, 0);
             cairo_t* cr = context.GetObject();
             cairo_set_line_width(cr, 2.0 * z);
@@ -270,118 +272,141 @@
 
         switch (keyChar)
         {
-          case 'a':
-            widget.FitContent();
-            break;
+        case 'a':
+          widget.FitContent();
+          break;
+
+        case 'c':
+          tool_ = Tool_Crop;
+          break;
 
-          case 'c':
-            tool_ = Tool_Crop;
-            break;
+        case 'd':
+        {
+          // dump to json and reload
+          Json::Value snapshot;
+          RadiographySceneWriter writer;
+          writer.Write(snapshot, widget.GetScene());
 
-          case 'e':
-          {
-            Orthanc::DicomMap tags;
+          LOG(INFO) << "JSON export was successful: "
+                    << snapshot.toStyledString();
+
+          boost::shared_ptr<RadiographyScene> scene(new RadiographyScene(GetBroker()));
+          RadiographySceneReader reader(*scene, context_->GetOrthancApiClient());
+
+          Orthanc::FontRegistry fontRegistry;
+          fontRegistry.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
+
+          reader.SetFontRegistry(fontRegistry);
+          reader.Read(snapshot);
 
-            // Minimal set of tags to generate a valid CR image
-            tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false);
-            tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false);
-            tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false);
-            //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false);
-            tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false);
-            tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false);
-            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false);
-            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false);
-            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false);
-            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false);
-            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false);
-            tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false);
-            tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false);
-            tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false);
-            tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false);
-            tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false);
+          widget.SetScene(scene);
+        };break;
+
+        case 'e':
+        {
+          Orthanc::DicomMap tags;
 
-            if (context_ != NULL)
-            {
-              widget.GetScene().ExportDicom(context_->GetOrthancApiClient(),
-                                            tags, 0.1, 0.1, widget.IsInverted(),
-                                            widget.GetInterpolation(), EXPORT_USING_PAM);
-            }
-            
-            break;
+          // Minimal set of tags to generate a valid CR image
+          tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false);
+          tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false);
+          tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false);
+          //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false);
+          tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false);
+          tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false);
+          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false);
+          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false);
+          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false);
+          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false);
+          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false);
+          tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false);
+          tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false);
+          tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false);
+          tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false);
+          tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false);
+
+          if (context_ != NULL)
+          {
+            widget.GetScene().ExportDicom(context_->GetOrthancApiClient(),
+                                          tags, std::string(), 0.1, 0.1, widget.IsInverted(),
+                                          widget.GetInterpolation(), EXPORT_USING_PAM);
           }
 
-          case 'i':
-            widget.SwitchInvert();
-            break;
-        
-          case 'm':
-            tool_ = Tool_Move;
+          break;
+        }
+
+        case 'i':
+          widget.SwitchInvert();
+          break;
+
+        case 'm':
+          tool_ = Tool_Move;
+          break;
+
+        case 'n':
+        {
+          switch (widget.GetInterpolation())
+          {
+          case ImageInterpolation_Nearest:
+            LOG(INFO) << "Switching to bilinear interpolation";
+            widget.SetInterpolation(ImageInterpolation_Bilinear);
             break;
 
-          case 'n':
-          {
-            switch (widget.GetInterpolation())
-            {
-              case ImageInterpolation_Nearest:
-                LOG(INFO) << "Switching to bilinear interpolation";
-                widget.SetInterpolation(ImageInterpolation_Bilinear);
-                break;
-              
-              case ImageInterpolation_Bilinear:
-                LOG(INFO) << "Switching to nearest neighbor interpolation";
-                widget.SetInterpolation(ImageInterpolation_Nearest);
-                break;
-
-              default:
-                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-            }
-          
-            break;
-          }
-        
-          case 'r':
-            tool_ = Tool_Rotate;
+          case ImageInterpolation_Bilinear:
+            LOG(INFO) << "Switching to nearest neighbor interpolation";
+            widget.SetInterpolation(ImageInterpolation_Nearest);
             break;
 
-          case 's':
-            tool_ = Tool_Resize;
-            break;
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+          
+          break;
+        }
 
-          case 'w':
-            tool_ = Tool_Windowing;
-            break;
+        case 'r':
+          tool_ = Tool_Rotate;
+          break;
+
+        case 's':
+          tool_ = Tool_Resize;
+          break;
+
+        case 'w':
+          tool_ = Tool_Windowing;
+          break;
 
-          case 'y':
-            if (modifiers & KeyboardModifiers_Control)
-            {
-              undoRedoStack_.Redo();
-              widget.NotifyContentChanged();
-            }
-            break;
+        case 'y':
+          if (modifiers & KeyboardModifiers_Control)
+          {
+            undoRedoStack_.Redo();
+            widget.NotifyContentChanged();
+          }
+          break;
 
-          case 'z':
-            if (modifiers & KeyboardModifiers_Control)
-            {
-              undoRedoStack_.Undo();
-              widget.NotifyContentChanged();
-            }
-            break;
-        
-          default:
-            break;
+        case 'z':
+          if (modifiers & KeyboardModifiers_Control)
+          {
+            undoRedoStack_.Undo();
+            widget.NotifyContentChanged();
+          }
+          break;
+
+        default:
+          break;
         }
       }
     };
 
-  
-  
+
+
     class SingleFrameEditorApplication :
-      public SampleSingleCanvasApplicationBase,
-      public IObserver
+        public SampleSingleCanvasApplicationBase,
+        public IObserver
     {
     private:
-      std::auto_ptr<RadiographyScene>  scene_;
-      RadiographyEditorInteractor      interactor_;
+      boost::shared_ptr<RadiographyScene>   scene_;
+      RadiographyEditorInteractor           interactor_;
+      Orthanc::FontRegistry                 fontRegistry_;
 
     public:
       SingleFrameEditorApplication(MessageBroker& broker) :
@@ -399,11 +424,11 @@
       {
         boost::program_options::options_description generic("Sample options");
         generic.add_options()
-          ("instance", boost::program_options::value<std::string>(),
-           "Orthanc ID of the instance")
-          ("frame", boost::program_options::value<unsigned int>()->default_value(0),
-           "Number of the frame, for multi-frame DICOM instances")
-          ;
+            ("instance", boost::program_options::value<std::string>(),
+             "Orthanc ID of the instance")
+            ("frame", boost::program_options::value<unsigned int>()->default_value(0),
+             "Number of the frame, for multi-frame DICOM instances")
+            ;
 
         options.add(generic);
       }
@@ -440,12 +465,11 @@
         std::string instance = parameters["instance"].as<std::string>();
         int frame = parameters["frame"].as<unsigned int>();
 
-        Orthanc::FontRegistry fonts;
-        fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
+        fontRegistry_.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
         
         scene_.reset(new RadiographyScene(GetBroker()));
         //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0);
-        scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false);
+        scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL);
 
 #if !defined(ORTHANC_ENABLE_WASM) || ORTHANC_ENABLE_WASM != 1
         Orthanc::HttpClient::ConfigureSsl(true, "/etc/ssl/certs/ca-certificates.crt");
@@ -454,18 +478,18 @@
         //scene_->LoadDicomWebFrame(context->GetWebService());
         
         {
-          RadiographyLayer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld");
+          RadiographyLayer& layer = scene_->LoadText(fontRegistry_.GetFont(0), "Hello\nworld", NULL);
           layer.SetResizeable(true);
         }
         
         {
-          RadiographyLayer& layer = scene_->LoadTestBlock(100, 50);
+          RadiographyLayer& layer = scene_->LoadTestBlock(100, 50, NULL);
           layer.SetResizeable(true);
           layer.SetPan(0, 200);
         }
         
         
-        mainWidget_ = new RadiographyWidget(GetBroker(), *scene_, "main-widget");
+        mainWidget_ = new RadiographyWidget(GetBroker(), scene_, "main-widget");
         mainWidget_->SetTransmitMouseOver(true);
         mainWidget_->SetInteractor(interactor_);
 
--- a/Framework/Messages/IObservable.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Messages/IObservable.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -64,6 +64,25 @@
     callables_[messageType].insert(callable);
   }
 
+  void IObservable::Unregister(IObserver *observer)
+  {
+    // delete all callables from this observer
+    for (Callables::iterator itCallableSet = callables_.begin();
+         itCallableSet != callables_.end(); ++itCallableSet)
+    {
+      for (std::set<ICallable*>::const_iterator
+             itCallable = itCallableSet->second.begin(); itCallable != itCallableSet->second.end(); )
+      {
+        if ((*itCallable)->GetObserver() == observer)
+        {
+          delete *itCallable;
+          itCallableSet->second.erase(itCallable++);
+        }
+        else
+          ++itCallable;
+      }
+    }
+  }
   
   void IObservable::EmitMessage(const IMessage& message)
   {
--- a/Framework/Messages/IObservable.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Messages/IObservable.h	Thu Nov 29 15:11:19 2018 +0100
@@ -58,6 +58,8 @@
     // Takes ownsership
     void RegisterObserverCallback(ICallable* callable);
 
+    void Unregister(IObserver* observer);
+
     void EmitMessage(const IMessage& message);
 
     // Takes ownsership
--- a/Framework/Messages/MessageForwarder.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Messages/MessageForwarder.h	Thu Nov 29 15:11:19 2018 +0100
@@ -59,10 +59,10 @@
    * C is an observer of B and knows that B is re-emitting many messages from A
    *
    * instead of implementing a callback, B will create a MessageForwarder that will emit the messages in his name:
-   * A.RegisterObserverCallback(new MessageForwarder<A::MessageType>(broker, *this)  // where this is B
+   * A.RegisterObserverCallback(new MessageForwarder<A::MessageType>(broker, *this)  // where "this" is B
    *
    * in C:
-   * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback))   // where this is C
+   * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback))   // where "this" is C
    */
   template<typename TMessage>
   class MessageForwarder : public IMessageForwarder, public Callable<MessageForwarder<TMessage>, TMessage>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyAlphaLayer.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,132 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyAlphaLayer.h"
+
+#include "RadiographyScene.h"
+
+#include <Core/Images/Image.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+
+  void RadiographyAlphaLayer::SetAlpha(Orthanc::ImageAccessor* image)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> raii(image);
+
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    SetSize(image->GetWidth(), image->GetHeight());
+    alpha_ = raii;
+  }
+
+  void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer,
+                                     const AffineTransform2D& viewTransform,
+                                     ImageInterpolation interpolation) const
+  {
+    if (alpha_.get() == NULL)
+    {
+      return;
+    }
+
+    if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    unsigned int cropX, cropY, cropWidth, cropHeight;
+    GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+    const AffineTransform2D t = AffineTransform2D::Combine(
+          viewTransform, GetTransform(),
+          AffineTransform2D::CreateOffset(cropX, cropY));
+
+    Orthanc::ImageAccessor cropped;
+    alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
+
+    Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
+
+    t.Apply(tmp, cropped, interpolation, true /* clear */);
+
+    // Blit
+    const unsigned int width = buffer.GetWidth();
+    const unsigned int height = buffer.GetHeight();
+
+    float value = foreground_;
+
+    if (useWindowing_)
+    {
+      float center, width;
+      if (scene_.GetWindowing(center, width))
+      {
+        value = center + width / 2.0f;  // TODO: shouldn't it be center alone ?
+      }
+    }
+
+    for (unsigned int y = 0; y < height; y++)
+    {
+      float *q = reinterpret_cast<float*>(buffer.GetRow(y));
+      const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++, q++)
+      {
+        float a = static_cast<float>(*p) / 255.0f;
+
+        *q = (a * value + (1.0f - a) * (*q));
+      }
+    }
+  }
+
+  bool RadiographyAlphaLayer::GetRange(float& minValue,
+                                       float& maxValue) const
+  {
+    if (useWindowing_)
+    {
+      return false;
+    }
+    else
+    {
+      minValue = 0;
+      maxValue = 0;
+
+      if (foreground_ < 0)
+      {
+        minValue = foreground_;
+      }
+
+      if (foreground_ > 0)
+      {
+        maxValue = foreground_;
+      }
+
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyAlphaLayer.h	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,89 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "RadiographyLayer.h"
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  // creates a transparent layer whose alpha channel is provided as a UINT8 image to SetAlpha.
+  // The color of the "mask" is either defined by a ForegroundValue or by the center value of the
+  // windowing from the scene.
+  class RadiographyAlphaLayer : public RadiographyLayer
+  {
+  private:
+    const RadiographyScene&                scene_;
+    std::auto_ptr<Orthanc::ImageAccessor>  alpha_;      // Grayscale8
+    bool                                   useWindowing_;
+    float                                  foreground_;
+
+  public:
+    RadiographyAlphaLayer(const RadiographyScene& scene) :
+      scene_(scene),
+      useWindowing_(true),
+      foreground_(0)
+    {
+    }
+
+
+    void SetForegroundValue(float foreground)
+    {
+      useWindowing_ = false;
+      foreground_ = foreground;
+    }
+
+    float GetForegroundValue() const
+    {
+      return foreground_;
+    }
+
+    bool IsUsingWindowing() const
+    {
+      return useWindowing_;
+    }
+
+    void SetAlpha(Orthanc::ImageAccessor* image);
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const
+    {
+      return false;
+    }
+
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation) const;
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const;
+
+    const Orthanc::ImageAccessor& GetAlpha() const
+    {
+      return *(alpha_.get());
+    }
+
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyDicomLayer.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,159 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyDicomLayer.h"
+
+#include "RadiographyScene.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
+
+static OrthancPlugins::DicomTag  ConvertTag(const Orthanc::DicomTag& tag)
+{
+  return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
+}
+
+namespace OrthancStone
+{
+
+  void RadiographyDicomLayer::ApplyConverter()
+  {
+    if (source_.get() != NULL &&
+        converter_.get() != NULL)
+    {
+      converted_.reset(converter_->ConvertFrame(*source_));
+    }
+  }
+
+
+  void RadiographyDicomLayer::SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
+  {
+    converter_.reset(new DicomFrameConverter);
+    converter_->ReadParameters(dataset);
+    ApplyConverter();
+
+    std::string tmp;
+    Vector pixelSpacing;
+
+    if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
+        LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
+        pixelSpacing.size() == 2)
+    {
+      SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
+    }
+
+    //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY());
+
+    OrthancPlugins::DicomDatasetReader reader(dataset);
+
+    unsigned int width, height;
+    if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
+        !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      SetSize(width, height);
+    }
+  }
+
+  void RadiographyDicomLayer::SetSourceImage(Orthanc::ImageAccessor* image)   // Takes ownership
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> raii(image);
+
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    SetSize(image->GetWidth(), image->GetHeight());
+
+    source_ = raii;
+    ApplyConverter();
+  }
+
+  void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
+                                     const AffineTransform2D& viewTransform,
+                                     ImageInterpolation interpolation) const
+  {
+    if (converted_.get() != NULL)
+    {
+      if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      unsigned int cropX, cropY, cropWidth, cropHeight;
+      GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+      AffineTransform2D t = AffineTransform2D::Combine(
+            viewTransform, GetTransform(),
+            AffineTransform2D::CreateOffset(cropX, cropY));
+
+      Orthanc::ImageAccessor cropped;
+      converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
+
+      t.Apply(buffer, cropped, interpolation, false);
+    }
+  }
+
+
+  bool RadiographyDicomLayer::GetDefaultWindowing(float& center,
+                                                  float& width) const
+  {
+    if (converter_.get() != NULL &&
+        converter_->HasDefaultWindow())
+    {
+      center = static_cast<float>(converter_->GetDefaultWindowCenter());
+      width = static_cast<float>(converter_->GetDefaultWindowWidth());
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool RadiographyDicomLayer::GetRange(float& minValue,
+                                       float& maxValue) const
+  {
+    if (converted_.get() != NULL)
+    {
+      if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyDicomLayer.h	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,74 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "RadiographyLayer.h"
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+  class DicomFrameConverter;
+
+  class RadiographyDicomLayer : public RadiographyLayer
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
+    std::auto_ptr<DicomFrameConverter>     converter_;
+    std::auto_ptr<Orthanc::ImageAccessor>  converted_;  // Float32
+    std::string                            instanceId_;
+    unsigned int                           frame_;
+
+    void ApplyConverter();
+
+  public:
+    void SetInstance(const std::string& instanceId, unsigned int frame)
+    {
+      instanceId_ = instanceId;
+      frame_ = frame;
+    }
+
+    std::string GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      return frame_;
+    }
+
+    void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset);
+
+    void SetSourceImage(Orthanc::ImageAccessor* image);   // Takes ownership
+
+    virtual void Render(Orthanc::ImageAccessor& buffer,
+                        const AffineTransform2D& viewTransform,
+                        ImageInterpolation interpolation) const;
+
+    virtual bool GetDefaultWindowing(float& center,
+                                     float& width) const;
+
+    virtual bool GetRange(float& minValue,
+                          float& maxValue) const;
+  };
+}
--- a/Framework/Radiography/RadiographyLayer.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyLayer.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -32,18 +32,41 @@
   }
 
 
+  RadiographyLayer::Geometry::Geometry() :
+    hasCrop_(false),
+    panX_(0),
+    panY_(0),
+    angle_(0),
+    resizeable_(false),
+    pixelSpacingX_(1),
+    pixelSpacingY_(1)
+  {
+
+  }
+
+  void RadiographyLayer::Geometry::GetCrop(unsigned int &x, unsigned int &y, unsigned int &width, unsigned int &height) const
+  {
+    if (!hasCrop_)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);  // you should probably use Radiography::GetCrop() or at least call HasCrop() before
+
+    x = cropX_;
+    y = cropY_;
+    width = cropWidth_;
+    height = cropHeight_;
+  }
+
   void RadiographyLayer::UpdateTransform()
   {
-    transform_ = AffineTransform2D::CreateScaling(pixelSpacingX_, pixelSpacingY_);
+    transform_ = AffineTransform2D::CreateScaling(geometry_.GetPixelSpacingX(), geometry_.GetPixelSpacingY());
 
     double centerX, centerY;
     GetCenter(centerX, centerY);
 
     transform_ = AffineTransform2D::Combine(
-      AffineTransform2D::CreateOffset(panX_ + centerX, panY_ + centerY),
-      AffineTransform2D::CreateRotation(angle_),
-      AffineTransform2D::CreateOffset(-centerX, -centerY),
-      transform_);
+          AffineTransform2D::CreateOffset(geometry_.GetPanX() + centerX, geometry_.GetPanY() + centerY),
+          AffineTransform2D::CreateRotation(geometry_.GetAngle()),
+          AffineTransform2D::CreateOffset(-centerX, -centerY),
+          transform_);
 
     transformInverse_ = AffineTransform2D::Invert(transform_);
   }
@@ -73,28 +96,28 @@
 
     switch (corner)
     {
-      case Corner_TopLeft:
-        x = dx;
-        y = dy;
-        break;
+    case Corner_TopLeft:
+      x = dx;
+      y = dy;
+      break;
 
-      case Corner_TopRight:
-        x = dx + dwidth;
-        y = dy;
-        break;
+    case Corner_TopRight:
+      x = dx + dwidth;
+      y = dy;
+      break;
 
-      case Corner_BottomLeft:
-        x = dx;
-        y = dy + dheight;
-        break;
+    case Corner_BottomLeft:
+      x = dx;
+      y = dy + dheight;
+      break;
 
-      case Corner_BottomRight:
-        x = dx + dwidth;
-        y = dy + dheight;
-        break;
+    case Corner_BottomRight:
+      x = dx + dwidth;
+      y = dy + dheight;
+      break;
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
     transform_.Apply(x, y);
@@ -105,7 +128,7 @@
                                   double y) const
   {
     transformInverse_.Apply(x, y);
-        
+
     unsigned int cropX, cropY, cropWidth, cropHeight;
     GetCrop(cropX, cropY, cropWidth, cropHeight);
 
@@ -127,7 +150,7 @@
 
     cairo_t* cr = context.GetObject();
     cairo_set_line_width(cr, 2.0 / zoom);
-        
+
     double x, y;
     x = dx;
     y = dy;
@@ -162,18 +185,16 @@
     index_(0),
     hasSize_(false),
     width_(0),
-    height_(0),
-    hasCrop_(false),
-    pixelSpacingX_(1),
-    pixelSpacingY_(1),
-    panX_(0),
-    panY_(0),
-    angle_(0),
-    resizeable_(false)
+    height_(0)
   {
     UpdateTransform();
   }
 
+  void RadiographyLayer::ResetCrop()
+  {
+    geometry_.ResetCrop();
+    UpdateTransform();
+  }
 
   void RadiographyLayer::SetCrop(unsigned int x,
                                  unsigned int y,
@@ -184,34 +205,36 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
-        
+
     if (x + width > width_ ||
         y + height > height_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
-        
-    hasCrop_ = true;
-    cropX_ = x;
-    cropY_ = y;
-    cropWidth_ = width;
-    cropHeight_ = height;
 
+    geometry_.SetCrop(x, y, width, height);
     UpdateTransform();
   }
 
-      
+  void RadiographyLayer::SetGeometry(const Geometry& geometry)
+  {
+    geometry_ = geometry;
+
+    if (hasSize_)
+    {
+      UpdateTransform();
+    }
+  }
+
+
   void RadiographyLayer::GetCrop(unsigned int& x,
                                  unsigned int& y,
                                  unsigned int& width,
                                  unsigned int& height) const
   {
-    if (hasCrop_)
+    if (GetGeometry().HasCrop())
     {
-      x = cropX_;
-      y = cropY_;
-      width = cropWidth_;
-      height = cropHeight_;
+      GetGeometry().GetCrop(x, y, width, height);
     }
     else 
     {
@@ -222,10 +245,10 @@
     }
   }
 
-      
+  
   void RadiographyLayer::SetAngle(double angle)
   {
-    angle_ = angle;
+    geometry_.SetAngle(angle);
     UpdateTransform();
   }
 
@@ -239,7 +262,7 @@
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
     }
-        
+
     hasSize_ = true;
     width_ = width;
     height_ = height;
@@ -251,7 +274,7 @@
   Extent2D RadiographyLayer::GetExtent() const
   {
     Extent2D extent;
-       
+
     unsigned int x, y, width, height;
     GetCrop(x, y, width, height);
 
@@ -264,7 +287,7 @@
     AddToExtent(extent, dx + dwidth, dy);
     AddToExtent(extent, dx, dy + dheight);
     AddToExtent(extent, dx + dwidth, dy + dheight);
-        
+
     return extent;
   }
 
@@ -282,7 +305,7 @@
     else
     {
       transformInverse_.Apply(sceneX, sceneY);
-        
+
       int x = static_cast<int>(std::floor(sceneX));
       int y = static_cast<int>(std::floor(sceneY));
 
@@ -320,8 +343,7 @@
   void RadiographyLayer::SetPan(double x,
                                 double y)
   {
-    panX_ = x;
-    panY_ = y;
+    geometry_.SetPan(x, y);
     UpdateTransform();
   }
 
@@ -329,8 +351,7 @@
   void RadiographyLayer::SetPixelSpacing(double x,
                                          double y)
   {
-    pixelSpacingX_ = x;
-    pixelSpacingY_ = y;
+    geometry_.SetPixelSpacing(x, y);
     UpdateTransform();
   }
 
@@ -352,8 +373,8 @@
     GetCrop(cropX, cropY, cropWidth, cropHeight);
     GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight);
   }
-      
-      
+
+
   bool RadiographyLayer::LookupCorner(Corner& corner /* out */,
                                       double x,
                                       double y,
@@ -366,26 +387,26 @@
       Corner_BottomLeft,
       Corner_BottomRight
     };
-        
+
     unsigned int cropX, cropY, cropWidth, cropHeight;
     GetCrop(cropX, cropY, cropWidth, cropHeight);
 
     double threshold = Square(viewportDistance / zoom);
-        
+
     for (size_t i = 0; i < 4; i++)
     {
       double cx, cy;
       GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight);
 
       double d = Square(cx - x) + Square(cy - y);
-        
+
       if (d <= threshold)
       {
         corner = CORNERS[i];
         return true;
       }
     }
-        
+
     return false;
   }
 }
--- a/Framework/Radiography/RadiographyLayer.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyLayer.h	Thu Nov 29 15:11:19 2018 +0100
@@ -31,24 +31,114 @@
   {
     friend class RadiographyScene;
       
+  public:
+    class Geometry
+    {
+      bool               hasCrop_;
+      unsigned int       cropX_;
+      unsigned int       cropY_;
+      unsigned int       cropWidth_;
+      unsigned int       cropHeight_;
+      double             panX_;
+      double             panY_;
+      double             angle_;
+      bool               resizeable_;
+      double             pixelSpacingX_;
+      double             pixelSpacingY_;
+
+    public:
+      Geometry();
+
+      void ResetCrop()
+      {
+        hasCrop_ = false;
+      }
+
+      void SetCrop(unsigned int x,
+                   unsigned int y,
+                   unsigned int width,
+                   unsigned int height)
+      {
+        cropX_ = x;
+        cropY_ = y;
+        cropWidth_ = width;
+        cropHeight_ = height;
+      }
+
+      bool HasCrop() const
+      {
+        return hasCrop_;
+      }
+
+      void GetCrop(unsigned int& x,
+                   unsigned int& y,
+                   unsigned int& width,
+                   unsigned int& height) const;
+
+      void SetAngle(double angle)
+      {
+        angle_ = angle;
+      }
+
+      double GetAngle() const
+      {
+        return angle_;
+      }
+
+      void SetPan(double x,
+                  double y)
+      {
+        panX_ = x;
+        panY_ = y;
+      }
+
+      double GetPanX() const
+      {
+        return panX_;
+      }
+
+      double GetPanY() const
+      {
+        return panY_;
+      }
+
+      bool IsResizeable() const
+      {
+        return resizeable_;
+      }
+
+      void SetResizeable(bool resizeable)
+      {
+        resizeable_ = resizeable;
+      }
+
+      void SetPixelSpacing(double x,
+                           double y)
+      {
+        pixelSpacingX_ = x;
+        pixelSpacingY_ = y;
+      }
+
+      double GetPixelSpacingX() const
+      {
+        return pixelSpacingX_;
+      }
+
+      double GetPixelSpacingY() const
+      {
+        return pixelSpacingY_;
+      }
+
+    };
+
   private:
     size_t             index_;
     bool               hasSize_;
     unsigned int       width_;
     unsigned int       height_;
-    bool               hasCrop_;
-    unsigned int       cropX_;
-    unsigned int       cropY_;
-    unsigned int       cropWidth_;
-    unsigned int       cropHeight_;
     AffineTransform2D  transform_;
     AffineTransform2D  transformInverse_;
-    double             pixelSpacingX_;
-    double             pixelSpacingY_;
-    double             panX_;
-    double             panY_;
-    double             angle_;
-    bool               resizeable_;
+    Geometry           geometry_;
 
   protected:
     const AffineTransform2D& GetTransform() const
@@ -94,11 +184,15 @@
       return index_;
     }
 
-    void ResetCrop()
+    const Geometry& GetGeometry() const
     {
-      hasCrop_ = false;
+      return geometry_;
     }
 
+    void SetGeometry(const Geometry& geometry);
+
+    void ResetCrop();
+
     void SetCrop(unsigned int x,
                  unsigned int y,
                  unsigned int width,
@@ -111,14 +205,22 @@
 
     void SetAngle(double angle);
 
-    double GetAngle() const
+    void SetPan(double x,
+                double y);
+
+    void SetResizeable(bool resizeable)
     {
-      return angle_;
+      geometry_.SetResizeable(resizeable);
     }
 
     void SetSize(unsigned int width,
                  unsigned int height);
 
+    bool HasSize() const
+    {
+      return hasSize_;
+    }
+
     unsigned int GetWidth() const
     {
       return width_;
@@ -136,32 +238,9 @@
                   double sceneX,
                   double sceneY) const;
 
-    void SetPan(double x,
-                double y);
-
     void SetPixelSpacing(double x,
                          double y);
 
-    double GetPixelSpacingX() const
-    {
-      return pixelSpacingX_;
-    }   
-
-    double GetPixelSpacingY() const
-    {
-      return pixelSpacingY_;
-    }   
-
-    double GetPanX() const
-    {
-      return panX_;
-    }
-
-    double GetPanY() const
-    {
-      return panY_;
-    }
-
     void GetCenter(double& centerX,
                    double& centerY) const;
 
@@ -175,16 +254,6 @@
                       double zoom,
                       double viewportDistance) const;
 
-    bool IsResizeable() const
-    {
-      return resizeable_;
-    }
-
-    void SetResizeable(bool resizeable)
-    {
-      resizeable_ = resizeable;
-    }
-
     virtual bool GetDefaultWindowing(float& center,
                                      float& width) const = 0;
 
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -58,7 +58,7 @@
       sourceCropWidth_(tracker.cropWidth_),
       sourceCropHeight_(tracker.cropHeight_)
     {
-      tracker.accessor_.GetLayer().GetCrop(targetCropX_, targetCropY_,
+      tracker.accessor_.GetLayer().GetGeometry().GetCrop(targetCropX_, targetCropY_,
                                            targetCropWidth_, targetCropHeight_);
     }
   };
@@ -77,7 +77,7 @@
   {
     if (accessor_.IsValid())
     {
-      accessor_.GetLayer().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);          
+      accessor_.GetLayer().GetGeometry().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);
     }
   }
 
--- a/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -51,8 +51,8 @@
       RadiographySceneCommand(tracker.accessor_),
       sourceX_(tracker.panX_),
       sourceY_(tracker.panY_),
-      targetX_(tracker.accessor_.GetLayer().GetPanX()),
-      targetY_(tracker.accessor_.GetLayer().GetPanY())
+      targetX_(tracker.accessor_.GetLayer().GetGeometry().GetPanX()),
+      targetY_(tracker.accessor_.GetLayer().GetGeometry().GetPanY())
     {
     }
   };
@@ -72,8 +72,8 @@
   {
     if (accessor_.IsValid())
     {
-      panX_ = accessor_.GetLayer().GetPanX();
-      panY_ = accessor_.GetLayer().GetPanY();
+      panX_ = accessor_.GetLayer().GetGeometry().GetPanX();
+      panY_ = accessor_.GetLayer().GetGeometry().GetPanY();
     }
   }
 
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -73,10 +73,10 @@
       sourceSpacingY_(tracker.originalSpacingY_),
       sourcePanX_(tracker.originalPanX_),
       sourcePanY_(tracker.originalPanY_),
-      targetSpacingX_(tracker.accessor_.GetLayer().GetPixelSpacingX()),
-      targetSpacingY_(tracker.accessor_.GetLayer().GetPixelSpacingY()),
-      targetPanX_(tracker.accessor_.GetLayer().GetPanX()),
-      targetPanY_(tracker.accessor_.GetLayer().GetPanY())
+      targetSpacingX_(tracker.accessor_.GetLayer().GetGeometry().GetPixelSpacingX()),
+      targetSpacingY_(tracker.accessor_.GetLayer().GetGeometry().GetPixelSpacingY()),
+      targetPanX_(tracker.accessor_.GetLayer().GetGeometry().GetPanX()),
+      targetPanY_(tracker.accessor_.GetLayer().GetGeometry().GetPanY())
     {
     }
   };
@@ -94,12 +94,12 @@
     roundScaling_(roundScaling)
   {
     if (accessor_.IsValid() &&
-        accessor_.GetLayer().IsResizeable())
+        accessor_.GetLayer().GetGeometry().IsResizeable())
     {
-      originalSpacingX_ = accessor_.GetLayer().GetPixelSpacingX();
-      originalSpacingY_ = accessor_.GetLayer().GetPixelSpacingY();
-      originalPanX_ = accessor_.GetLayer().GetPanX();
-      originalPanY_ = accessor_.GetLayer().GetPanY();
+      originalSpacingX_ = accessor_.GetLayer().GetGeometry().GetPixelSpacingX();
+      originalSpacingY_ = accessor_.GetLayer().GetGeometry().GetPixelSpacingY();
+      originalPanX_ = accessor_.GetLayer().GetGeometry().GetPanX();
+      originalPanY_ = accessor_.GetLayer().GetGeometry().GetPanY();
 
       switch (corner)
       {
@@ -149,7 +149,7 @@
   void RadiographyLayerResizeTracker::MouseUp()
   {
     if (accessor_.IsValid() &&
-        accessor_.GetLayer().IsResizeable())
+        accessor_.GetLayer().GetGeometry().IsResizeable())
     {
       undoRedoStack_.Add(new UndoRedoCommand(*this));
     }
@@ -164,7 +164,7 @@
     static const double ROUND_SCALING = 0.1;
         
     if (accessor_.IsValid() &&
-        accessor_.GetLayer().IsResizeable())
+        accessor_.GetLayer().GetGeometry().IsResizeable())
     {
       double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
 
@@ -180,8 +180,8 @@
       // Keep the opposite corner at a fixed location
       double ox, oy;
       layer.GetCorner(ox, oy, oppositeCorner_);
-      layer.SetPan(layer.GetPanX() + oppositeX_ - ox,
-                   layer.GetPanY() + oppositeY_ - oy);
+      layer.SetPan(layer.GetGeometry().GetPanX() + oppositeX_ - ox,
+                   layer.GetGeometry().GetPanY() + oppositeY_ - oy);
     }
   }
 }
--- a/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -58,7 +58,7 @@
     UndoRedoCommand(const RadiographyLayerRotateTracker& tracker) :
       RadiographySceneCommand(tracker.accessor_),
       sourceAngle_(tracker.originalAngle_),
-      targetAngle_(tracker.accessor_.GetLayer().GetAngle())
+      targetAngle_(tracker.accessor_.GetLayer().GetGeometry().GetAngle())
     {
     }
   };
@@ -100,7 +100,7 @@
     if (accessor_.IsValid())
     {
       accessor_.GetLayer().GetCenter(centerX_, centerY_);
-      originalAngle_ = accessor_.GetLayer().GetAngle();
+      originalAngle_ = accessor_.GetLayer().GetGeometry().GetAngle();
 
       double sceneX, sceneY;
       view.MapDisplayToScene(sceneX, sceneY, x, y);
--- a/Framework/Radiography/RadiographyScene.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyScene.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -21,6 +21,9 @@
 
 #include "RadiographyScene.h"
 
+#include "RadiographyAlphaLayer.h"
+#include "RadiographyDicomLayer.h"
+#include "RadiographyTextLayer.h"
 #include "../Toolbox/DicomFrameConverter.h"
 
 #include <Core/Images/Image.h>
@@ -121,299 +124,6 @@
     }
   }
 
-
-
-  class RadiographyScene::AlphaLayer : public RadiographyLayer
-  {
-  private:
-    const RadiographyScene&                scene_;
-    std::auto_ptr<Orthanc::ImageAccessor>  alpha_;      // Grayscale8
-    bool                                   useWindowing_;
-    float                                  foreground_;
-
-  public:
-    AlphaLayer(const RadiographyScene& scene) :
-      scene_(scene),
-      useWindowing_(true),
-      foreground_(0)
-    {
-    }
-
-
-    void SetForegroundValue(float foreground)
-    {
-      useWindowing_ = false;
-      foreground_ = foreground;
-    }
-
-
-    void SetAlpha(Orthanc::ImageAccessor* image)
-    {
-      std::auto_ptr<Orthanc::ImageAccessor> raii(image);
-
-      if (image == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-
-      if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      SetSize(image->GetWidth(), image->GetHeight());
-      alpha_ = raii;
-    }
-
-    virtual bool GetDefaultWindowing(float& center,
-                                     float& width) const
-    {
-      return false;
-    }
-
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const
-    {
-      if (alpha_.get() == NULL)
-      {
-        return;
-      }
-
-      if (buffer.GetFormat() != Orthanc::PixelFormat_Float32)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      unsigned int cropX, cropY, cropWidth, cropHeight;
-      GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-      const AffineTransform2D t = AffineTransform2D::Combine(
-            viewTransform, GetTransform(),
-            AffineTransform2D::CreateOffset(cropX, cropY));
-
-      Orthanc::ImageAccessor cropped;
-      alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
-
-      Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false);
-      
-      t.Apply(tmp, cropped, interpolation, true /* clear */);
-
-      // Blit
-      const unsigned int width = buffer.GetWidth();
-      const unsigned int height = buffer.GetHeight();
-
-      float value = foreground_;
-
-      if (useWindowing_)
-      {
-        float center, width;
-        if (scene_.GetWindowing(center, width))
-        {
-          value = center + width / 2.0f;
-        }
-      }
-
-      for (unsigned int y = 0; y < height; y++)
-      {
-        float *q = reinterpret_cast<float*>(buffer.GetRow(y));
-        const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y));
-
-        for (unsigned int x = 0; x < width; x++, p++, q++)
-        {
-          float a = static_cast<float>(*p) / 255.0f;
-
-          *q = (a * value + (1.0f - a) * (*q));
-        }
-      }
-    }
-
-
-    virtual bool GetRange(float& minValue,
-                          float& maxValue) const
-    {
-      if (useWindowing_)
-      {
-        return false;
-      }
-      else
-      {
-        minValue = 0;
-        maxValue = 0;
-
-        if (foreground_ < 0)
-        {
-          minValue = foreground_;
-        }
-
-        if (foreground_ > 0)
-        {
-          maxValue = foreground_;
-        }
-
-        return true;
-      }
-    }
-  };
-
-
-  class RadiographyScene::TextLayer : public AlphaLayer
-  {
-  private:
-    std::string                text_;
-
-  public:
-    TextLayer(const RadiographyScene& scene) :
-      AlphaLayer(scene)
-    {
-    }
-
-    void LoadText(const Orthanc::Font& font,
-                  const std::string& utf8)
-    {
-      SetAlpha(font.RenderAlpha(utf8));
-    }
-
-  };
-
-
-  class RadiographyScene::DicomLayer : public RadiographyLayer
-  {
-  private:
-    std::auto_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
-    std::auto_ptr<DicomFrameConverter>     converter_;
-    std::auto_ptr<Orthanc::ImageAccessor>  converted_;  // Float32
-
-    static OrthancPlugins::DicomTag  ConvertTag(const Orthanc::DicomTag& tag)
-    {
-      return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
-    }
-
-
-    void ApplyConverter()
-    {
-      if (source_.get() != NULL &&
-          converter_.get() != NULL)
-      {
-        converted_.reset(converter_->ConvertFrame(*source_));
-      }
-    }
-
-  public:
-    void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
-    {
-      converter_.reset(new DicomFrameConverter);
-      converter_->ReadParameters(dataset);
-      ApplyConverter();
-
-      std::string tmp;
-      Vector pixelSpacing;
-
-      if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
-          LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
-          pixelSpacing.size() == 2)
-      {
-        SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
-      }
-
-      //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY());
-      
-      OrthancPlugins::DicomDatasetReader reader(dataset);
-
-      unsigned int width, height;
-      if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
-          !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-      else
-      {
-        SetSize(width, height);
-      }
-    }
-
-
-    void SetSourceImage(Orthanc::ImageAccessor* image)   // Takes ownership
-    {
-      std::auto_ptr<Orthanc::ImageAccessor> raii(image);
-
-      if (image == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-
-      SetSize(image->GetWidth(), image->GetHeight());
-
-      source_ = raii;
-      ApplyConverter();
-    }
-
-
-    virtual void Render(Orthanc::ImageAccessor& buffer,
-                        const AffineTransform2D& viewTransform,
-                        ImageInterpolation interpolation) const
-    {
-      if (converted_.get() != NULL)
-      {
-        if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        unsigned int cropX, cropY, cropWidth, cropHeight;
-        GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-        AffineTransform2D t = AffineTransform2D::Combine(
-              viewTransform, GetTransform(),
-              AffineTransform2D::CreateOffset(cropX, cropY));
-
-        Orthanc::ImageAccessor cropped;
-        converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
-        
-        t.Apply(buffer, cropped, interpolation, false);
-      }
-    }
-
-
-    virtual bool GetDefaultWindowing(float& center,
-                                     float& width) const
-    {
-      if (converter_.get() != NULL &&
-          converter_->HasDefaultWindow())
-      {
-        center = static_cast<float>(converter_->GetDefaultWindowCenter());
-        width = static_cast<float>(converter_->GetDefaultWindowWidth());
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-
-
-    virtual bool GetRange(float& minValue,
-                          float& maxValue) const
-    {
-      if (converted_.get() != NULL)
-      {
-        if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_);
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  };
-
-
   RadiographyLayer& RadiographyScene::RegisterLayer(RadiographyLayer* layer)
   {
     if (layer == NULL)
@@ -423,12 +133,14 @@
 
     std::auto_ptr<RadiographyLayer> raii(layer);
 
+    LOG(INFO) << "Registering layer: " << countLayers_;
+
     size_t index = countLayers_++;
     raii->SetIndex(index);
     layers_[index] = raii.release();
 
-    EmitMessage(GeometryChangedMessage(*this));
-    EmitMessage(ContentChangedMessage(*this));
+    EmitMessage(GeometryChangedMessage(*this, *layer));
+    EmitMessage(ContentChangedMessage(*this, *layer));
 
     return *layer;
   }
@@ -454,8 +166,18 @@
     }
   }
 
+  void RadiographyScene::GetLayersIndexes(std::vector<size_t>& output) const
+  {
+    for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
+    {
+      output.push_back(it->first);
+    }
+  }
+
   void RadiographyScene::RemoveLayer(size_t layerIndex)
   {
+    LOG(INFO) << "Removing layer: " << layerIndex;
+
     if (layerIndex > countLayers_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
@@ -463,15 +185,17 @@
     delete layers_[layerIndex];
     layers_.erase(layerIndex);
     countLayers_--;
+    LOG(INFO) << "Removing layer, there are now : " << countLayers_ << " layers";
   }
 
-  RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex)
+  const RadiographyLayer& RadiographyScene::GetLayer(size_t layerIndex) const
   {
     if (layerIndex > countLayers_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
-    return *(layers_[layerIndex]);
+
+    return *(layers_.at(layerIndex));
   }
 
   bool RadiographyScene::GetWindowing(float& center,
@@ -511,17 +235,23 @@
 
 
   RadiographyLayer& RadiographyScene::LoadText(const Orthanc::Font& font,
-                                               const std::string& utf8)
+                                               const std::string& utf8,
+                                               RadiographyLayer::Geometry* geometry)
   {
-    std::auto_ptr<AlphaLayer>  alpha(new AlphaLayer(*this));
+    std::auto_ptr<RadiographyTextLayer>  alpha(new RadiographyTextLayer(*this));
     alpha->LoadText(font, utf8);
+    if (geometry != NULL)
+    {
+      alpha->SetGeometry(*geometry);
+    }
 
     return RegisterLayer(alpha.release());
   }
 
 
   RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width,
-                                                    unsigned int height)
+                                                    unsigned int height,
+                                                    RadiographyLayer::Geometry* geometry)
   {
     std::auto_ptr<Orthanc::Image>  block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
 
@@ -544,19 +274,34 @@
       Orthanc::ImageProcessing::Set(region, color);
     }
 
-    std::auto_ptr<AlphaLayer>  alpha(new AlphaLayer(*this));
-    alpha->SetAlpha(block.release());
+    return LoadAlphaBitmap(block.release(), geometry);
+  }
+
+  RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry)
+  {
+    std::auto_ptr<RadiographyAlphaLayer>  alpha(new RadiographyAlphaLayer(*this));
+    alpha->SetAlpha(bitmap);
+    if (geometry != NULL)
+    {
+      alpha->SetGeometry(*geometry);
+    }
 
     return RegisterLayer(alpha.release());
   }
 
-
   RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc,
                                                      const std::string& instance,
                                                      unsigned int frame,
-                                                     bool httpCompression)
+                                                     bool httpCompression,
+                                                     RadiographyLayer::Geometry* geometry)
   {
-    RadiographyLayer& layer = RegisterLayer(new DicomLayer);
+    RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer));
+    layer.SetInstance(instance, frame);
+
+    if (geometry != NULL)
+    {
+      layer.SetGeometry(*geometry);
+    }
 
     {
       IWebService::HttpHeaders headers;
@@ -594,7 +339,7 @@
 
   RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web)
   {
-    RadiographyLayer& layer = RegisterLayer(new DicomLayer);
+    RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer);
 
 
     return layer;
@@ -616,7 +361,7 @@
       assert(layer->second != NULL);
 
       OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize());
-      dynamic_cast<DicomLayer*>(layer->second)->SetDicomTags(dicom);
+      dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetDicomTags(dicom);
 
       float c, w;
       if (!hasWindowing_ &&
@@ -627,7 +372,7 @@
         windowingWidth_ = w;
       }
 
-      EmitMessage(GeometryChangedMessage(*this));
+      EmitMessage(GeometryChangedMessage(*this, *(layer->second)));
     }
   }
 
@@ -652,9 +397,9 @@
 
       std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
       reader->ReadFromMemory(content);
-      dynamic_cast<DicomLayer*>(layer->second)->SetSourceImage(reader.release());
+      dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release());
 
-      EmitMessage(ContentChangedMessage(*this));
+      EmitMessage(ContentChangedMessage(*this, *(layer->second)));
     }
   }
 
@@ -678,7 +423,7 @@
                                 const AffineTransform2D& viewTransform,
                                 ImageInterpolation interpolation) const
   {
-    Orthanc::ImageProcessing::Set(buffer, 0);
+    Orthanc::ImageProcessing::Set(buffer, 0); // TODO: get background color (depending on inverted state)
 
     // Render layers in the background-to-foreground order
     for (size_t index = 0; index < countLayers_; index++)
@@ -765,16 +510,6 @@
   }
 
 
-  void RadiographyScene::ExportToJson(Json::Value &output)
-  {
-    for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++)
-    {
-      Json::Value jsonLayer;
-      it->
-
-    }
-  }
-
   void RadiographyScene::ExportDicom(OrthancApiClient& orthanc,
                                      const Orthanc::DicomMap& dicom,
                                      const std::string& parentOrthancId,
--- a/Framework/Radiography/RadiographyScene.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyScene.h	Thu Nov 29 15:11:19 2018 +0100
@@ -32,8 +32,46 @@
       public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_Widget_GeometryChanged, RadiographyScene> GeometryChangedMessage;
-    typedef OriginMessage<MessageType_Widget_ContentChanged, RadiographyScene> ContentChangedMessage;
+    class GeometryChangedMessage :
+      public OriginMessage<MessageType_Scene_GeometryChanged, RadiographyScene>
+    {
+    private:
+      RadiographyLayer&        layer_;
+
+    public:
+      GeometryChangedMessage(const RadiographyScene& origin,
+                             RadiographyLayer& layer) :
+        OriginMessage(origin),
+        layer_(layer)
+      {
+      }
+
+      RadiographyLayer& GetLayer() const
+      {
+        return layer_;
+      }
+    };
+
+    class ContentChangedMessage :
+      public OriginMessage<MessageType_Scene_ContentChanged, RadiographyScene>
+    {
+    private:
+      RadiographyLayer&        layer_;
+
+    public:
+      ContentChangedMessage(const RadiographyScene& origin,
+                             RadiographyLayer& layer) :
+        OriginMessage(origin),
+        layer_(layer)
+      {
+      }
+
+      RadiographyLayer& GetLayer() const
+      {
+        return layer_;
+      }
+    };
+
 
     class LayerAccessor : public boost::noncopyable
     {
@@ -69,9 +107,6 @@
 
 
   private:
-    class AlphaLayer;
-    class DicomLayer;
-
     typedef std::map<size_t, RadiographyLayer*>  Layers;
 
     size_t  countLayers_;
@@ -105,26 +140,29 @@
                       float width);
 
     RadiographyLayer& LoadText(const Orthanc::Font& font,
-                               const std::string& utf8);
+                               const std::string& utf8,
+                               RadiographyLayer::Geometry* geometry);
     
     RadiographyLayer& LoadTestBlock(unsigned int width,
-                                    unsigned int height);
-    
+                                    unsigned int height,
+                                    RadiographyLayer::Geometry* geometry);
+
+    RadiographyLayer& LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap,  // takes ownership
+                                      RadiographyLayer::Geometry* geometry);
+
     RadiographyLayer& LoadDicomFrame(OrthancApiClient& orthanc,
                                      const std::string& instance,
                                      unsigned int frame,
-                                     bool httpCompression);
+                                     bool httpCompression,
+                                     RadiographyLayer::Geometry* geometry); // pass NULL if you want default geometry
 
     RadiographyLayer& LoadDicomWebFrame(IWebService& web);
 
     void RemoveLayer(size_t layerIndex);
 
-    size_t GetLayerCount()
-    {
-      return countLayers_;
-    }
+    const RadiographyLayer& GetLayer(size_t layerIndex) const;
 
-    RadiographyLayer& GetLayer(size_t layerIndex);
+    void GetLayersIndexes(std::vector<size_t>& output) const;
 
     Extent2D GetSceneExtent() const;
 
@@ -162,8 +200,5 @@
                 bool invert,
                 ImageInterpolation interpolation,
                 bool usePam);
-
-    // temporary version used by VSOL
-    void ExportToJson(Json::Value& output);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographySceneReader.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,112 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographySceneReader.h"
+
+#include <Core/Images/FontRegistry.h>
+#include <Core/Images/PngReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+  void RadiographySceneReader::Read(const Json::Value& input)
+  {
+    unsigned int version = input["version"].asUInt();
+
+    if (version != 1)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+
+    for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++)
+    {
+      const Json::Value& jsonLayer = input["layers"][(int)layerIndex];
+      RadiographyLayer::Geometry geometry;
+
+      if (jsonLayer["type"].asString() == "dicom")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+        scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), true, &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "text")
+      {
+        if (fontRegistry_ == NULL || fontRegistry_->GetSize() == 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); // you must provide a FontRegistry if you need to re-create text layers.
+        }
+
+        ReadLayerGeometry(geometry, jsonLayer);
+        const Orthanc::Font* font = fontRegistry_->FindFont(jsonLayer["fontName"].asString());
+        if (font == NULL) // if not found, take the first font in the registry
+        {
+          font = &(fontRegistry_->GetFont(0));
+        }
+        scene_.LoadText(*font, jsonLayer["text"].asString(), &geometry);
+      }
+      else if (jsonLayer["type"].asString() == "alpha")
+      {
+        ReadLayerGeometry(geometry, jsonLayer);
+
+        const std::string& pngContentBase64 = jsonLayer["content"].asString();
+        std::string pngContent;
+        std::string mimeType;
+        Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64);
+
+        std::auto_ptr<Orthanc::ImageAccessor>  image;
+        if (mimeType == "image/png")
+        {
+          image.reset(new Orthanc::PngReader());
+          dynamic_cast<Orthanc::PngReader*>(image.get())->ReadFromMemory(pngContent);
+        }
+        else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+
+        RadiographyAlphaLayer& layer = dynamic_cast<RadiographyAlphaLayer&>(scene_.LoadAlphaBitmap(image.release(), &geometry));
+
+        if (!jsonLayer["isUsingWindowing"].asBool())
+        {
+          layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble()));
+        }
+      }
+      else
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  void RadiographySceneReader::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer)
+  {
+    {// crop
+      unsigned int x, y, width, height;
+      if (jsonLayer["crop"]["hasCrop"].asBool())
+      {
+        x = jsonLayer["crop"]["x"].asUInt();
+        y = jsonLayer["crop"]["y"].asUInt();
+        width = jsonLayer["crop"]["width"].asUInt();
+        height = jsonLayer["crop"]["height"].asUInt();
+        geometry.SetCrop(x, y, width, height);
+      }
+    }
+
+    geometry.SetAngle(jsonLayer["angle"].asDouble());
+    geometry.SetResizeable(jsonLayer["isResizable"].asBool());
+    geometry.SetPan(jsonLayer["pan"]["x"].asDouble(), jsonLayer["pan"]["y"].asDouble());
+    geometry.SetPixelSpacing(jsonLayer["pixelSpacing"]["x"].asDouble(), jsonLayer["pixelSpacing"]["y"].asDouble());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographySceneReader.h	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,59 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "RadiographyScene.h"
+#include "RadiographyAlphaLayer.h"
+#include "RadiographyDicomLayer.h"
+#include "RadiographyTextLayer.h"
+#include <json/value.h>
+#include <Core/Images/FontRegistry.h>
+
+namespace OrthancStone
+{
+  class OrthancApiClient;
+
+  class RadiographySceneReader : public boost::noncopyable
+  {
+    RadiographyScene&             scene_;
+    OrthancApiClient&             orthancApiClient_;
+    const Orthanc::FontRegistry*  fontRegistry_;
+
+  public:
+    RadiographySceneReader(RadiographyScene& scene, OrthancApiClient& orthancApiClient) :
+      scene_(scene),
+      orthancApiClient_(orthancApiClient),
+      fontRegistry_(NULL)
+    {
+    }
+
+    void Read(const Json::Value& output);
+
+    void SetFontRegistry(const Orthanc::FontRegistry& fontRegistry)
+    {
+      fontRegistry_ = &fontRegistry;
+    }
+
+  private:
+    void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& output);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographySceneWriter.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,136 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographySceneWriter.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Images/PngWriter.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+  void RadiographySceneWriter::Write(Json::Value& output, const RadiographyScene& scene)
+  {
+    output["version"] = 1;
+    output["layers"] = Json::arrayValue;
+
+    std::vector<size_t> layersIndexes;
+    scene.GetLayersIndexes(layersIndexes);
+
+    for (std::vector<size_t>::iterator itLayerIndex = layersIndexes.begin(); itLayerIndex < layersIndexes.end(); itLayerIndex++)
+    {
+      Json::Value layer;
+      WriteLayer(layer, scene.GetLayer(*itLayerIndex));
+      output["layers"].append(layer);
+    }
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer)
+  {
+    output["type"] = "dicom";
+    output["instanceId"] = layer.GetInstanceId();
+    output["frame"] = layer.GetFrame();
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyTextLayer& layer)
+  {
+    output["type"] = "text";
+    output["text"] = layer.GetText();
+    output["fontName"] = layer.GetFontName();
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer)
+  {
+    output["type"] = "alpha";
+
+    //output["bitmap"] =
+    const Orthanc::ImageAccessor& alpha = layer.GetAlpha();
+
+    Orthanc::PngWriter pngWriter;
+    std::string pngContent;
+    std::string pngContentBase64;
+    pngWriter.WriteToMemory(pngContent, alpha);
+
+    Orthanc::Toolbox::EncodeDataUriScheme(pngContentBase64, "image/png", pngContent);
+    output["content"] = pngContentBase64;
+    output["foreground"] = layer.GetForegroundValue();
+    output["isUsingWindowing"] = layer.IsUsingWindowing();
+  }
+
+  void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyLayer& layer)
+  {
+    const RadiographyLayer::Geometry& geometry = layer.GetGeometry();
+
+    {// crop
+      Json::Value crop;
+      if (geometry.HasCrop())
+      {
+        unsigned int x, y, width, height;
+        geometry.GetCrop(x, y, width, height);
+        crop["hasCrop"] = true;
+        crop["x"] = x;
+        crop["y"] = y;
+        crop["width"] = width;
+        crop["height"] = height;
+      }
+      else
+      {
+        crop["hasCrop"] = false;
+      }
+
+      output["crop"] = crop;
+    }
+
+    output["angle"] = geometry.GetAngle();
+    output["isResizable"] = geometry.IsResizeable();
+
+    {// pan
+      Json::Value pan;
+      pan["x"] = geometry.GetPanX();
+      pan["y"] = geometry.GetPanY();
+      output["pan"] = pan;
+    }
+
+    {// pixelSpacing
+      Json::Value pan;
+      pan["x"] = geometry.GetPixelSpacingX();
+      pan["y"] = geometry.GetPixelSpacingY();
+      output["pixelSpacing"] = pan;
+    }
+
+    if (dynamic_cast<const RadiographyTextLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyTextLayer&>(layer));
+    }
+    else if (dynamic_cast<const RadiographyDicomLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyDicomLayer&>(layer));
+    }
+    else if (dynamic_cast<const RadiographyAlphaLayer*>(&layer) != NULL)
+    {
+      WriteLayer(output, dynamic_cast<const RadiographyAlphaLayer&>(layer));
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographySceneWriter.h	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,50 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "RadiographyScene.h"
+#include "RadiographyAlphaLayer.h"
+#include "RadiographyDicomLayer.h"
+#include "RadiographyTextLayer.h"
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  class RadiographySceneWriter : public boost::noncopyable
+  {
+
+  public:
+    RadiographySceneWriter()
+    {
+    }
+
+    void Write(Json::Value& output, const RadiographyScene& scene);
+
+  private:
+    void WriteLayer(Json::Value& output, const RadiographyLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyTextLayer& layer);
+    void WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyTextLayer.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,36 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiographyTextLayer.h"
+
+#include "RadiographyScene.h"
+
+namespace OrthancStone
+{
+  void RadiographyTextLayer::LoadText(const Orthanc::Font& font,
+                                      const std::string& utf8)
+  {
+    text_ = utf8;
+    fontName_ = font.GetName();
+
+    SetAlpha(font.RenderAlpha(utf8));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Radiography/RadiographyTextLayer.h	Thu Nov 29 15:11:19 2018 +0100
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "RadiographyAlphaLayer.h"
+
+namespace OrthancStone
+{
+  class RadiographyScene;
+
+  class RadiographyTextLayer : public RadiographyAlphaLayer
+  {
+  private:
+    std::string                text_;
+    std::string                fontName_;
+
+  public:
+    RadiographyTextLayer(const RadiographyScene& scene) :
+      RadiographyAlphaLayer(scene)
+    {
+    }
+
+    void LoadText(const Orthanc::Font& font,
+                  const std::string& utf8);
+
+    const std::string& GetText() const
+    {
+      return text_;
+    }
+
+    const std::string& GetFontName() const
+    {
+      return fontName_;
+    }
+  };
+}
--- a/Framework/Radiography/RadiographyWidget.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyWidget.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -31,7 +31,7 @@
                                          ImageInterpolation interpolation)
   {
     float windowCenter, windowWidth;
-    scene_.GetWindowingWithDefault(windowCenter, windowWidth);
+    scene_->GetWindowingWithDefault(windowCenter, windowWidth);
       
     float x0 = windowCenter - windowWidth / 2.0f;
     float x1 = windowCenter + windowWidth / 2.0f;
@@ -56,7 +56,7 @@
         cairoBuffer_.reset(new CairoSurface(width, height));
       }
 
-      scene_.Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
+      scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
         
       // Conversion from Float32 to BGRA32 (cairo). Very similar to
       // GrayscaleFrameRenderer => TODO MERGE?
@@ -128,7 +128,7 @@
 
     if (hasSelection_)
     {
-      scene_.DrawBorder(context, selectedLayer_, view.GetZoom());
+      scene_->DrawBorder(context, selectedLayer_, view.GetZoom());
     }
 
     return true;
@@ -136,23 +136,16 @@
 
 
   RadiographyWidget::RadiographyWidget(MessageBroker& broker,
-                                       RadiographyScene& scene,
+                                       boost::shared_ptr<RadiographyScene> scene,
                                        const std::string& name) :
     WorldSceneWidget(name),
     IObserver(broker),
-    scene_(scene),
     invert_(false),
     interpolation_(ImageInterpolation_Nearest),
     hasSelection_(false),
     selectedLayer_(0)    // Dummy initialization
   {
-    scene.RegisterObserverCallback(
-      new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage>
-      (*this, &RadiographyWidget::OnGeometryChanged));
-
-    scene.RegisterObserverCallback(
-      new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage>
-      (*this, &RadiographyWidget::OnContentChanged));
+    SetScene(scene);
   }
 
 
@@ -216,4 +209,25 @@
       NotifyContentChanged();
     }
   }
+
+  void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene)
+  {
+    if (scene_ != NULL)
+    {
+      scene_->Unregister(this);
+    }
+
+    scene_ = scene;
+
+    scene_->RegisterObserverCallback(
+      new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage>
+      (*this, &RadiographyWidget::OnGeometryChanged));
+
+    scene_->RegisterObserverCallback(
+      new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage>
+      (*this, &RadiographyWidget::OnContentChanged));
+
+    // force redraw
+    FitContent();
+  }
 }
--- a/Framework/Radiography/RadiographyWidget.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Radiography/RadiographyWidget.h	Thu Nov 29 15:11:19 2018 +0100
@@ -32,7 +32,7 @@
     public IObserver
   {
   private:
-    RadiographyScene&                      scene_;
+    boost::shared_ptr<RadiographyScene>    scene_;
     std::auto_ptr<Orthanc::ImageAccessor>  floatBuffer_;
     std::auto_ptr<CairoSurface>            cairoBuffer_;
     bool                                   invert_;
@@ -47,7 +47,7 @@
   protected:
     virtual Extent2D GetSceneExtent()
     {
-      return scene_.GetSceneExtent();
+      return scene_->GetSceneExtent();
     }
 
     virtual bool RenderScene(CairoContext& context,
@@ -55,14 +55,16 @@
 
   public:
     RadiographyWidget(MessageBroker& broker,
-                      RadiographyScene& scene,
+                      boost::shared_ptr<RadiographyScene> scene,  // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now)
                       const std::string& name);
 
     RadiographyScene& GetScene() const
     {
-      return scene_;
+      return *scene_;
     }
 
+    void SetScene(boost::shared_ptr<RadiographyScene> scene);
+
     void Unselect()
     {
       hasSelection_ = false;
--- a/Framework/StoneEnumerations.h	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/StoneEnumerations.h	Thu Nov 29 15:11:19 2018 +0100
@@ -157,6 +157,9 @@
     MessageType_OrthancApi_GenericHttpError_Ready,
     MessageType_OrthancApi_GenericEmptyResponse_Ready,
 
+    MessageType_Scene_GeometryChanged,
+    MessageType_Scene_ContentChanged,
+
     MessageType_ViewportChanged,
 
     // used in unit tests only
--- a/Framework/Toolbox/OrthancApiClient.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -170,7 +170,7 @@
   class OrthancApiClient::CachedHttpRequestSuccessMessage
   {
   protected:
-    const std::string&             uri_;
+    std::string                    uri_;
     void*                          answer_;
     size_t                         answerSize_;
     IWebService::HttpHeaders       answerHeaders_;
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Nov 23 16:06:23 2018 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Nov 29 15:11:19 2018 +0100
@@ -246,6 +246,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerCropTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMoveTracker.cpp
@@ -253,6 +255,9 @@
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerRotateTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneReader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneWriter.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyTextLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWindowingTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp
--- a/UnitTestsSources/TestMessageBroker.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ b/UnitTestsSources/TestMessageBroker.cpp	Thu Nov 29 15:11:19 2018 +0100
@@ -1,158 +1,416 @@
-///**
-// * Stone of Orthanc
-// * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
-// * Department, University Hospital of Liege, Belgium
-// * Copyright (C) 2017-2018 Osimis S.A., Belgium
-// *
-// * This program is free software: you can redistribute it and/or
-// * modify it under the terms of the GNU Affero General Public License
-// * as published by the Free Software Foundation, either version 3 of
-// * the License, or (at your option) any later version.
-// *
-// * This program is distributed in the hope that it will be useful, but
-// * WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// * Affero General Public License for more details.
-// *
-// * You should have received a copy of the GNU Affero General Public License
-// * along with this program. If not, see <http://www.gnu.org/licenses/>.
-// **/
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
 
 
-//#include "gtest/gtest.h"
+#include "gtest/gtest.h"
+
+#include "Framework/Messages/MessageBroker.h"
+#include "Framework/Messages/Promise.h"
+#include "Framework/Messages/IObservable.h"
+#include "Framework/Messages/IObserver.h"
+#include "Framework/Messages/MessageForwarder.h"
+
 
-//#include "../Framework/Messages/MessageBroker.h"
-//#include "../Framework/Messages/IMessage.h"
-//#include "../Framework/Messages/IObservable.h"
-//#include "../Framework/Messages/IObserver.h"
-//#include "../Framework/StoneEnumerations.h"
+int testCounter = 0;
+namespace {
+
+  using namespace OrthancStone;
+
+
+  enum CustomMessageType
+  {
+    CustomMessageType_First = MessageType_CustomMessage + 1,
+
+    CustomMessageType_Completed,
+    CustomMessageType_Increment
+  };
 
 
-//static int test1Counter = 0;
-//static int test2Counter = 0;
-//class MyFullObserver : public OrthancStone::IObserver
-//{
+  class MyObservable : public IObservable
+  {
+  public:
+    struct MyCustomMessage: public BaseMessage<CustomMessageType_Completed>
+    {
+      int payload_;
+
+      MyCustomMessage(int payload)
+        : BaseMessage(),
+          payload_(payload)
+      {}
+    };
+
+    MyObservable(MessageBroker& broker)
+      : IObservable(broker)
+    {}
+
+  };
 
-//public:
-//  MyFullObserver(OrthancStone::MessageBroker& broker)
-//    : OrthancStone::IObserver(broker)
-//  {
-////    DeclareHandledMessage(OrthancStone::MessageType_Test1);
-////    DeclareIgnoredMessage(OrthancStone::MessageType_Test2);
-//  }
+  class MyObserver : public IObserver
+  {
+  public:
+    MyObserver(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+
+    void HandleCompletedMessage(const MyObservable::MyCustomMessage& message)
+    {
+      testCounter += message.payload_;
+    }
+
+  };
+
+
+  class MyIntermediate : public IObserver, public IObservable
+  {
+    IObservable& observedObject_;
+  public:
+    MyIntermediate(MessageBroker& broker, IObservable& observedObject)
+      : IObserver(broker),
+        IObservable(broker),
+        observedObject_(observedObject)
+    {
+      observedObject_.RegisterObserverCallback(new MessageForwarder<MyObservable::MyCustomMessage>(broker, *this));
+    }
+  };
 
 
-//  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-//    switch (message.GetType())
-//    {
-//    case OrthancStone::MessageType_Test1:
-//      test1Counter++;
-//      break;
-//    case OrthancStone::MessageType_Test2:
-//      test2Counter++;
-//      break;
-//    default:
-//      throw OrthancStone::MessageNotDeclaredException(message.GetType());
-//    }
-//  }
+  class MyPromiseSource : public IObservable
+  {
+    Promise* currentPromise_;
+  public:
+    struct MyPromiseMessage: public BaseMessage<MessageType_Test1>
+    {
+      int increment;
+
+      MyPromiseMessage(int increment)
+        : BaseMessage(),
+          increment(increment)
+      {}
+    };
+
+    MyPromiseSource(MessageBroker& broker)
+      : IObservable(broker),
+        currentPromise_(NULL)
+    {}
+
+    Promise& StartSomethingAsync()
+    {
+      currentPromise_ = new Promise(GetBroker());
+      return *currentPromise_;
+    }
 
-//};
+    void CompleteSomethingAsyncWithSuccess(int payload)
+    {
+      currentPromise_->Success(MyPromiseMessage(payload));
+      delete currentPromise_;
+    }
 
-//class MyPartialObserver : public OrthancStone::IObserver
-//{
+    void CompleteSomethingAsyncWithFailure(int payload)
+    {
+      currentPromise_->Failure(MyPromiseMessage(payload));
+      delete currentPromise_;
+    }
+  };
+
 
-//public:
-//  MyPartialObserver(OrthancStone::MessageBroker& broker)
-//    : OrthancStone::IObserver(broker)
-//  {
-////    DeclareHandledMessage(OrthancStone::MessageType_Test1);
-//    // don't declare Test2 on purpose
-//  }
+  class MyPromiseTarget : public IObserver
+  {
+  public:
+    MyPromiseTarget(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+
+    void IncrementCounter(const MyPromiseSource::MyPromiseMessage& args)
+    {
+      testCounter += args.increment;
+    }
+
+    void DecrementCounter(const MyPromiseSource::MyPromiseMessage& args)
+    {
+      testCounter -= args.increment;
+    }
+  };
+}
 
 
-//  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-//    switch (message.GetType())
-//    {
-//    case OrthancStone::MessageType_Test1:
-//      test1Counter++;
-//      break;
-//    case OrthancStone::MessageType_Test2:
-//      test2Counter++;
-//      break;
-//    default:
-//      throw OrthancStone::MessageNotDeclaredException(message.GetType());
-//    }
-//  }
+TEST(MessageBroker, TestPermanentConnectionSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver    observer(broker);
+
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+
+  // Unregister the observer; make sure it's not called anymore
+  observable.Unregister(&observer);
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
+
+TEST(MessageBroker, TestMessageForwarderSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
+
+TEST(MessageBroker, TestPermanentConnectionDeleteObserver)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver*   observer = new MyObserver(broker);
 
-//};
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage));
 
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
 
-//class MyObservable : public OrthancStone::IObservable
-//{
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
+
+TEST(MessageBroker, TestMessageForwarderDeleteIntermediate)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate* intermediate = new MyIntermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate->RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
 
-//public:
-//  MyObservable(OrthancStone::MessageBroker& broker)
-//    : OrthancStone::IObservable(broker)
-//  {
-//    DeclareEmittableMessage(OrthancStone::MessageType_Test1);
-//    DeclareEmittableMessage(OrthancStone::MessageType_Test2);
-//  }
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  delete intermediate;
+
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(12, testCounter);
+}
 
-//};
+TEST(MessageBroker, TestCustomMessage)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
 
 
-//TEST(MessageBroker, NormalUsage)
-//{
-//  OrthancStone::MessageBroker broker;
-//  MyObservable observable(broker);
+TEST(MessageBroker, TestPromiseSuccessFailure)
+{
+  MessageBroker broker;
+  MyPromiseSource  source(broker);
+  MyPromiseTarget target(broker);
+
+  // test a successful promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess(10);
+  ASSERT_EQ(10, testCounter);
 
-//  test1Counter = 0;
+  // test a failing promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure(15);
+  ASSERT_EQ(-15, testCounter);
+}
 
-//  // no observers have been registered -> nothing shall happen
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+TEST(MessageBroker, TestPromiseDeleteTarget)
+{
+  MessageBroker broker;
+  MyPromiseSource source(broker);
+  MyPromiseTarget* target = new MyPromiseTarget(broker);
 
-//  ASSERT_EQ(0, test1Counter);
+  // create the promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
 
-//  // register an observer, check it is called
-//  MyFullObserver fullObserver(broker);
-//  ASSERT_NO_THROW(observable.RegisterObserver(fullObserver));
+  // delete the promise target
+  delete target;
+
+  // trigger the promise, make sure it does not throw and does not call the callback
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess(10);
+  ASSERT_EQ(0, testCounter);
 
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+  // test a failing promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure(15);
+  ASSERT_EQ(0, testCounter);
+}
+
+#if __cplusplus >= 201103L
 
-//  ASSERT_EQ(1, test1Counter);
+#include <functional>
+
+namespace OrthancStone {
+
+  template <typename TMessage>
+  class LambdaCallable : public MessageHandler<TMessage>
+  {
+  private:
 
-//  // register an invalid observer, check it raises an exception
-//  MyPartialObserver partialObserver(broker);
-//  ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException);
+    IObserver&      observer_;
+    std::function<void (const TMessage&)> lambda_;
 
-//  // check an exception is thrown when the observable emits an undeclared message
-//  ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_VolumeSlicer_GeometryReady)), OrthancStone::MessageNotDeclaredException);
+  public:
+    LambdaCallable(IObserver& observer,
+                    std::function<void (const TMessage&)> lambdaFunction) :
+             observer_(observer),
+             lambda_(lambdaFunction)
+    {
+    }
 
-//  // unregister the observer, make sure nothing happens afterwards
-//  observable.UnregisterObserver(fullObserver);
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
-//  ASSERT_EQ(1, test1Counter);
-//}
+    virtual void Apply(const IMessage& message)
+    {
+      lambda_(dynamic_cast<const TMessage&>(message));
+    }
+
+    virtual MessageType GetMessageType() const
+    {
+      return static_cast<MessageType>(TMessage::Type);
+    }
+
+    virtual IObserver* GetObserver() const
+    {
+      return &observer_;
+    }
+  };
+
 
-//TEST(MessageBroker, DeleteObserverWhileRegistered)
-//{
-//  OrthancStone::MessageBroker broker;
-//  MyObservable observable(broker);
+}
+
+TEST(MessageBroker, TestLambdaSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver*   observer = new MyObserver(broker);
 
-//  test1Counter = 0;
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;}));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(24, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
 
-//  {
-//    // register an observer, check it is called
-//    MyFullObserver observer(broker);
-//    observable.RegisterObserver(observer);
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
 
-//    observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+namespace {
+  class MyObserverWithLambda : public IObserver {
+  private:
+    int multiplier_;  // this is a private variable we want to access in a lambda
+
+  public:
+    MyObserverWithLambda(MessageBroker& broker, int multiplier, MyObservable& observable)
+      : IObserver(broker),
+        multiplier_(multiplier)
+    {
+      // register a callable to a lambda that access private members
+      observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*this, [this](const MyObservable::MyCustomMessage& message) {
+        testCounter += multiplier_ * message.payload_;
+      }));
 
-//    ASSERT_EQ(1, test1Counter);
-//  }
+    }
+  };
+}
+
+TEST(MessageBroker, TestLambdaCaptureThisAndAccessPrivateMembers)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserverWithLambda*   observer = new MyObserverWithLambda(broker, 3, observable);
 
-//  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
-//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(36, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
 
-//  ASSERT_EQ(1, test1Counter);
-//}
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(0, testCounter);
+}
+
+#endif // C++ 11
--- a/UnitTestsSources/TestMessageBroker2.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,410 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "gtest/gtest.h"
-
-#include "Framework/Messages/MessageBroker.h"
-#include "Framework/Messages/Promise.h"
-#include "Framework/Messages/IObservable.h"
-#include "Framework/Messages/IObserver.h"
-#include "Framework/Messages/MessageForwarder.h"
-
-
-int testCounter = 0;
-namespace {
-
-  using namespace OrthancStone;
-
-
-  enum CustomMessageType
-  {
-    CustomMessageType_First = MessageType_CustomMessage + 1,
-
-    CustomMessageType_Completed,
-    CustomMessageType_Increment
-  };
-
-
-  class MyObservable : public IObservable
-  {
-  public:
-    struct MyCustomMessage: public BaseMessage<CustomMessageType_Completed>
-    {
-      int payload_;
-
-      MyCustomMessage(int payload)
-        : BaseMessage(),
-          payload_(payload)
-      {}
-    };
-
-    MyObservable(MessageBroker& broker)
-      : IObservable(broker)
-    {}
-
-  };
-
-  class MyObserver : public IObserver
-  {
-  public:
-    MyObserver(MessageBroker& broker)
-      : IObserver(broker)
-    {}
-
-    void HandleCompletedMessage(const MyObservable::MyCustomMessage& message)
-    {
-      testCounter += message.payload_;
-    }
-
-  };
-
-
-  class MyIntermediate : public IObserver, public IObservable
-  {
-    IObservable& observedObject_;
-  public:
-    MyIntermediate(MessageBroker& broker, IObservable& observedObject)
-      : IObserver(broker),
-        IObservable(broker),
-        observedObject_(observedObject)
-    {
-      observedObject_.RegisterObserverCallback(new MessageForwarder<MyObservable::MyCustomMessage>(broker, *this));
-    }
-  };
-
-
-  class MyPromiseSource : public IObservable
-  {
-    Promise* currentPromise_;
-  public:
-    struct MyPromiseMessage: public BaseMessage<MessageType_Test1>
-    {
-      int increment;
-
-      MyPromiseMessage(int increment)
-        : BaseMessage(),
-          increment(increment)
-      {}
-    };
-
-    MyPromiseSource(MessageBroker& broker)
-      : IObservable(broker),
-        currentPromise_(NULL)
-    {}
-
-    Promise& StartSomethingAsync()
-    {
-      currentPromise_ = new Promise(GetBroker());
-      return *currentPromise_;
-    }
-
-    void CompleteSomethingAsyncWithSuccess(int payload)
-    {
-      currentPromise_->Success(MyPromiseMessage(payload));
-      delete currentPromise_;
-    }
-
-    void CompleteSomethingAsyncWithFailure(int payload)
-    {
-      currentPromise_->Failure(MyPromiseMessage(payload));
-      delete currentPromise_;
-    }
-  };
-
-
-  class MyPromiseTarget : public IObserver
-  {
-  public:
-    MyPromiseTarget(MessageBroker& broker)
-      : IObserver(broker)
-    {}
-
-    void IncrementCounter(const MyPromiseSource::MyPromiseMessage& args)
-    {
-      testCounter += args.increment;
-    }
-
-    void DecrementCounter(const MyPromiseSource::MyPromiseMessage& args)
-    {
-      testCounter -= args.increment;
-    }
-  };
-}
-
-
-TEST(MessageBroker2, TestPermanentConnectionSimpleUseCase)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver    observer(broker);
-
-  // create a permanent connection between an observable and an observer
-  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(20, testCounter);
-}
-
-TEST(MessageBroker2, TestMessageForwarderSimpleUseCase)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyIntermediate intermediate(broker, observable);
-  MyObserver    observer(broker);
-
-  // let the observer observers the intermediate that is actually forwarding the messages from the observable
-  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(20, testCounter);
-}
-
-TEST(MessageBroker2, TestPermanentConnectionDeleteObserver)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver*   observer = new MyObserver(broker);
-
-  // create a permanent connection between an observable and an observer
-  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // delete the observer and check that the callback is not called anymore
-  delete observer;
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(0, testCounter);
-}
-
-TEST(MessageBroker2, TestMessageForwarderDeleteIntermediate)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyIntermediate* intermediate = new MyIntermediate(broker, observable);
-  MyObserver    observer(broker);
-
-  // let the observer observers the intermediate that is actually forwarding the messages from the observable
-  intermediate->RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  delete intermediate;
-
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(12, testCounter);
-}
-
-TEST(MessageBroker2, TestCustomMessage)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyIntermediate intermediate(broker, observable);
-  MyObserver    observer(broker);
-
-  // let the observer observers the intermediate that is actually forwarding the messages from the observable
-  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(20, testCounter);
-}
-
-
-TEST(MessageBroker2, TestPromiseSuccessFailure)
-{
-  MessageBroker broker;
-  MyPromiseSource  source(broker);
-  MyPromiseTarget target(broker);
-
-  // test a successful promise
-  source.StartSomethingAsync()
-      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
-      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
-
-  testCounter = 0;
-  source.CompleteSomethingAsyncWithSuccess(10);
-  ASSERT_EQ(10, testCounter);
-
-  // test a failing promise
-  source.StartSomethingAsync()
-      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
-      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
-
-  testCounter = 0;
-  source.CompleteSomethingAsyncWithFailure(15);
-  ASSERT_EQ(-15, testCounter);
-}
-
-TEST(MessageBroker2, TestPromiseDeleteTarget)
-{
-  MessageBroker broker;
-  MyPromiseSource source(broker);
-  MyPromiseTarget* target = new MyPromiseTarget(broker);
-
-  // create the promise
-  source.StartSomethingAsync()
-      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
-      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
-
-  // delete the promise target
-  delete target;
-
-  // trigger the promise, make sure it does not throw and does not call the callback
-  testCounter = 0;
-  source.CompleteSomethingAsyncWithSuccess(10);
-  ASSERT_EQ(0, testCounter);
-
-  // test a failing promise
-  source.StartSomethingAsync()
-      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
-      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
-
-  testCounter = 0;
-  source.CompleteSomethingAsyncWithFailure(15);
-  ASSERT_EQ(0, testCounter);
-}
-
-#if __cplusplus >= 201103L
-
-#include <functional>
-
-namespace OrthancStone {
-
-  template <typename TMessage>
-  class LambdaCallable : public MessageHandler<TMessage>
-  {
-  private:
-
-    IObserver&      observer_;
-    std::function<void (const TMessage&)> lambda_;
-
-  public:
-    LambdaCallable(IObserver& observer,
-                    std::function<void (const TMessage&)> lambdaFunction) :
-             observer_(observer),
-             lambda_(lambdaFunction)
-    {
-    }
-
-    virtual void Apply(const IMessage& message)
-    {
-      lambda_(dynamic_cast<const TMessage&>(message));
-    }
-
-    virtual MessageType GetMessageType() const
-    {
-      return static_cast<MessageType>(TMessage::Type);
-    }
-
-    virtual IObserver* GetObserver() const
-    {
-      return &observer_;
-    }
-  };
-
-
-}
-
-TEST(MessageBroker2, TestLambdaSimpleUseCase)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver*   observer = new MyObserver(broker);
-
-  // create a permanent connection between an observable and an observer
-  observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;}));
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(24, testCounter);
-
-  // delete the observer and check that the callback is not called anymore
-  delete observer;
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(0, testCounter);
-}
-
-namespace {
-  class MyObserverWithLambda : public IObserver {
-  private:
-    int multiplier_;  // this is a private variable we want to access in a lambda
-
-  public:
-    MyObserverWithLambda(MessageBroker& broker, int multiplier, MyObservable& observable)
-      : IObserver(broker),
-        multiplier_(multiplier)
-    {
-      // register a callable to a lambda that access private members
-      observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*this, [this](const MyObservable::MyCustomMessage& message) {
-        testCounter += multiplier_ * message.payload_;
-      }));
-
-    }
-  };
-}
-
-TEST(MessageBroker2, TestLambdaCaptureThisAndAccessPrivateMembers)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserverWithLambda*   observer = new MyObserverWithLambda(broker, 3, observable);
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(36, testCounter);
-
-  // delete the observer and check that the callback is not called anymore
-  delete observer;
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(0, testCounter);
-}
-
-#endif // C++ 11
--- a/UnitTestsSources/TestMessageBroker2_connect_ok.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,226 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "gtest/gtest.h"
-
-#include <boost/noncopyable.hpp>
-#include <boost/function.hpp>
-#include <boost/bind.hpp>
-
-#include <string>
-#include <map>
-#include <set>
-
-int testCounter = 0;
-namespace {
-
-  enum MessageType
-  {
-    // used in unit tests only
-    MessageType_Test1,
-    MessageType_Test2,
-
-    MessageType_LastGenericStoneMessage
-  };
-
-  struct IMessage  : public boost::noncopyable
-  {
-    MessageType messageType_;
-  public:
-    IMessage(const MessageType& messageType)
-      : messageType_(messageType)
-    {}
-    virtual ~IMessage() {}
-
-    MessageType GetType() const {return messageType_;}
-  };
-
-
-  class IObserver;
-  class IObservable;
-
-  /*
-   * This is a central message broker.  It keeps track of all observers and knows
-   * when an observer is deleted.
-   * This way, it can prevent an observable to send a message to a dead observer.
-   */
-  class MessageBroker : public boost::noncopyable
-  {
-
-    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
-
-  public:
-
-    void Register(IObserver& observer)
-    {
-      activeObservers_.insert(&observer);
-    }
-
-    void Unregister(IObserver& observer)
-    {
-      activeObservers_.erase(&observer);
-    }
-
-    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
-  };
-
-
-  class IObserver : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
-
-  public:
-    IObserver(MessageBroker& broker)
-      : broker_(broker)
-    {
-      broker_.Register(*this);
-    }
-
-    virtual ~IObserver()
-    {
-      broker_.Unregister(*this);
-    }
-
-    void HandleMessage_(IObservable &from, const IMessage &message)
-    {
-
-      HandleMessage(from, message);
-    }
-
-    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
-
-
-  protected:
-
-
-  };
-
-//  struct ICallableObserver
-//  {
-//    IObserver* observer;
-//  };
-
-//  typedef void (IObserver::*ObserverSingleMesssageHandler)(IObservable& from, const IMessage& message);
-
-//  template <typename TObserver>
-//  struct CallableObserver : public ICallableObserver
-//  {
-//    void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message);
-//  };
-
-  struct CallableObserver
-  {
-    IObserver* observer;
-    boost::function<void (IObservable& from, const IMessage& message)> f;
-  };
-
-  class IObservable : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                     broker_;
-
-    std::set<IObserver*>              observers_;
-
-    std::map<MessageType, std::set<CallableObserver*> > callables_;
-  public:
-
-    IObservable(MessageBroker& broker)
-      : broker_(broker)
-    {
-    }
-    virtual ~IObservable()
-    {
-    }
-
-    void EmitMessage(const IMessage& message)
-    {
-      //broker_.EmitMessage(*this, observers_, message);
-
-      // TODO check if observer is still alive and call !
-      CallableObserver* callable = *(callables_[message.GetType()].begin());
-      callable->f(*this, message);
-    }
-
-    void RegisterObserver(IObserver& observer)
-    {
-      observers_.insert(&observer);
-    }
-
-    void UnregisterObserver(IObserver& observer)
-    {
-      observers_.erase(&observer);
-    }
-
-
-    //template<typename TObserver> void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message))
-    void Connect(MessageType messageType, IObserver& observer, boost::function<void (IObservable& from, const IMessage& message)> f)
-    {
-      callables_[messageType] = std::set<CallableObserver*>();
-      CallableObserver* callable = new CallableObserver();
-      callable->observer = &observer;
-      callable->f = f;
-      callables_[messageType].insert(callable);
-    }
-  };
-
-
-  class MyObservable : public IObservable
-  {
-  public:
-    MyObservable(MessageBroker& broker)
-      : IObservable(broker)
-    {}
-  };
-
-  class MyObserver : public IObserver
-  {
-  public:
-    MyObserver(MessageBroker& broker)
-      : IObserver(broker)
-    {}
-    virtual void HandleMessage(IObservable& from, const IMessage& message) {}
-    void HandleSpecificMessage(IObservable& from, const IMessage& message)
-    {
-      testCounter++;
-    }
-
-  };
-
-}
-
-//#define STONE_CONNECT(observabe, messageType, observerPtr, observerMemberFnPtr)
-
-TEST(MessageBroker2, Test1)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver    observer(broker);
-
-
-  observable.Connect(MessageType_Test1, observer, boost::bind(&MyObserver::HandleSpecificMessage, &observer, _1, _2));
-  //STONE_CONNECT(observable, MessageType_Test1, observer, &MyObserver::HandleSpecificMessage)
-  observable.EmitMessage(IMessage(MessageType_Test1));
-
-  ASSERT_EQ(1, testCounter);
-}
-
-
--- a/UnitTestsSources/TestMessageBroker2_promise_and_connect_ok.cpp	Fri Nov 23 16:06:23 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,520 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "gtest/gtest.h"
-
-#include <boost/noncopyable.hpp>
-#include <boost/function.hpp>
-#include <boost/bind.hpp>
-
-#include <string>
-#include <map>
-#include <set>
-
-int testCounter = 0;
-namespace {
-
-  enum MessageType
-  {
-    MessageType_Test1,
-    MessageType_Test2,
-
-    MessageType_CustomMessage,
-    MessageType_LastGenericStoneMessage
-  };
-
-  struct IMessage  : public boost::noncopyable
-  {
-    MessageType messageType_;
-  public:
-    IMessage(const MessageType& messageType)
-      : messageType_(messageType)
-    {}
-    virtual ~IMessage() {}
-
-    virtual int GetType() const {return messageType_;}
-  };
-
-
-  struct ICustomMessage  : public IMessage
-  {
-    int customMessageType_;
-  public:
-    ICustomMessage(int customMessageType)
-      : IMessage(MessageType_CustomMessage),
-        customMessageType_(customMessageType)
-    {}
-    virtual ~ICustomMessage() {}
-
-    virtual int GetType() const {return customMessageType_;}
-  };
-
-
-  class IObserver;
-  class IObservable;
-  class IPromiseTarget;
-  class IPromiseSource;
-  class Promise;
-
-  /*
-   * This is a central message broker.  It keeps track of all observers and knows
-   * when an observer is deleted.
-   * This way, it can prevent an observable to send a message to a delete observer.
-   * It does the same book-keeping for the IPromiseTarget and IPromiseSource
-   */
-  class MessageBroker : public boost::noncopyable
-  {
-
-    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
-    std::set<IPromiseTarget*> activePromiseTargets_;
-    std::set<IPromiseSource*> activePromiseSources_;
-
-  public:
-
-    void Register(IObserver& observer)
-    {
-      activeObservers_.insert(&observer);
-    }
-
-    void Unregister(IObserver& observer)
-    {
-      activeObservers_.erase(&observer);
-    }
-
-    void Register(IPromiseTarget& target)
-    {
-      activePromiseTargets_.insert(&target);
-    }
-
-    void Unregister(IPromiseTarget& target)
-    {
-      activePromiseTargets_.erase(&target);
-    }
-
-    void Register(IPromiseSource& source)
-    {
-      activePromiseSources_.insert(&source);
-    }
-
-    void Unregister(IPromiseSource& source)
-    {
-      activePromiseSources_.erase(&source);
-    }
-
-    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
-
-    bool IsActive(IPromiseTarget* target)
-    {
-      return activePromiseTargets_.find(target) != activePromiseTargets_.end();
-    }
-
-    bool IsActive(IPromiseSource* source)
-    {
-      return activePromiseSources_.find(source) != activePromiseSources_.end();
-    }
-
-    bool IsActive(IObserver* observer)
-    {
-      return activeObservers_.find(observer) != activeObservers_.end();
-    }
-  };
-
-  struct IPromiseArgs
-  {
-public:
-    virtual ~IPromiseArgs() {}
-  };
-
-  class EmptyPromiseArguments : public IPromiseArgs
-  {
-
-  };
-
-  class Promise : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
-
-    IPromiseTarget*                                           successTarget_;
-    boost::function<void (const IPromiseArgs& message)>       successCallable_;
-
-    IPromiseTarget*                                           failureTarget_;
-    boost::function<void (const IPromiseArgs& message)>       failureCallable_;
-
-  public:
-    Promise(MessageBroker& broker)
-      : broker_(broker),
-        successTarget_(NULL),
-        failureTarget_(NULL)
-    {
-    }
-
-    void Success(const IPromiseArgs& message)
-    {
-      // check the target is still alive in the broker
-      if (broker_.IsActive(successTarget_))
-      {
-        successCallable_(message);
-      }
-    }
-
-    void Failure(const IPromiseArgs& message)
-    {
-      // check the target is still alive in the broker
-      if (broker_.IsActive(failureTarget_))
-      {
-        failureCallable_(message);
-      }
-    }
-
-    Promise& Then(IPromiseTarget* target, boost::function<void (const IPromiseArgs& message)> f)
-    {
-      if (successTarget_ != NULL)
-      {
-        // TODO: throw throw new "Promise may only have a single success target"
-      }
-      successTarget_ = target;
-      successCallable_ = f;
-      return *this;
-    }
-
-    Promise& Else(IPromiseTarget* target, boost::function<void (const IPromiseArgs& message)> f)
-    {
-      if (failureTarget_ != NULL)
-      {
-        // TODO: throw throw new "Promise may only have a single failure target"
-      }
-      failureTarget_ = target;
-      failureCallable_ = f;
-      return *this;
-    }
-
-  };
-
-  class IObserver : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
-
-  public:
-    IObserver(MessageBroker& broker)
-      : broker_(broker)
-    {
-      broker_.Register(*this);
-    }
-
-    virtual ~IObserver()
-    {
-      broker_.Unregister(*this);
-    }
-
-  };
-
-  class IPromiseTarget : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
-
-  public:
-    IPromiseTarget(MessageBroker& broker)
-      : broker_(broker)
-    {
-      broker_.Register(*this);
-    }
-
-    virtual ~IPromiseTarget()
-    {
-      broker_.Unregister(*this);
-    }
-  };
-
-  class IPromiseSource : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
-
-  public:
-    IPromiseSource(MessageBroker& broker)
-      : broker_(broker)
-    {
-      broker_.Register(*this);
-    }
-
-    virtual ~IPromiseSource()
-    {
-      broker_.Unregister(*this);
-    }
-  };
-
-
-  struct CallableObserver
-  {
-    IObserver* observer;
-    boost::function<void (IObservable& from, const IMessage& message)> f;
-  };
-
-  class IObservable : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                     broker_;
-
-    std::set<IObserver*>              observers_;
-
-    std::map<int, std::set<CallableObserver*> > callables_;
-  public:
-
-    IObservable(MessageBroker& broker)
-      : broker_(broker)
-    {
-    }
-    virtual ~IObservable()
-    {
-    }
-
-    void EmitMessage(const IMessage& message)
-    {
-      //broker_.EmitMessage(*this, observers_, message);
-      int messageType = message.GetType();
-      if (callables_.find(messageType) != callables_.end())
-      {
-        for (std::set<CallableObserver*>::iterator observer = callables_[messageType].begin(); observer != callables_[messageType].end(); observer++)
-        {
-          CallableObserver* callable = *observer;
-          if (broker_.IsActive(callable->observer))
-          {
-            callable->f(*this, message);
-          }
-        }
-      }
-
-    }
-
-    void RegisterObserver(IObserver& observer)
-    {
-      observers_.insert(&observer);
-    }
-
-    void UnregisterObserver(IObserver& observer)
-    {
-      observers_.erase(&observer);
-    }
-
-    //template<typename TObserver> void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message))
-    void Connect(int messageType, IObserver& observer, boost::function<void (IObservable& from, const IMessage& message)> f)
-    {
-      callables_[messageType] = std::set<CallableObserver*>();
-      CallableObserver* callable = new CallableObserver();
-      callable->observer = &observer;
-      callable->f = f;
-      callables_[messageType].insert(callable);
-    }
-  };
-
-
-  enum CustomMessageType
-  {
-    CustomMessageType_First = MessageType_LastGenericStoneMessage + 1,
-
-    CustomMessageType_Completed
-  };
-
-  class MyObservable : public IObservable
-  {
-  public:
-    struct MyCustomMessage: public ICustomMessage
-    {
-      int payload_;
-      MyCustomMessage(int payload)
-        : ICustomMessage(CustomMessageType_Completed),
-          payload_(payload)
-      {}
-    };
-
-    MyObservable(MessageBroker& broker)
-      : IObservable(broker)
-    {}
-
-  };
-
-  class MyObserver : public IObserver
-  {
-  public:
-    MyObserver(MessageBroker& broker)
-      : IObserver(broker)
-    {}
-    void HandleCompletedMessage(IObservable& from, const IMessage& message)
-    {
-      const MyObservable::MyCustomMessage& msg = dynamic_cast<const MyObservable::MyCustomMessage&>(message);
-      testCounter += msg.payload_;
-    }
-
-  };
-
-
-  class MyPromiseSource : public IPromiseSource
-  {
-    Promise* currentPromise_;
-  public:
-    struct MyPromiseArgs : public IPromiseArgs
-    {
-      int increment;
-    };
-
-    MyPromiseSource(MessageBroker& broker)
-      : IPromiseSource(broker),
-        currentPromise_(NULL)
-    {}
-
-    Promise& StartSomethingAsync()
-    {
-      currentPromise_ = new Promise(broker_);
-      return *currentPromise_;
-    }
-
-    void CompleteSomethingAsyncWithSuccess()
-    {
-      currentPromise_->Success(EmptyPromiseArguments());
-      delete currentPromise_;
-    }
-
-    void CompleteSomethingAsyncWithFailure()
-    {
-      currentPromise_->Failure(EmptyPromiseArguments());
-      delete currentPromise_;
-    }
-  };
-
-
-  class MyPromiseTarget : public IPromiseTarget
-  {
-  public:
-    MyPromiseTarget(MessageBroker& broker)
-      : IPromiseTarget(broker)
-    {}
-
-    void IncrementCounter(const IPromiseArgs& args)
-    {
-      testCounter++;
-    }
-
-    void DecrementCounter(const IPromiseArgs& args)
-    {
-      testCounter--;
-    }
-  };
-}
-
-#define CONNECT_MESSAGES(observablePtr, messageType, observerPtr, observerFnPtr) (observablePtr)->Connect(messageType, *(observerPtr), boost::bind(observerFnPtr, observerPtr, _1, _2))
-#define PTHEN(targetPtr, targetFnPtr) Then(targetPtr, boost::bind(targetFnPtr, targetPtr, _1))
-#define PELSE(targetPtr, targetFnPtr) Else(targetPtr, boost::bind(targetFnPtr, targetPtr, _1))
-
-
-TEST(MessageBroker2, TestPermanentConnectionSimpleUseCase)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver    observer(broker);
-
-  // create a permanent connection between an observable and an observer
-  CONNECT_MESSAGES(&observable, CustomMessageType_Completed, &observer, &MyObserver::HandleCompletedMessage);
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(20, testCounter);
-}
-
-TEST(MessageBroker2, TestPermanentConnectionDeleteObserver)
-{
-  MessageBroker broker;
-  MyObservable  observable(broker);
-  MyObserver*   observer = new MyObserver(broker);
-
-  // create a permanent connection between an observable and an observer
-  CONNECT_MESSAGES(&observable, CustomMessageType_Completed, observer, &MyObserver::HandleCompletedMessage);
-
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(12));
-  ASSERT_EQ(12, testCounter);
-
-  // delete the observer and check that the callback is not called anymore
-  delete observer;
-
-  // the connection is permanent; if we emit the same message again, the observer will be notified again
-  testCounter = 0;
-  observable.EmitMessage(MyObservable::MyCustomMessage(20));
-  ASSERT_EQ(0, testCounter);
-}
-
-
-TEST(MessageBroker2, TestPromiseSuccessFailure)
-{
-  MessageBroker broker;
-  MyPromiseSource  source(broker);
-  MyPromiseTarget target(broker);
-
-  // test a successful promise
-  source.StartSomethingAsync()
-      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
-      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
-
-  testCounter = 0;
-  source.CompleteSomethingAsyncWithSuccess();
-  ASSERT_EQ(1, testCounter);
-
-  // test a failing promise
-  source.StartSomethingAsync()
-      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
-      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
-
-  testCounter = 0;
-  source.CompleteSomethingAsyncWithFailure();
-  ASSERT_EQ(-1, testCounter);
-}
-
-//TEST(MessageBroker2, TestPromiseDeleteTarget)
-//{
-//  MessageBroker broker;
-//  MyPromiseSource  source(broker);
-//  MyPromiseTarget target(broker);
-
-//  // test a successful promise
-//  source.StartSomethingAsync()
-//      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
-//      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
-
-//  testCounter = 0;
-//  source.CompleteSomethingAsyncWithSuccess();
-//  ASSERT_EQ(1, testCounter);
-
-//  // test a failing promise
-//  source.StartSomethingAsync()
-//      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
-//      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
-
-//  testCounter = 0;
-//  source.CompleteSomethingAsyncWithFailure();
-//  ASSERT_EQ(-1, testCounter);
-//}