changeset 860:238693c3bc51 am-dev

merge default -> am-dev
author Alain Mazy <alain@mazy.be>
date Mon, 24 Jun 2019 14:35:00 +0200
parents a6e17a5a39e7 (current diff) 6845a05f9526 (diff)
children 23701fbf228e
files .hgignore Applications/Qt/QCairoWidget.h Applications/Qt/QtStoneApplicationRunner.cpp Framework/Layers/CircleMeasureTracker.cpp Framework/Layers/CircleMeasureTracker.h Framework/Layers/ColorFrameRenderer.cpp Framework/Layers/ColorFrameRenderer.h Framework/Layers/DicomSeriesVolumeSlicer.cpp Framework/Layers/DicomSeriesVolumeSlicer.h Framework/Layers/DicomStructureSetSlicer.cpp Framework/Layers/DicomStructureSetSlicer.h Framework/Layers/FrameRenderer.cpp Framework/Layers/FrameRenderer.h Framework/Layers/GrayscaleFrameRenderer.cpp Framework/Layers/GrayscaleFrameRenderer.h Framework/Layers/ILayerRenderer.h Framework/Layers/IVolumeSlicer.h Framework/Layers/LineLayerRenderer.cpp Framework/Layers/LineLayerRenderer.h Framework/Layers/LineMeasureTracker.cpp Framework/Layers/LineMeasureTracker.h Framework/Layers/RenderStyle.cpp Framework/Layers/RenderStyle.h Framework/Layers/SeriesFrameRendererFactory.cpp Framework/Layers/SeriesFrameRendererFactory.h Framework/Layers/SingleFrameRendererFactory.cpp Framework/Layers/SingleFrameRendererFactory.h Framework/Layers/SliceOutlineRenderer.cpp Framework/Layers/SliceOutlineRenderer.h Framework/Radiography/RadiographySceneReader.cpp Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.h Framework/Scene2DViewport/EditCircleMeasureTracker.cpp Framework/Scene2DViewport/EditCircleMeasureTracker.h Framework/Scene2DViewport/EditLineMeasureTracker.cpp Framework/Scene2DViewport/EditLineMeasureTracker.h Framework/Scene2DViewport/MeasureTools.cpp Framework/Scene2DViewport/MeasureTools.h Framework/Scene2DViewport/PointerTypes.h Framework/SmartLoader.cpp Framework/SmartLoader.h Framework/Toolbox/BaseWebService.cpp Framework/Toolbox/BaseWebService.h Framework/Toolbox/DicomFrameConverter.cpp Framework/Toolbox/DicomFrameConverter.h Framework/Toolbox/DownloadStack.cpp Framework/Toolbox/DownloadStack.h Framework/Toolbox/IDelayedCallExecutor.h Framework/Toolbox/ISeriesLoader.h Framework/Toolbox/IWebService.cpp Framework/Toolbox/IWebService.h Framework/Toolbox/MessagingToolbox.cpp Framework/Toolbox/MessagingToolbox.h Framework/Toolbox/OrientedBoundingBox.cpp Framework/Toolbox/OrientedBoundingBox.h Framework/Toolbox/OrthancApiClient.cpp Framework/Toolbox/OrthancApiClient.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Toolbox/ParallelSlices.cpp Framework/Toolbox/ParallelSlices.h Framework/Toolbox/ParallelSlicesCursor.cpp Framework/Toolbox/ParallelSlicesCursor.h Framework/Toolbox/Slice.cpp Framework/Toolbox/Slice.h Framework/Toolbox/ViewportGeometry.cpp Framework/Toolbox/ViewportGeometry.h Framework/Toolbox/VolumeImageGeometry.cpp Framework/Toolbox/VolumeImageGeometry.h Framework/Viewport/CairoContext.cpp Framework/Viewport/CairoContext.h Framework/Viewport/CairoFont.cpp Framework/Viewport/CairoFont.h Framework/Viewport/CairoSurface.cpp Framework/Viewport/CairoSurface.h Framework/Viewport/IMouseTracker.h Framework/Viewport/IStatusBar.h Framework/Viewport/IViewport.h Framework/Viewport/WidgetViewport.cpp Framework/Viewport/WidgetViewport.h Framework/Volumes/ISlicedVolume.h Framework/Volumes/IVolumeLoader.h Framework/Volumes/StructureSetLoader.cpp Framework/Volumes/StructureSetLoader.h Framework/Widgets/CairoWidget.cpp Framework/Widgets/CairoWidget.h Framework/Widgets/EmptyWidget.cpp Framework/Widgets/EmptyWidget.h Framework/Widgets/IWidget.h Framework/Widgets/IWorldSceneInteractor.h Framework/Widgets/IWorldSceneMouseTracker.h Framework/Widgets/LayoutWidget.cpp Framework/Widgets/LayoutWidget.h Framework/Widgets/PanMouseTracker.cpp Framework/Widgets/PanMouseTracker.h Framework/Widgets/PanZoomMouseTracker.cpp Framework/Widgets/PanZoomMouseTracker.h Framework/Widgets/SliceViewerWidget.cpp Framework/Widgets/SliceViewerWidget.h Framework/Widgets/TestCairoWidget.cpp Framework/Widgets/TestCairoWidget.h Framework/Widgets/TestWorldSceneWidget.cpp Framework/Widgets/TestWorldSceneWidget.h Framework/Widgets/WidgetBase.cpp Framework/Widgets/WidgetBase.h Framework/Widgets/WorldSceneWidget.cpp Framework/Widgets/WorldSceneWidget.h Framework/Widgets/ZoomMouseTracker.cpp Framework/Widgets/ZoomMouseTracker.h Framework/dev.h Platforms/Wasm/Defaults.cpp Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 408 files changed, 27086 insertions(+), 18712 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed Jun 19 17:36:33 2019 +0200
+++ b/.hgignore	Mon Jun 24 14:35:00 2019 +0200
@@ -35,3 +35,6 @@
 Samples/Sdl/CMakeLists.txt.orig
 Samples/Qt/ThirdPartyDownloads/
 
+Samples/WebAssembly/build/
+Samples/WebAssembly/ThirdPartyDownloads/
+Samples/WebAssembly/installDir/
--- a/.hgtags	Wed Jun 19 17:36:33 2019 +0200
+++ b/.hgtags	Mon Jun 24 14:35:00 2019 +0200
@@ -1,1 +1,2 @@
 90f3a60576a9f08dcf783752a7f67ce0615a5371 rtviewer19
+6d15261f9c9965a2f4b64658e318b370933b175e toa2019061901
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/GuiAdapter.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,799 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "GuiAdapter.h"
+
+#if ORTHANC_ENABLE_OPENGL == 1
+#  include "../../Framework/OpenGL/OpenGLIncludes.h"
+#endif
+
+#if ORTHANC_ENABLE_SDL == 1
+#  include <SDL_video.h>
+#  include <SDL_render.h>
+#  include <SDL.h>
+#endif
+
+#if ORTHANC_ENABLE_THREADS == 1
+#  include "../../Framework/Messages/LockingEmitter.h"
+#endif
+
+namespace OrthancStone
+{
+  void GuiAdapter::RegisterWidget(boost::shared_ptr<IGuiAdapterWidget> widget)
+  {
+    widgets_.push_back(widget);
+  }
+
+  std::ostream& operator<<(
+    std::ostream& os, const GuiAdapterKeyboardEvent& event)
+  {
+    os << "ctrl: " << event.ctrlKey << ", " <<
+      "shift: " << event.shiftKey << ", " <<
+      "alt: " << event.altKey;
+    return os;
+  }
+
+#if ORTHANC_ENABLE_WASM == 1
+  void GuiAdapter::Run()
+  {
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterUiEvent& dest,
+    int                      eventType,
+    const EmscriptenUiEvent& src)
+  {
+    // no data for now
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterMouseEvent& dest,
+    int                         eventType,
+    const EmscriptenMouseEvent& src)
+  {
+    memset(&dest, 0, sizeof(GuiAdapterMouseEvent));
+    switch (eventType)
+    {
+    case EMSCRIPTEN_EVENT_CLICK:
+      LOG(ERROR) << "Emscripten EMSCRIPTEN_EVENT_CLICK is not supported";
+      ORTHANC_ASSERT(false, "Not supported");
+      break;
+    case EMSCRIPTEN_EVENT_MOUSEDOWN:
+      dest.type = GUIADAPTER_EVENT_MOUSEDOWN;
+      break;
+    case EMSCRIPTEN_EVENT_MOUSEMOVE:
+      dest.type = GUIADAPTER_EVENT_MOUSEMOVE;
+      break;
+    case EMSCRIPTEN_EVENT_MOUSEUP:
+      dest.type = GUIADAPTER_EVENT_MOUSEUP;
+      break;
+    case EMSCRIPTEN_EVENT_WHEEL:
+      dest.type = GUIADAPTER_EVENT_WHEEL;
+      break;
+
+    default:
+      LOG(ERROR) << "Emscripten event: " << eventType << " is not supported";
+      ORTHANC_ASSERT(false, "Not supported");
+    }
+    //dest.timestamp = src.timestamp;
+    //dest.screenX = src.screenX;
+    //dest.screenY = src.screenY;
+    //dest.clientX = src.clientX;
+    //dest.clientY = src.clientY;
+    dest.ctrlKey = src.ctrlKey;
+    dest.shiftKey = src.shiftKey;
+    dest.altKey = src.altKey;
+    //dest.metaKey = src.metaKey;
+    dest.button = src.button;
+    //dest.buttons = src.buttons;
+    //dest.movementX = src.movementX;
+    //dest.movementY = src.movementY;
+    dest.targetX = src.targetX;
+    dest.targetY = src.targetY;
+    //dest.canvasX = src.canvasX;
+    //dest.canvasY = src.canvasY;
+    //dest.padding = src.padding;
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterWheelEvent& dest,
+    int                         eventType,
+    const EmscriptenWheelEvent& src)
+  {
+    ConvertFromPlatform(dest.mouse, eventType, src.mouse);
+    dest.deltaX = src.deltaX;
+    dest.deltaY = src.deltaY;
+    switch (src.deltaMode)
+    {
+    case DOM_DELTA_PIXEL:
+      dest.deltaMode = GUIADAPTER_DELTA_PIXEL;
+      break;
+    case DOM_DELTA_LINE:
+      dest.deltaMode = GUIADAPTER_DELTA_LINE;
+      break;
+    case DOM_DELTA_PAGE:
+      dest.deltaMode = GUIADAPTER_DELTA_PAGE;
+      break;
+    default:
+      ORTHANC_ASSERT(false, "Unknown deltaMode: " << src.deltaMode <<
+        " in wheel event...");
+    }
+    dest.deltaMode = src.deltaMode;
+  }
+
+  void ConvertFromPlatform(
+    GuiAdapterKeyboardEvent& dest,
+    const EmscriptenKeyboardEvent& src)
+  {
+    dest.ctrlKey = src.ctrlKey;
+    dest.shiftKey = src.shiftKey;
+    dest.altKey = src.altKey;
+  }
+   
+  template<typename GenericFunc>
+  struct FuncAdapterPayload
+  {
+    std::string canvasId;
+    void* userData;
+    GenericFunc callback;
+  };
+
+  template<typename GenericFunc,
+           typename GuiAdapterEvent,
+           typename EmscriptenEvent>
+  EM_BOOL OnEventAdapterFunc(
+    int eventType, const EmscriptenEvent* emEvent, void* userData)
+  {
+
+    // userData is OnMouseWheelFuncAdapterPayload
+    FuncAdapterPayload<GenericFunc>* payload =
+      reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
+    // LOG(INFO) << "OnEventAdapterFunc";
+    // LOG(INFO) << "------------------";
+    // LOG(INFO) << "eventType: " << eventType << " wheelEvent: " << 
+    //   (int)wheelEvent << " userData: " << userData << 
+    //   " payload->userData: " << payload->userData;
+    
+    GuiAdapterEvent guiEvent;
+    ConvertFromPlatform(guiEvent, eventType, *emEvent);
+    bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData);
+    return static_cast<EM_BOOL>(ret);
+  }
+
+  template<typename GenericFunc,
+    typename GuiAdapterEvent,
+    typename EmscriptenEvent>
+    EM_BOOL OnEventAdapterFunc2(
+      int /*eventType*/, const EmscriptenEvent* wheelEvent, void* userData)
+  {
+    // userData is OnMouseWheelFuncAdapterPayload
+    FuncAdapterPayload<GenericFunc>* payload =
+      reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
+    
+    GuiAdapterEvent guiEvent;
+    ConvertFromPlatform(guiEvent, *wheelEvent);
+    bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData);
+    return static_cast<EM_BOOL>(ret);
+  }
+
+  template<typename GenericFunc>
+    EM_BOOL OnEventAdapterFunc3(
+      double time, void* userData)
+  {
+    // userData is OnMouseWheelFuncAdapterPayload
+    FuncAdapterPayload<GenericFunc>* payload =
+      reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData);
+    //std::auto_ptr< FuncAdapterPayload<GenericFunc> > deleter(payload);
+    bool ret = (*(payload->callback))(time, payload->userData);
+    return static_cast<EM_BOOL>(ret);
+  }
+
+  // resize: (const char* target, void* userData, EM_BOOL useCapture, em_ui_callback_func callback)
+  template<
+    typename GenericFunc, 
+    typename GuiAdapterEvent, 
+    typename EmscriptenEvent, 
+    typename EmscriptenSetCallbackFunc>
+  static void SetCallback(
+    EmscriptenSetCallbackFunc emFunc, 
+    std::string canvasId, void* userData, bool capture, GenericFunc func)
+  {
+    // TODO: write RemoveCallback with an int id that gets returned from
+    // here
+    FuncAdapterPayload<GenericFunc>* payload = 
+      new FuncAdapterPayload<GenericFunc>();
+    std::auto_ptr<FuncAdapterPayload<GenericFunc> > payloadP(payload);
+    payload->canvasId = canvasId;
+    payload->callback = func;
+    payload->userData = userData;
+    void* userDataRaw = reinterpret_cast<void*>(payload);
+    // LOG(INFO) << "SetCallback -- userDataRaw: " << userDataRaw <<
+    //   " payload: " << payload << " payload->userData: " << payload->userData;
+    (*emFunc)(
+      canvasId.c_str(),
+      userDataRaw,
+      static_cast<EM_BOOL>(capture),
+      &OnEventAdapterFunc<GenericFunc, GuiAdapterEvent, EmscriptenEvent>,
+      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
+    payloadP.release();
+  }
+
+  template<
+    typename GenericFunc,
+    typename GuiAdapterEvent,
+    typename EmscriptenEvent,
+    typename EmscriptenSetCallbackFunc>
+    static void SetCallback2(
+      EmscriptenSetCallbackFunc emFunc,
+      std::string canvasId, void* userData, bool capture, GenericFunc func)
+  {
+    std::auto_ptr<FuncAdapterPayload<GenericFunc> > payload(
+      new FuncAdapterPayload<GenericFunc>()
+    );
+    payload->canvasId = canvasId;
+    payload->callback = func;
+    payload->userData = userData;
+    void* userDataRaw = reinterpret_cast<void*>(payload.release());
+    (*emFunc)(
+      canvasId.c_str(),
+      userDataRaw,
+      static_cast<EM_BOOL>(capture),
+      &OnEventAdapterFunc2<GenericFunc, GuiAdapterEvent, EmscriptenEvent>,
+      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
+  }
+
+  template<
+    typename GenericFunc,
+    typename EmscriptenSetCallbackFunc>
+    static void SetAnimationFrameCallback(
+      EmscriptenSetCallbackFunc emFunc,
+      void* userData, GenericFunc func)
+  {
+    // LOG(ERROR) << "SetAnimationFrameCallback !!!!!! (RequestAnimationFrame)";
+    std::auto_ptr<FuncAdapterPayload<GenericFunc> > payload(
+      new FuncAdapterPayload<GenericFunc>()
+    );
+    payload->canvasId = "UNDEFINED";
+    payload->callback = func;
+    payload->userData = userData;
+    void* userDataRaw = reinterpret_cast<void*>(payload.release());
+    (*emFunc)(
+      &OnEventAdapterFunc3<GenericFunc>,
+      userDataRaw);
+  }
+
+  void GuiAdapter::SetWheelCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func)
+  {
+    SetCallback<OnMouseWheelFunc, GuiAdapterWheelEvent, EmscriptenWheelEvent>(
+      &emscripten_set_wheel_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetMouseDownCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+  {
+    SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>(
+      &emscripten_set_mousedown_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetMouseMoveCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+  {
+    // LOG(INFO) << "SetMouseMoveCallback -- " << "supplied userData: " << 
+    //   userData;
+
+    SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>(
+      &emscripten_set_mousemove_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetMouseUpCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+  {
+    SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>(
+      &emscripten_set_mouseup_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetKeyDownCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyDownFunc func)
+  {
+    SetCallback2<OnKeyDownFunc, GuiAdapterKeyboardEvent, EmscriptenKeyboardEvent>(
+      &emscripten_set_keydown_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetKeyUpCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyUpFunc func)
+  {
+    SetCallback2<OnKeyUpFunc, GuiAdapterKeyboardEvent, EmscriptenKeyboardEvent>(
+      &emscripten_set_keyup_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::SetResizeCallback(
+    std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func)
+  {
+    SetCallback<OnWindowResizeFunc, GuiAdapterUiEvent, EmscriptenUiEvent>(
+      &emscripten_set_resize_callback_on_thread,
+      canvasId,
+      userData,
+      capture,
+      func);
+  }
+
+  void GuiAdapter::RequestAnimationFrame(
+    OnAnimationFrameFunc func, void* userData)
+  {
+    // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+";
+    // LOG(ERROR) << "RequestAnimationFrame";
+    // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+";
+    SetAnimationFrameCallback<OnAnimationFrameFunc>(
+      &emscripten_request_animation_frame_loop,
+      userData,
+      func);
+  }
+
+#if 0
+  void GuiAdapter::SetKeyDownCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyDownFunc func)
+  {
+    emscripten_set_keydown_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func);
+  }
+  void GuiAdapter::SetKeyUpCallback(
+    std::string canvasId, void* userData, bool capture, OnKeyUpFunc func)
+  {
+    emscripten_set_keyup_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func);
+  }
+
+  void GuiAdapter::SetResizeCallback(std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func)
+  {
+    emscripten_set_resize_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func);
+  }
+
+  void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData)
+  {
+    emscripten_request_animation_frame_loop(func, userData);
+  }
+#endif
+
+
+#else
+
+// SDL ONLY
+void ConvertFromPlatform(
+  GuiAdapterMouseEvent& dest,
+  bool ctrlPressed, bool shiftPressed, bool altPressed,
+  const SDL_Event& source)
+{
+  memset(&dest, 0, sizeof(GuiAdapterMouseEvent));
+  switch (source.type)
+  {
+  case SDL_MOUSEBUTTONDOWN:
+    dest.type = GUIADAPTER_EVENT_MOUSEDOWN;
+    break;
+  case SDL_MOUSEMOTION:
+    dest.type = GUIADAPTER_EVENT_MOUSEMOVE;
+    break;
+  case SDL_MOUSEBUTTONUP:
+    dest.type = GUIADAPTER_EVENT_MOUSEUP;
+    break;
+  case SDL_MOUSEWHEEL:
+    dest.type = GUIADAPTER_EVENT_WHEEL;
+    break;
+  default:
+    LOG(ERROR) << "SDL event: " << source.type << " is not supported";
+    ORTHANC_ASSERT(false, "Not supported");
+    }
+  //dest.timestamp = src.timestamp;
+  //dest.screenX = src.screenX;
+  //dest.screenY = src.screenY;
+  //dest.clientX = src.clientX;
+  //dest.clientY = src.clientY;
+  dest.ctrlKey = ctrlPressed;
+  dest.shiftKey = shiftPressed;
+  dest.altKey = altPressed;
+  //dest.metaKey = src.metaKey;
+  switch (source.button.button)
+  {
+  case SDL_BUTTON_MIDDLE:
+    dest.button = 1;
+    break;
+
+  case SDL_BUTTON_RIGHT:
+    dest.button = 2;
+    break;
+
+  case SDL_BUTTON_LEFT:
+    dest.button = 0;
+    break;
+
+  default:
+    break;
+  }
+  //dest.buttons = src.buttons;
+  //dest.movementX = src.movementX;
+  //dest.movementY = src.movementY;
+  dest.targetX = source.button.x;
+  dest.targetY = source.button.y;
+  //dest.canvasX = src.canvasX;
+  //dest.canvasY = src.canvasY;
+  //dest.padding = src.padding;
+  }
+
+void ConvertFromPlatform(
+  GuiAdapterWheelEvent& dest,
+  bool ctrlPressed, bool shiftPressed, bool altPressed,
+  const SDL_Event& source)
+{
+  ConvertFromPlatform(dest.mouse, ctrlPressed, shiftPressed, altPressed, source);
+  dest.deltaX = source.wheel.x;
+  dest.deltaY = source.wheel.y;
+}
+
+
+
+  // SDL ONLY
+  void GuiAdapter::SetResizeCallback(
+    std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func)
+  {
+    resizeHandlers_.push_back(EventHandlerData<OnWindowResizeFunc>(canvasId, func, userData));
+  }
+
+  // SDL ONLY
+  void GuiAdapter::SetMouseDownCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc func)
+ {
+    mouseDownHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData));
+ }
+
+  // SDL ONLY
+  void GuiAdapter::SetMouseMoveCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc  func)
+ {
+    mouseMoveHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData));
+  }
+
+  // SDL ONLY
+  void GuiAdapter::SetMouseUpCallback(
+    std::string canvasId, void* userData, bool capture, OnMouseEventFunc  func)
+ {
+    mouseUpHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData));
+  }
+
+  // SDL ONLY
+  void GuiAdapter::SetWheelCallback(
+   std::string canvasId, void* userData, bool capture, OnMouseWheelFunc  func)
+  {
+    mouseWheelHandlers_.push_back(EventHandlerData<OnMouseWheelFunc>(canvasId, func, userData));
+  }
+
+  // SDL ONLY
+  void GuiAdapter::SetKeyDownCallback(
+   std::string canvasId, void* userData, bool capture, OnKeyDownFunc   func)
+ {
+ }
+
+  // SDL ONLY
+  void GuiAdapter::SetKeyUpCallback(
+   std::string canvasId, void* userData, bool capture, OnKeyUpFunc    func)
+ {
+ }
+
+
+  // SDL ONLY
+  void GuiAdapter::OnAnimationFrame()
+  {
+    for (size_t i = 0; i < animationFrameHandlers_.size(); i++)
+    {
+      // TODO: fix time 
+      (*(animationFrameHandlers_[i].first))(0, animationFrameHandlers_[i].second);
+    }
+  }
+
+  // SDL ONLY
+  void GuiAdapter::OnResize()
+  {
+    for (size_t i = 0; i < resizeHandlers_.size(); i++)
+    {
+      (*(resizeHandlers_[i].func))(
+        resizeHandlers_[i].canvasName, 0, resizeHandlers_[i].userData);
+    }
+  }
+
+  // SDL ONLY
+  void GuiAdapter::OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event)
+  {
+
+    // the SDL window name IS the canvas name ("canvas" is used because this lib
+    // is designed for Wasm
+    SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID);
+    ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!");
+
+    const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow);
+    ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!");
+
+    std::string windowTitle(windowTitleSz);
+    ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!");
+
+    switch (event.mouse.type)
+    {
+    case GUIADAPTER_EVENT_WHEEL:
+      for (size_t i = 0; i < mouseWheelHandlers_.size(); i++)
+      {
+        if(mouseWheelHandlers_[i].canvasName == windowTitle)
+          (*(mouseWheelHandlers_[i].func))(windowTitle, &event, mouseWheelHandlers_[i].userData);
+      }
+      break;
+    default:
+      ORTHANC_ASSERT(false, "Wrong event.type: " << event.mouse.type << " in GuiAdapter::OnMouseWheelEvent(...)");
+      break;
+    }
+  }
+
+  // SDL ONLY
+  void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event)
+  {
+    // the SDL window name IS the canvas name ("canvas" is used because this lib
+    // is designed for Wasm
+    SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID);
+    ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!");
+     
+    const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow);
+    ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!");
+
+    std::string windowTitle(windowTitleSz);
+    ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!");
+
+    switch (event.type)
+    {
+    case GUIADAPTER_EVENT_MOUSEDOWN:
+      for (size_t i = 0; i < mouseDownHandlers_.size(); i++)
+      {
+        if (mouseDownHandlers_[i].canvasName == windowTitle)
+          (*(mouseDownHandlers_[i].func))(windowTitle, &event, mouseDownHandlers_[i].userData);
+      }
+      break;
+    case GUIADAPTER_EVENT_MOUSEMOVE:
+      for (size_t i = 0; i < mouseMoveHandlers_.size(); i++)
+      {
+        if (mouseMoveHandlers_[i].canvasName == windowTitle)
+          (*(mouseMoveHandlers_[i].func))(windowTitle, &event, mouseMoveHandlers_[i].userData);
+      }
+      break;
+    case GUIADAPTER_EVENT_MOUSEUP:
+      for (size_t i = 0; i < mouseUpHandlers_.size(); i++)
+      {
+        if (mouseUpHandlers_[i].canvasName == windowTitle)
+          (*(mouseUpHandlers_[i].func))(windowTitle, &event, mouseUpHandlers_[i].userData);
+      }
+      break;
+    default:
+      ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnMouseEvent(...)");
+      break;
+    }
+
+    ////boost::shared_ptr<IGuiAdapterWidget> GetWidgetFromWindowId();
+    //boost::shared_ptr<IGuiAdapterWidget> foundWidget;
+    //VisitWidgets([foundWidget, windowID](auto widget)
+    //  {
+    //    if (widget->GetSdlWindowID() == windowID)
+    //      foundWidget = widget;
+    //  });
+    //ORTHANC_ASSERT(foundWidget, "WindowID " << windowID << " was not found in the registered widgets!");
+    //if(foundWidget)
+    //  foundWidget->
+  }
+
+  // SDL ONLY
+  void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData)
+  {
+    animationFrameHandlers_.push_back(std::make_pair(func, userData));
+  }
+
+# if ORTHANC_ENABLE_OPENGL == 1 && !defined(__APPLE__)   /* OpenGL debug is not available on OS X */
+
+  // SDL ONLY
+  static void GLAPIENTRY
+    OpenGLMessageCallback(GLenum source,
+      GLenum type,
+      GLuint id,
+      GLenum severity,
+      GLsizei length,
+      const GLchar* message,
+      const void* userParam)
+  {
+    if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
+    {
+      fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+        (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
+        type, severity, message);
+    }
+}
+# endif
+
+  // SDL ONLY
+  void GuiAdapter::Run()
+  {
+# if ORTHANC_ENABLE_OPENGL == 1 && !defined(__APPLE__)
+    glEnable(GL_DEBUG_OUTPUT);
+    glDebugMessageCallback(OpenGLMessageCallback, 0);
+# endif
+
+    // Uint32 SDL_GetWindowID(SDL_Window* window)
+    // SDL_Window* SDL_GetWindowFromID(Uint32 id)   // may return NULL
+
+    bool stop = false;
+    while (!stop)
+    {
+      {
+        LockingEmitter::WriterLock lock(lockingEmitter_);
+        OnAnimationFrame(); // in SDL we must call it
+      }
+
+      SDL_Event event;
+
+      while (!stop && SDL_PollEvent(&event))
+      {
+        LockingEmitter::WriterLock lock(lockingEmitter_);
+
+        if (event.type == SDL_QUIT)
+        {
+          // TODO: call exit callbacks here
+          stop = true;
+          break;
+        }
+        else if ( (event.type == SDL_MOUSEMOTION) ||
+                  (event.type == SDL_MOUSEBUTTONDOWN) ||
+                  (event.type == SDL_MOUSEBUTTONUP) )
+        {
+          int scancodeCount = 0;
+          const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+          bool ctrlPressed(false);
+          bool shiftPressed(false);
+          bool altPressed(false);
+            
+          if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL])
+            ctrlPressed = true;
+          if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL])
+            ctrlPressed = true;
+          if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT])
+            shiftPressed = true;
+          if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT])
+            shiftPressed = true;
+          if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT])
+            altPressed = true;
+          
+          GuiAdapterMouseEvent dest;
+          ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event);
+          OnMouseEvent(event.window.windowID, dest);
+#if 0
+          // for reference, how to create trackers
+          if (tracker)
+          {
+            PointerEvent e;
+            e.AddPosition(compositor.GetPixelCenterCoordinates(
+              event.button.x, event.button.y));
+            tracker->PointerMove(e);
+          }
+#endif
+        }
+        else if (event.type == SDL_MOUSEWHEEL)
+        {
+
+          int scancodeCount = 0;
+          const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+          bool ctrlPressed(false);
+          bool shiftPressed(false);
+          bool altPressed(false);
+
+          if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL])
+            ctrlPressed = true;
+          if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL])
+            ctrlPressed = true;
+          if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT])
+            shiftPressed = true;
+          if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT])
+            shiftPressed = true;
+          if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT])
+            altPressed = true;
+
+          GuiAdapterWheelEvent dest;
+          ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event);
+          OnMouseWheelEvent(event.window.windowID, dest);
+
+          //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
+
+          //int x, y;
+          //SDL_GetMouseState(&x, &y);
+
+          //if (event.wheel.y > 0)
+          //{
+          //  locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
+          //}
+          //else if (event.wheel.y < 0)
+          //{
+          //  locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
+          //}
+        }
+        else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+        {
+#if 0
+          tracker.reset();
+#endif
+          OnResize();
+        }
+        else if (event.type == SDL_KEYDOWN && event.key.repeat == 0 /* Ignore key bounce */)
+        {
+          switch (event.key.keysym.sym)
+          {
+          case SDLK_f:
+            // window.GetWindow().ToggleMaximize(); //TODO: move to particular handler
+            break;
+
+          case SDLK_s:
+#if 0
+            // TODO: re-enable at application-level!!!!
+            VisitWidgets(
+              [](auto value)
+              {
+                auto widget = boost::dynamic_pointer_cast<VolumeSlicerWidget()
+                value->FitContent();
+              });
+#endif
+            break;
+
+          case SDLK_q:
+            stop = true;
+            break;
+
+          default:
+            break;
+          }
+        }
+        //        HandleApplicationEvent(controller, compositor, event, tracker);
+      }
+
+      SDL_Delay(1);
+    }
+  }
+#endif
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Generic/GuiAdapter.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,363 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+#pragma once
+
+#include <string>
+
+#if ORTHANC_ENABLE_WASM != 1
+# ifdef __EMSCRIPTEN__
+#   error __EMSCRIPTEN__ is defined and ORTHANC_ENABLE_WASM != 1
+# endif
+#endif
+
+#if ORTHANC_ENABLE_WASM == 1
+# ifndef __EMSCRIPTEN__
+#   error __EMSCRIPTEN__ is not defined and ORTHANC_ENABLE_WASM == 1
+# endif
+#endif
+
+#if ORTHANC_ENABLE_WASM == 1
+# include <emscripten/html5.h>
+#else
+# if ORTHANC_ENABLE_SDL == 1
+#   include <SDL.h>
+# endif
+#endif
+
+#include "../../Framework/StoneException.h"
+
+#if ORTHANC_ENABLE_THREADS != 1
+# include "../../Framework/Messages/LockingEmitter.h"
+#endif
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+namespace OrthancStone
+{
+
+  /**
+  This interface is used to store the widgets that are controlled by the 
+  GuiAdapter and receive event callbacks.
+  The callbacks may possibly be downcast (using dynamic_cast, for safety) \
+  to the actual widget type
+  */
+  class IGuiAdapterWidget
+  {
+  public:
+    virtual ~IGuiAdapterWidget() {}
+
+  };
+
+  enum GuiAdapterMouseEventType
+  {
+    GUIADAPTER_EVENT_MOUSEDOWN = 1973,
+    GUIADAPTER_EVENT_MOUSEMOVE = 1974,
+    GUIADAPTER_EVENT_MOUSEUP   = 1975,
+    GUIADAPTER_EVENT_WHEEL     = 1976
+  };
+
+  const unsigned int GUIADAPTER_DELTA_PIXEL = 2973;
+  const unsigned int GUIADAPTER_DELTA_LINE  = 2974;
+  const unsigned int GUIADAPTER_DELTA_PAGE  = 2975;
+
+  struct GuiAdapterUiEvent;
+  struct GuiAdapterMouseEvent;
+  struct GuiAdapterWheelEvent;
+  struct GuiAdapterKeyboardEvent;
+
+  class LockingEmitter;
+    
+#if 1
+  typedef bool (*OnMouseEventFunc)(std::string canvasId, const GuiAdapterMouseEvent* mouseEvent, void* userData);
+  typedef bool (*OnMouseWheelFunc)(std::string canvasId, const GuiAdapterWheelEvent* wheelEvent, void* userData);
+  typedef bool (*OnKeyDownFunc)   (std::string canvasId, const GuiAdapterKeyboardEvent*   keyEvent,   void* userData);
+  typedef bool (*OnKeyUpFunc)     (std::string canvasId, const GuiAdapterKeyboardEvent*   keyEvent,   void* userData);
+
+  typedef bool (*OnAnimationFrameFunc)(double time, void* userData);
+  typedef bool (*OnWindowResizeFunc)(std::string canvasId, const GuiAdapterUiEvent* uiEvent, void* userData);
+
+#else
+
+#if ORTHANC_ENABLE_WASM == 1
+  typedef EM_BOOL (*OnMouseEventFunc)(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+  typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData);
+  typedef EM_BOOL (*OnKeyDownFunc)   (int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData);
+  typedef EM_BOOL (*OnKeyUpFunc)     (int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData);
+
+  typedef EM_BOOL (*OnAnimationFrameFunc)(double time, void* userData);
+  typedef EM_BOOL (*OnWindowResizeFunc)(int eventType, const EmscriptenUiEvent* uiEvent, void* userData);
+#else
+  typedef bool    (*OnMouseEventFunc)(int eventType, const SDL_Event* mouseEvent, void* userData);
+  typedef bool    (*OnMouseWheelFunc)(int eventType, const SDL_Event* wheelEvent, void* userData);
+  typedef bool    (*OnKeyDownFunc)   (int eventType, const SDL_Event* keyEvent,   void* userData);
+  typedef bool    (*OnKeyUpFunc)     (int eventType, const SDL_Event* keyEvent,   void* userData);
+
+  typedef bool    (*OnAnimationFrameFunc)(double time, void* userData);
+  typedef bool    (*OnWindowResizeFunc)(int eventType, const GuiAdapterUiEvent* uiEvent, void* userData);
+#endif
+
+#endif
+  struct GuiAdapterMouseEvent {
+    GuiAdapterMouseEventType type;
+    //double                   timestamp;
+    //long                     screenX;
+    //long                     screenY;
+    //long                     clientX;
+    //long                     clientY;
+    bool                     ctrlKey;
+    bool                     shiftKey;
+    bool                     altKey;
+    //bool                     metaKey;
+    unsigned short           button;
+    //unsigned short           buttons;
+    //long                     movementX;
+    //long                     movementY;
+    long                     targetX;
+    long                     targetY;
+    // canvasX and canvasY are deprecated - there no longer exists a Module['canvas'] object, so canvasX/Y are no longer reported (register a listener on canvas directly to get canvas coordinates, or translate manually)
+    //long                     canvasX;
+    //long                     canvasY;
+    //long                     padding;
+  };
+
+  struct GuiAdapterWheelEvent {
+    GuiAdapterMouseEvent mouse;
+    double deltaX;
+    double deltaY;
+    unsigned long deltaMode;
+  };
+
+  // we don't use any data now
+  struct GuiAdapterUiEvent {};
+
+  // EmscriptenKeyboardEvent
+  struct GuiAdapterKeyboardEvent
+  {
+    bool ctrlKey;
+    bool shiftKey;
+    bool altKey;
+  };
+
+  std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event);
+
+  /*
+    Mousedown event trigger when either the left or right (or middle) mouse is pressed 
+    on the object;
+
+    Mouseup event trigger when either the left or right (or middle) mouse is released
+    above the object after triggered mousedown event and held.
+
+    Click event trigger when the only left mouse button is pressed and released on the
+    same object, requires the Mousedown and Mouseup event happened before Click event.
+
+    The normal expect trigger order: onMousedown >> onMouseup >> onClick
+
+    Testing in Chrome v58, the time between onMouseup and onClick events are around 
+    7ms to 15ms
+
+    FROM: https://codingrepo.com/javascript/2017/05/19/javascript-difference-mousedown-mouseup-click-events/
+  */
+#if ORTHANC_ENABLE_WASM == 1
+  void ConvertFromPlatform(
+    GuiAdapterUiEvent&       dest,
+    int                      eventType,
+    const EmscriptenUiEvent& src);
+
+  void ConvertFromPlatform(
+    GuiAdapterMouseEvent&       dest,
+    int                         eventType,
+    const EmscriptenMouseEvent& src);
+  
+  void ConvertFromPlatform(
+    GuiAdapterWheelEvent&       dest,
+    int                         eventType,
+    const EmscriptenWheelEvent& src);
+
+  void ConvertFromPlatform(
+    GuiAdapterKeyboardEvent&       dest,
+    const EmscriptenKeyboardEvent& src);
+
+#else
+
+# if ORTHANC_ENABLE_SDL == 1
+  void ConvertFromPlatform(
+    GuiAdapterMouseEvent& dest,
+    bool ctrlPressed, bool shiftPressed, bool altPressed,
+    const SDL_Event& source);
+
+  void ConvertFromPlatform(
+    GuiAdapterWheelEvent& dest,
+    bool ctrlPressed, bool shiftPressed, bool altPressed,
+    const SDL_Event& source);
+
+# endif
+
+#endif
+
+  class GuiAdapter
+  {
+  public:
+#if ORTHANC_ENABLE_THREADS == 1
+    GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter)
+#else
+    GuiAdapter()
+#endif
+    {
+      static int instanceCount = 0;
+      ORTHANC_ASSERT(instanceCount == 0);
+      instanceCount = 1;
+    }
+
+    void RegisterWidget(boost::shared_ptr<IGuiAdapterWidget> widget);
+    
+    /**
+      emscripten_set_resize_callback("#window", NULL, false, OnWindowResize);
+
+      emscripten_set_wheel_callback("mycanvas1", widget1_.get(), false, OnXXXMouseWheel);
+      emscripten_set_wheel_callback("mycanvas2", widget2_.get(), false, OnXXXMouseWheel);
+      emscripten_set_wheel_callback("mycanvas3", widget3_.get(), false, OnXXXMouseWheel);
+
+      emscripten_set_keydown_callback("#window", NULL, false, OnKeyDown);
+      emscripten_set_keyup_callback("#window", NULL, false, OnKeyUp);
+
+      emscripten_request_animation_frame_loop(OnAnimationFrame, NULL);
+    
+      SDL:
+      see https://wiki.libsdl.org/SDL_CaptureMouse
+
+    */
+
+    void SetMouseDownCallback(std::string canvasId, void* userData, bool capture, OnMouseEventFunc   func);
+    void SetMouseMoveCallback(std::string canvasId, void* userData, bool capture, OnMouseEventFunc   func);
+    void SetMouseUpCallback  (std::string canvasId, void* userData, bool capture, OnMouseEventFunc   func);
+    void SetWheelCallback    (std::string canvasId, void* userData, bool capture, OnMouseWheelFunc   func);
+    void SetKeyDownCallback  (std::string canvasId, void* userData, bool capture, OnKeyDownFunc      func);
+    void SetKeyUpCallback    (std::string canvasId, void* userData, bool capture, OnKeyUpFunc        func);
+    
+    // if you pass "#window", under SDL, then any Window resize will trigger the callback
+    void SetResizeCallback (std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func);
+
+    void RequestAnimationFrame(OnAnimationFrameFunc func, void* userData);
+
+    // TODO: implement and call to remove canvases [in SDL, although code should be generic]
+    void SetOnExitCallback();
+
+    // void 
+    // OnWindowResize
+    // oracle
+    // broker
+
+    /**
+    Under SDL, this function does NOT return until all windows have been closed.
+    Under wasm, it returns without doing anything, since the event loop is managed
+    by the browser.
+    */
+    void Run();
+
+#if ORTHANC_ENABLE_WASM != 1
+    /**
+    This method must be called in order for callback handler to be allowed to 
+    be registered.
+
+    We'll retrieve its name and use it as the canvas name in all subsequent 
+    calls
+    */
+    //void RegisterSdlWindow(SDL_Window* window);
+    //void UnregisterSdlWindow(SDL_Window* window);
+#endif
+
+  private:
+
+#if ORTHANC_ENABLE_THREADS == 1
+    /**
+    This object is used by the multithreaded Oracle to serialize access to
+    shared data. We need to use it as soon as we access the state.
+    */
+    LockingEmitter& lockingEmitter_;
+#endif
+
+    /**
+    In SDL, this executes all the registered headers
+    */
+    void OnAnimationFrame();
+
+    //void RequestAnimationFrame(OnAnimationFrameFunc func, void* userData);
+    std::vector<std::pair<OnAnimationFrameFunc, void*> >  
+      animationFrameHandlers_;
+    
+    void OnResize();
+
+#if ORTHANC_ENABLE_SDL == 1
+    template<typename Func>
+    struct EventHandlerData
+    {
+      EventHandlerData(std::string canvasName, Func func, void* userData) 
+        : canvasName(canvasName)
+        , func(func)
+        , userData(userData)
+      {
+      }
+
+      std::string canvasName;
+      Func        func;
+      void*       userData;
+    };
+    std::vector<EventHandlerData<OnWindowResizeFunc> > resizeHandlers_;
+    std::vector<EventHandlerData<OnMouseEventFunc  > > mouseDownHandlers_;
+    std::vector<EventHandlerData<OnMouseEventFunc  > > mouseMoveHandlers_;
+    std::vector<EventHandlerData<OnMouseEventFunc  > > mouseUpHandlers_;
+    std::vector<EventHandlerData<OnMouseWheelFunc  > > mouseWheelHandlers_;
+    
+
+    /**
+    This executes all the registered headers if needed (in wasm, the browser
+    deals with this)
+    */
+    void OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event);
+    
+    /**
+    Same remark as OnMouseEvent
+    */
+    void OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event);
+
+    boost::shared_ptr<IGuiAdapterWidget> GetWidgetFromWindowId();
+
+#endif
+
+    /**
+    This executes all the registered headers if needed (in wasm, the browser
+    deals with this)
+    */
+    void ViewportsUpdateSize();
+
+    std::vector<boost::weak_ptr<IGuiAdapterWidget> > widgets_;
+
+    template<typename F> void VisitWidgets(F func)
+    {
+      for (size_t i = 0; i < widgets_.size(); i++)
+      {
+        boost::shared_ptr<IGuiAdapterWidget> widget = widgets_[i].lock();
+        func(widget);
+      }
+    }
+  };
+}
--- a/Applications/Generic/NativeStoneApplicationContext.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Generic/NativeStoneApplicationContext.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,9 +21,9 @@
 
 #pragma once
 
-#include "../../Framework/Viewport/WidgetViewport.h"
-#include "../../Framework/Volumes/ISlicedVolume.h"
-#include "../../Framework/Volumes/IVolumeLoader.h"
+#include "../../Framework/Deprecated/Viewport/WidgetViewport.h"
+#include "../../Framework/Deprecated/Volumes/ISlicedVolume.h"
+#include "../../Framework/Deprecated/Volumes/IVolumeLoader.h"
 
 #include <list>
 #include <boost/thread.hpp>
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Generic/NativeStoneApplicationRunner.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,13 +19,13 @@
  **/
 
 
-#if ORTHANC_ENABLE_NATIVE != 1
-#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1
+#if ORTHANC_ENABLE_THREADS != 1
+#error this file shall be included only with the ORTHANC_ENABLE_THREADS set to 1
 #endif
 
 #include "NativeStoneApplicationRunner.h"
 
-#include "../../Framework/Toolbox/MessagingToolbox.h"
+#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h"
 #include "../../Platforms/Generic/OracleWebService.h"
 #include "../../Platforms/Generic/OracleDelayedCallExecutor.h"
 #include "NativeStoneApplicationContext.h"
@@ -180,7 +180,7 @@
 
       {
         OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters);
-        if (!MessagingToolbox::CheckOrthancVersion(orthanc))
+        if (!Deprecated::MessagingToolbox::CheckOrthancVersion(orthanc))
         {
           LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of "
             << "Orthanc, please upgrade";
--- a/Applications/Generic/NativeStoneApplicationRunner.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Generic/NativeStoneApplicationRunner.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,8 +23,8 @@
 
 #include "../IStoneApplication.h"
 
-#if ORTHANC_ENABLE_NATIVE != 1
-#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1
+#if ORTHANC_ENABLE_THREADS != 1
+#error this file shall be included only with the ORTHANC_ENABLE_THREADS set to 1
 #endif
 
 namespace OrthancStone
--- a/Applications/IStoneApplication.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/IStoneApplication.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,9 +22,10 @@
 #pragma once
 
 #include "StoneApplicationContext.h"
+#include "../Framework/Deprecated/Viewport/WidgetViewport.h"
+
 #include <boost/program_options.hpp>
-#include "../Framework/Viewport/WidgetViewport.h"
-#include "json/json.h"
+#include <json/json.h>
 
 namespace OrthancStone
 {
--- a/Applications/Qt/QCairoWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Qt/QCairoWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,8 +21,8 @@
 #pragma once
 
 #include "../../Applications/Generic/NativeStoneApplicationContext.h"
-#include "../../Framework/Viewport/CairoSurface.h"
-#include "../../Framework/Widgets/IWidget.h"
+#include "../../Framework/Wrappers/CairoSurface.h"
+#include "../../Framework/Deprecated/Widgets/IWidget.h"
 
 #include <QWidget>
 #include <memory>
--- a/Applications/Qt/QtStoneApplicationRunner.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Qt/QtStoneApplicationRunner.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -27,7 +27,7 @@
 #include <boost/program_options.hpp>
 #include <QApplication>
 
-#include "../../Framework/Toolbox/MessagingToolbox.h"
+#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h"
 
 #include <Core/Logging.h>
 #include <Core/HttpClient.h>
--- a/Applications/Samples/CMakeLists.txt	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/CMakeLists.txt	Mon Jun 24 14:35:00 2019 +0200
@@ -8,6 +8,9 @@
 
 include(../../Resources/CMake/OrthancStoneParameters.cmake)
 
+set(ENABLE_STONE_DEPRECATED ON)  # Need deprecated classes for these samples
+
+
 if (OPENSSL_NO_CAPIENG)
 add_definitions(-DOPENSSL_NO_CAPIENG=1)
 endif()
@@ -79,6 +82,7 @@
 
 endif()
 
+
 #####################################################################
 ## Configuration for Orthanc
 #####################################################################
@@ -97,6 +101,10 @@
 set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
 set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
 
+add_definitions(
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
+
 
 #####################################################################
 ## Build a static library containing the Orthanc Stone framework
@@ -208,7 +216,6 @@
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.h
-      ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.h
       ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp
--- a/Applications/Samples/SampleApplicationBase.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SampleApplicationBase.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../../Applications/IStoneApplication.h"
-#include "../../Framework/Widgets/WorldSceneWidget.h"
+#include "../../Framework/Deprecated/Widgets/WorldSceneWidget.h"
 
 #if ORTHANC_ENABLE_WASM==1
 #include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h"
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -20,7 +20,7 @@
 
 #pragma once
 
-#include "Framework/Widgets/IWorldSceneInteractor.h"
+#include "../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h"
 
 using namespace OrthancStone;
 
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -61,7 +61,7 @@
 
       // sources
       smartLoader_.reset(new Deprecated::SmartLoader(IObserver::GetBroker(), context->GetOrthancApiClient()));
-      smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
+      smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam);
 
       mainLayout_->SetTransmitMouseOver(true);
       mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Mon Jun 24 14:35:00 2019 +0200
@@ -29,12 +29,12 @@
 
 #include "Applications/IStoneApplication.h"
 
-#include "Framework/Layers/CircleMeasureTracker.h"
-#include "Framework/Layers/LineMeasureTracker.h"
-#include "Framework/Widgets/SliceViewerWidget.h"
-#include "Framework/Widgets/LayoutWidget.h"
-#include "Framework/Messages/IObserver.h"
-#include "Framework/SmartLoader.h"
+#include "../../../Framework/Deprecated/Layers/CircleMeasureTracker.h"
+#include "../../../Framework/Deprecated/Layers/LineMeasureTracker.h"
+#include "../../../Framework/Deprecated/SmartLoader.h"
+#include "../../../Framework/Deprecated/Widgets/LayoutWidget.h"
+#include "../../../Framework/Deprecated/Widgets/SliceViewerWidget.h"
+#include "../../../Framework/Messages/IObserver.h"
 
 #if ORTHANC_ENABLE_WASM==1
 #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h"
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "Framework/Widgets/IWorldSceneInteractor.h"
+#include "../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h"
 
 using namespace OrthancStone;
 
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,12 +23,12 @@
 
 #include "SampleApplicationBase.h"
 
-#include "../../Framework/Layers/CircleMeasureTracker.h"
-#include "../../Framework/Layers/LineMeasureTracker.h"
-#include "../../Framework/Widgets/SliceViewerWidget.h"
-#include "../../Framework/Widgets/LayoutWidget.h"
+#include "../../Framework/Deprecated/Layers/CircleMeasureTracker.h"
+#include "../../Framework/Deprecated/Layers/LineMeasureTracker.h"
+#include "../../Framework/Deprecated/SmartLoader.h"
+#include "../../Framework/Deprecated/Widgets/LayoutWidget.h"
+#include "../../Framework/Deprecated/Widgets/SliceViewerWidget.h"
 #include "../../Framework/Messages/IObserver.h"
-#include "../../Framework/SmartLoader.h"
 
 #if ORTHANC_ENABLE_WASM==1
 #include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h"
@@ -312,7 +312,7 @@
 
           // sources
           smartLoader_.reset(new Deprecated::SmartLoader(GetBroker(), context->GetOrthancApiClient()));
-          smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
+          smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam);
 
           mainLayout_->SetTransmitMouseOver(true);
           mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
--- a/Applications/Samples/SingleFrameApplication.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/SingleFrameApplication.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,8 +23,8 @@
 
 #include "SampleApplicationBase.h"
 
-#include "../../Framework/Layers/DicomSeriesVolumeSlicer.h"
-#include "../../Framework/Widgets/SliceViewerWidget.h"
+#include "../../Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h"
+#include "../../Framework/Deprecated/Widgets/SliceViewerWidget.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
--- a/Applications/Samples/rt-viewer-demo/CMakeLists.txt	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Samples/rt-viewer-demo/CMakeLists.txt	Mon Jun 24 14:35:00 2019 +0200
@@ -81,6 +81,7 @@
 
 endif()
 
+
 #####################################################################
 ## Configuration for Orthanc
 #####################################################################
@@ -97,6 +98,10 @@
 set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
 set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
 
+add_definitions(
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
+
 
 #####################################################################
 ## Build a static library containing the Orthanc Stone framework
--- a/Applications/Sdl/SdlCairoSurface.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Sdl/SdlCairoSurface.h	Mon Jun 24 14:35:00 2019 +0200
@@ -24,8 +24,8 @@
 #if ORTHANC_ENABLE_SDL == 1
 
 #include "SdlWindow.h"
-#include "../../Framework/Viewport/CairoSurface.h"
-#include "../../Framework/Viewport/IViewport.h"
+#include "../../Framework/Wrappers/CairoSurface.h"
+#include "../../Framework/Deprecated/Viewport/IViewport.h"
 
 #include <boost/thread/mutex.hpp>
 
--- a/Applications/Sdl/SdlStoneApplicationRunner.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -25,7 +25,6 @@
 
 #include "SdlStoneApplicationRunner.h"
 
-#include "../../Framework/Toolbox/MessagingToolbox.h"
 #include "../../Platforms/Generic/OracleWebService.h"
 #include "SdlEngine.h"
 
--- a/Applications/StoneApplicationContext.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Applications/StoneApplicationContext.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,10 +21,10 @@
 
 #pragma once
 
-#include "../Framework/Toolbox/IWebService.h"
-#include "../Framework/Toolbox/IDelayedCallExecutor.h"
-#include "../Framework/Toolbox/OrthancApiClient.h"
-#include "../Framework/Viewport/WidgetViewport.h"
+#include "../Framework/Deprecated/Toolbox/IWebService.h"
+#include "../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h"
+#include "../Framework/Deprecated/Toolbox/OrthancApiClient.h"
+#include "../Framework/Deprecated/Viewport/WidgetViewport.h"
 
 
 #ifdef _MSC_VER
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/CircleMeasureTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,106 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CircleMeasureTracker.h"
+
+#include <stdio.h>
+#include <boost/math/constants/constants.hpp>
+
+namespace Deprecated
+{
+  CircleMeasureTracker::CircleMeasureTracker(IStatusBar* statusBar,
+                                             const OrthancStone::CoordinateSystem3D& slice,
+                                             double x, 
+                                             double y,
+                                             uint8_t red,
+                                             uint8_t green,
+                                             uint8_t blue,
+                                             const Orthanc::Font& font) :
+    statusBar_(statusBar),
+    slice_(slice),
+    x1_(x),
+    y1_(y),
+    x2_(x),
+    y2_(y),
+    font_(font)
+  {
+    color_[0] = red;
+    color_[1] = green;
+    color_[2] = blue;
+  }
+    
+
+  void CircleMeasureTracker::Render(OrthancStone::CairoContext& context,
+                                    double zoom)
+  {
+    double x = (x1_ + x2_) / 2.0;
+    double y = (y1_ + y2_) / 2.0;
+
+    OrthancStone::Vector tmp;
+    OrthancStone::LinearAlgebra::AssignVector(tmp, x2_ - x1_, y2_ - y1_);
+    double r = boost::numeric::ublas::norm_2(tmp) / 2.0;
+
+    context.SetSourceColor(color_[0], color_[1], color_[2]);
+
+    cairo_t* cr = context.GetObject();
+    cairo_save(cr);
+    cairo_set_line_width(cr, 2.0 / zoom);
+    cairo_translate(cr, x, y);
+    cairo_arc(cr, 0, 0, r, 0, 2.0 * boost::math::constants::pi<double>());
+    cairo_stroke_preserve(cr);
+    cairo_stroke(cr);
+    cairo_restore(cr);
+
+    context.DrawText(font_, FormatRadius(), x, y, OrthancStone::BitmapAnchor_Center);
+  }
+    
+
+  double CircleMeasureTracker::GetRadius() const  // In millimeters
+  {
+    OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_);
+    OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_);
+    return boost::numeric::ublas::norm_2(b - a) / 2.0;
+  }
+
+
+  std::string CircleMeasureTracker::FormatRadius() const
+  {
+    char buf[64];
+    sprintf(buf, "%0.01f cm", GetRadius() / 10.0);
+    return buf;
+  }
+
+  void CircleMeasureTracker::MouseMove(int displayX,
+                                       int displayY,
+                                       double x,
+                                       double y,
+                                       const std::vector<Touch>& displayTouches,
+                                       const std::vector<Touch>& sceneTouches)
+  {
+    x2_ = x;
+    y2_ = y;
+
+    if (statusBar_ != NULL)
+    {
+      statusBar_->SetMessage("Circle radius: " + FormatRadius());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/CircleMeasureTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Viewport/IStatusBar.h"
+#include "../../Toolbox/CoordinateSystem3D.h"
+
+#include <Core/Images/Font.h>
+
+namespace Deprecated
+{
+  class CircleMeasureTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    IStatusBar*           statusBar_;
+    OrthancStone::CoordinateSystem3D    slice_;
+    double                x1_;
+    double                y1_;
+    double                x2_;
+    double                y2_;
+    uint8_t               color_[3];
+    const Orthanc::Font&  font_;
+
+  public:
+    CircleMeasureTracker(IStatusBar* statusBar,
+                         const OrthancStone::CoordinateSystem3D& slice,
+                         double x, 
+                         double y,
+                         uint8_t red,
+                         uint8_t green,
+                         uint8_t blue,
+                         const Orthanc::Font& font);
+    
+    virtual bool HasRender() const
+    {
+      return true;
+    }
+
+    virtual void Render(OrthancStone::CairoContext& context,
+                        double zoom);
+    
+    double GetRadius() const;  // In millimeters
+
+    std::string FormatRadius() const;
+
+    virtual void MouseUp()
+    {
+      // Possibly create a new landmark "volume" with the circle in subclasses
+    }
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/ColorFrameRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,62 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ColorFrameRenderer.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+
+namespace Deprecated
+{
+  OrthancStone::CairoSurface* ColorFrameRenderer::GenerateDisplay(const RenderStyle& style)
+  {
+    std::auto_ptr<OrthancStone::CairoSurface> display
+      (new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */));
+
+    Orthanc::ImageAccessor target;
+    display->GetWriteableAccessor(target);
+    
+    Orthanc::ImageProcessing::Convert(target, *frame_);
+
+    return display.release();
+  }
+
+
+  ColorFrameRenderer::ColorFrameRenderer(const Orthanc::ImageAccessor& frame,
+                                         const OrthancStone::CoordinateSystem3D& framePlane,
+                                         double pixelSpacingX,
+                                         double pixelSpacingY,
+                                         bool isFullQuality) :
+    FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality),
+    frame_(Orthanc::Image::Clone(frame))
+  {
+    if (frame_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (frame_->GetFormat() != Orthanc::PixelFormat_RGB24)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/ColorFrameRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,43 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "FrameRenderer.h"
+
+namespace Deprecated
+{
+  class ColorFrameRenderer : public FrameRenderer
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor>   frame_;  // In RGB24
+
+  protected:
+    virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style);
+
+  public:
+    ColorFrameRenderer(const Orthanc::ImageAccessor& frame,
+                       const OrthancStone::CoordinateSystem3D& framePlane,
+                       double pixelSpacingX,
+                       double pixelSpacingY,
+                       bool isFullQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,162 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomSeriesVolumeSlicer.h"
+
+#include "FrameRenderer.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace Deprecated
+{
+
+  void DicomSeriesVolumeSlicer::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message)
+  {
+    if (message.GetOrigin().GetSlicesCount() > 0)
+    {
+      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this));
+    }
+    else
+    {
+      BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this));
+    }
+  }
+
+  void DicomSeriesVolumeSlicer::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message)
+  {
+    BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this));
+  }
+
+
+  class DicomSeriesVolumeSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory
+  {
+  private:
+    const OrthancSlicesLoader::SliceImageReadyMessage&  message_;
+
+  public:
+    RendererFactory(const OrthancSlicesLoader::SliceImageReadyMessage& message) :
+      message_(message)
+    {
+    }
+
+    virtual ILayerRenderer* CreateRenderer() const
+    {
+      bool isFull = (message_.GetEffectiveQuality() == SliceImageQuality_FullPng ||
+                     message_.GetEffectiveQuality() == SliceImageQuality_FullPam);
+
+      return FrameRenderer::CreateRenderer(message_.GetImage(), message_.GetSlice(), isFull);
+    }
+  };
+
+  void DicomSeriesVolumeSlicer::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message)
+  {
+    // first notify that the pixel data of the frame is ready (targeted to, i.e: an image cache)
+    BroadcastMessage(FrameReadyMessage(*this, message.GetImage(), 
+                                  message.GetEffectiveQuality(), message.GetSlice()));
+
+    // then notify that the layer is ready for rendering
+    RendererFactory factory(message);
+    BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry()));
+  }
+
+  void DicomSeriesVolumeSlicer::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message)
+  {
+    BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry()));
+  }
+
+
+  DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker,
+                                                   OrthancApiClient& orthanc) :
+    IVolumeSlicer(broker),
+    IObserver(broker),
+    loader_(broker, orthanc),
+    quality_(SliceImageQuality_FullPng)
+  {
+    loader_.RegisterObserverCallback(
+      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage>
+        (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady));
+
+    loader_.RegisterObserverCallback(
+      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage>
+      (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError));
+
+    loader_.RegisterObserverCallback(
+      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage>
+        (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady));
+
+    loader_.RegisterObserverCallback(
+      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage>
+      (*this, &DicomSeriesVolumeSlicer::OnSliceImageError));
+  }
+
+  
+  void DicomSeriesVolumeSlicer::LoadSeries(const std::string& seriesId)
+  {
+    loader_.ScheduleLoadSeries(seriesId);
+  }
+
+
+  void DicomSeriesVolumeSlicer::LoadInstance(const std::string& instanceId)
+  {
+    loader_.ScheduleLoadInstance(instanceId);
+  }
+
+
+  void DicomSeriesVolumeSlicer::LoadFrame(const std::string& instanceId,
+                                          unsigned int frame)
+  {
+    loader_.ScheduleLoadFrame(instanceId, frame);
+  }
+
+
+  bool DicomSeriesVolumeSlicer::GetExtent(std::vector<OrthancStone::Vector>& points,
+                                          const OrthancStone::CoordinateSystem3D& viewportSlice)
+  {
+    size_t index;
+
+    if (loader_.IsGeometryReady() &&
+        loader_.LookupSlice(index, viewportSlice))
+    {
+      loader_.GetSlice(index).GetExtent(points);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  void DicomSeriesVolumeSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
+  {
+    size_t index;
+
+    if (loader_.IsGeometryReady() &&
+        loader_.LookupSlice(index, viewportSlice))
+    {
+      loader_.ScheduleLoadSliceImage(index, quality_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,127 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IVolumeSlicer.h"
+#include "../Toolbox/IWebService.h"
+#include "../Toolbox/OrthancSlicesLoader.h"
+#include "../Toolbox/OrthancApiClient.h"
+
+namespace Deprecated
+{  
+  // this class is in charge of loading a Frame.
+  // once it's been loaded (first the geometry and then the image),
+  // messages are sent to observers so they can use it
+  class DicomSeriesVolumeSlicer :
+    public IVolumeSlicer,
+    public OrthancStone::IObserver
+    //private OrthancSlicesLoader::ISliceLoaderObserver
+  {
+  public:
+    // TODO: Add "frame" and "instanceId"
+    class FrameReadyMessage : public OrthancStone::OriginMessage<DicomSeriesVolumeSlicer>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      const Orthanc::ImageAccessor&  frame_;
+      SliceImageQuality              imageQuality_;
+      const Slice&                   slice_;
+
+    public:
+      FrameReadyMessage(DicomSeriesVolumeSlicer& origin,
+                        const Orthanc::ImageAccessor& frame,
+                        SliceImageQuality imageQuality,
+                        const Slice& slice) :
+        OriginMessage(origin),
+        frame_(frame),
+        imageQuality_(imageQuality),
+        slice_(slice)
+      {
+      }
+
+      const Orthanc::ImageAccessor& GetFrame() const
+      {
+        return frame_;
+      }
+
+      SliceImageQuality GetImageQuality() const
+      {
+        return imageQuality_;
+      }
+
+      const Slice& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+
+    
+  private:
+    class RendererFactory;
+    
+    OrthancSlicesLoader  loader_;
+    SliceImageQuality    quality_;
+
+  public:
+    DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker,
+                            OrthancApiClient& orthanc);
+
+    void LoadSeries(const std::string& seriesId);
+
+    void LoadInstance(const std::string& instanceId);
+
+    void LoadFrame(const std::string& instanceId,
+                   unsigned int frame);
+
+    void SetImageQuality(SliceImageQuality quality)
+    {
+      quality_ = quality;
+    }
+
+    SliceImageQuality GetImageQuality() const
+    {
+      return quality_;
+    }
+
+    size_t GetSlicesCount() const
+    {
+      return loader_.GetSlicesCount();
+    }
+
+    const Slice& GetSlice(size_t slice) const 
+    {
+      return loader_.GetSlice(slice);
+    }
+
+    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
+                           const OrthancStone::CoordinateSystem3D& viewportSlice);
+
+    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice);
+
+protected:
+    void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message);
+    void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message);
+    void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message);
+    void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,170 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomStructureSetSlicer.h"
+
+namespace Deprecated
+{
+  class DicomStructureSetSlicer::Renderer : public ILayerRenderer
+  {
+  private:
+    class Structure
+    {
+    private:
+      bool                                                         visible_;
+      uint8_t                                                      red_;
+      uint8_t                                                      green_;
+      uint8_t                                                      blue_;
+      std::string                                                  name_;
+      std::vector< std::vector<OrthancStone::DicomStructureSet::PolygonPoint> >  polygons_;
+
+    public:
+      Structure(OrthancStone::DicomStructureSet& structureSet,
+                const OrthancStone::CoordinateSystem3D& plane,
+                size_t index) :
+        name_(structureSet.GetStructureName(index))
+      {
+        structureSet.GetStructureColor(red_, green_, blue_, index);
+        visible_ = structureSet.ProjectStructure(polygons_, index, plane);
+      }
+
+      void Render(OrthancStone::CairoContext& context)
+      {
+        if (visible_)
+        {
+          cairo_t* cr = context.GetObject();
+        
+          context.SetSourceColor(red_, green_, blue_);
+
+          for (size_t i = 0; i < polygons_.size(); i++)
+          {
+            cairo_move_to(cr, polygons_[i][0].first, polygons_[i][0].second);
+
+            for (size_t j = 1; j < polygons_[i].size(); j++)
+            {
+              cairo_line_to(cr, polygons_[i][j].first, polygons_[i][j].second);
+            }
+
+            cairo_line_to(cr, polygons_[i][0].first, polygons_[i][0].second);
+            cairo_stroke(cr);
+          }
+        }
+      }
+    };
+
+    typedef std::list<Structure*>  Structures;
+    
+    OrthancStone::CoordinateSystem3D  plane_;
+    Structures          structures_;
+    
+  public:
+    Renderer(OrthancStone::DicomStructureSet& structureSet,
+             const OrthancStone::CoordinateSystem3D& plane) :
+      plane_(plane)
+    {
+      for (size_t k = 0; k < structureSet.GetStructuresCount(); k++)
+      {
+        structures_.push_back(new Structure(structureSet, plane, k));
+      }
+    }
+
+    virtual ~Renderer()
+    {
+      for (Structures::iterator it = structures_.begin();
+           it != structures_.end(); ++it)
+      {
+        delete *it;
+      }
+    }
+
+    virtual bool RenderLayer(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view)
+    {
+      cairo_set_line_width(context.GetObject(), 2.0f / view.GetZoom());
+
+      for (Structures::const_iterator it = structures_.begin();
+           it != structures_.end(); ++it)
+      {
+        assert(*it != NULL);
+        (*it)->Render(context);
+      }
+
+      return true;
+    }
+
+    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane()
+    {
+      return plane_;
+    }
+
+    virtual void SetLayerStyle(const RenderStyle& style)
+    {
+    }
+    
+    virtual bool IsFullQuality()
+    {
+      return true;
+    }
+  };
+
+
+  class DicomStructureSetSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory
+  {
+  private:
+    OrthancStone::DicomStructureSet&         structureSet_;
+    const OrthancStone::CoordinateSystem3D&  plane_;
+
+  public:
+    RendererFactory(OrthancStone::DicomStructureSet& structureSet,
+                    const OrthancStone::CoordinateSystem3D&  plane) :
+      structureSet_(structureSet),
+      plane_(plane)
+    {
+    }
+
+    virtual ILayerRenderer* CreateRenderer() const
+    {
+      return new Renderer(structureSet_, plane_);
+    }
+  };
+  
+
+  DicomStructureSetSlicer::DicomStructureSetSlicer(OrthancStone::MessageBroker& broker,
+                                                   StructureSetLoader& loader) :
+    IVolumeSlicer(broker),
+    IObserver(broker),
+    loader_(loader)
+  {
+    loader_.RegisterObserverCallback(
+      new OrthancStone::Callable<DicomStructureSetSlicer, StructureSetLoader::ContentChangedMessage>
+      (*this, &DicomStructureSetSlicer::OnStructureSetLoaded));
+  }
+
+
+  void DicomStructureSetSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane)
+  {
+    if (loader_.HasStructureSet())
+    {
+      RendererFactory factory(loader_.GetStructureSet(), viewportPlane);
+      BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,56 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IVolumeSlicer.h"
+#include "../Volumes/StructureSetLoader.h"
+
+namespace Deprecated
+{
+  class DicomStructureSetSlicer :
+    public IVolumeSlicer,
+    public OrthancStone::IObserver
+  {
+  private:
+    class Renderer;
+    class RendererFactory;
+
+    StructureSetLoader& loader_;
+
+    void OnStructureSetLoaded(const IVolumeLoader::ContentChangedMessage& message)
+    {
+      BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this));
+    }
+
+  public:
+    DicomStructureSetSlicer(OrthancStone::MessageBroker& broker,
+                            StructureSetLoader& loader);
+
+    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
+                           const OrthancStone::CoordinateSystem3D& viewportPlane)
+    {
+      return false;
+    }
+
+    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/FrameRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,140 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "FrameRenderer.h"
+
+#include "GrayscaleFrameRenderer.h"
+#include "ColorFrameRenderer.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  FrameRenderer::FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane,
+                               double pixelSpacingX,
+                               double pixelSpacingY,
+                               bool isFullQuality) :
+    framePlane_(framePlane),
+    pixelSpacingX_(pixelSpacingX),
+    pixelSpacingY_(pixelSpacingY),
+    isFullQuality_(isFullQuality)
+  {
+  }
+
+
+  bool FrameRenderer::RenderLayer(OrthancStone::CairoContext& context,
+                                  const ViewportGeometry& view)
+  {    
+    if (!style_.visible_)
+    {
+      return true;
+    }
+
+    if (display_.get() == NULL)
+    {
+      display_.reset(GenerateDisplay(style_));
+    }
+
+    assert(display_.get() != NULL);
+
+    cairo_t *cr = context.GetObject();
+
+    cairo_save(cr);
+
+    cairo_matrix_t transform;
+    cairo_matrix_init_identity(&transform);
+    cairo_matrix_scale(&transform, pixelSpacingX_, pixelSpacingY_);
+    cairo_matrix_translate(&transform, -0.5, -0.5);
+    cairo_transform(cr, &transform);
+
+    //cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+    cairo_set_source_surface(cr, display_->GetObject(), 0, 0);
+
+    switch (style_.interpolation_)
+    {
+      case OrthancStone::ImageInterpolation_Nearest:
+        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
+        break;
+
+      case OrthancStone::ImageInterpolation_Bilinear:
+        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    cairo_paint_with_alpha(cr, style_.alpha_);
+
+    if (style_.drawGrid_)
+    {
+      context.SetSourceColor(style_.drawColor_);
+      cairo_set_line_width(cr, 0.5 / view.GetZoom());
+
+      for (unsigned int x = 0; x <= display_->GetWidth(); x++)
+      {
+        cairo_move_to(cr, x, 0);
+        cairo_line_to(cr, x, display_->GetHeight());
+      }
+
+      for (unsigned int y = 0; y <= display_->GetHeight(); y++)
+      {
+        cairo_move_to(cr, 0, y);
+        cairo_line_to(cr, display_->GetWidth(), y);
+      }
+
+      cairo_stroke(cr);
+    }
+
+    cairo_restore(cr);
+
+    return true;
+  }
+
+
+  void FrameRenderer::SetLayerStyle(const RenderStyle& style)
+  {
+    style_ = style;
+    display_.reset(NULL);
+  }
+
+
+  ILayerRenderer* FrameRenderer::CreateRenderer(const Orthanc::ImageAccessor& frame,
+                                                const Deprecated::Slice& framePlane,
+                                                bool isFullQuality)
+  {
+    if (frame.GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      return new ColorFrameRenderer(frame,
+                                    framePlane.GetGeometry(), 
+                                    framePlane.GetPixelSpacingX(),
+                                    framePlane.GetPixelSpacingY(), isFullQuality);
+    }
+    else
+    {
+      return new GrayscaleFrameRenderer(frame,
+                                        framePlane.GetConverter(),
+                                        framePlane.GetGeometry(), 
+                                        framePlane.GetPixelSpacingX(),
+                                        framePlane.GetPixelSpacingY(), isFullQuality);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/FrameRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerRenderer.h"
+
+#include "../Toolbox/Slice.h"
+
+namespace Deprecated
+{
+  class FrameRenderer : public ILayerRenderer
+  {
+  private:
+    OrthancStone::CoordinateSystem3D            framePlane_;
+    double                        pixelSpacingX_;
+    double                        pixelSpacingY_;
+    RenderStyle                   style_;
+    bool                          isFullQuality_;
+    std::auto_ptr<OrthancStone::CairoSurface>   display_;
+
+  protected:
+    virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style) = 0;
+
+  public:
+    FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane,
+                  double pixelSpacingX,
+                  double pixelSpacingY,
+                  bool isFullQuality);
+
+    virtual bool RenderLayer(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view);
+
+    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane()
+    {
+      return framePlane_;
+    }
+
+    virtual void SetLayerStyle(const RenderStyle& style);
+
+    virtual bool IsFullQuality() 
+    {
+      return isFullQuality_;
+    }
+
+    // TODO: Avoid cloning the "frame"
+    static ILayerRenderer* CreateRenderer(const Orthanc::ImageAccessor& frame,
+                                          const Deprecated::Slice& framePlane,
+                                          bool isFullQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/GrayscaleFrameRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,141 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GrayscaleFrameRenderer.h"
+
+#include <Core/Images/Image.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  OrthancStone::CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style)
+  {
+    assert(frame_->GetFormat() == Orthanc::PixelFormat_Float32);
+
+    std::auto_ptr<OrthancStone::CairoSurface> result;
+
+    float windowCenter, windowWidth;
+    style.ComputeWindowing(windowCenter, windowWidth,
+                           defaultWindowCenter_, defaultWindowWidth_);
+
+    float x0 = windowCenter - windowWidth / 2.0f;
+    float x1 = windowCenter + windowWidth / 2.0f;
+
+    //LOG(INFO) << "Window: " << x0 << " => " << x1;
+
+    result.reset(new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */));
+
+    const uint8_t* lut = NULL;
+    if (style.applyLut_)
+    {
+      if (Orthanc::EmbeddedResources::GetFileResourceSize(style.lut_) != 3 * 256)
+      {
+        // Invalid colormap
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      lut = reinterpret_cast<const uint8_t*>(Orthanc::EmbeddedResources::GetFileResourceBuffer(style.lut_));
+    }
+
+    Orthanc::ImageAccessor target;
+    result->GetWriteableAccessor(target);
+    
+    const unsigned int width = target.GetWidth();
+    const unsigned int height = target.GetHeight();
+    
+    for (unsigned int y = 0; y < height; y++)
+    {
+      const float* p = reinterpret_cast<const float*>(frame_->GetConstRow(y));
+      uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++, q += 4)
+      {
+        uint8_t v = 0;
+        if (windowWidth >= 0.001f)  // Avoid division by zero
+        {
+          if (*p >= x1)
+          {
+            v = 255;
+          }
+          else if (*p <= x0)
+          {
+            v = 0;
+          }
+          else
+          {
+            // https://en.wikipedia.org/wiki/Linear_interpolation
+            v = static_cast<uint8_t>(255.0f * (*p - x0) / (x1 - x0));
+          }
+
+          if (style.reverse_ ^ (photometric_ == Orthanc::PhotometricInterpretation_Monochrome1))
+          {
+            v = 255 - v;
+          }
+        }
+
+        if (style.applyLut_)
+        {
+          assert(lut != NULL);
+          q[3] = 255;
+          q[2] = lut[3 * v];
+          q[1] = lut[3 * v + 1];
+          q[0] = lut[3 * v + 2];
+        }
+        else
+        {
+          q[3] = 255;
+          q[2] = v;
+          q[1] = v;
+          q[0] = v;
+        }
+      }
+    }
+
+    return result.release();
+  }
+
+
+  GrayscaleFrameRenderer::GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame,
+                                                 const Deprecated::DicomFrameConverter& converter,
+                                                 const OrthancStone::CoordinateSystem3D& framePlane,
+                                                 double pixelSpacingX,
+                                                 double pixelSpacingY,
+                                                 bool isFullQuality) :
+    FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality),
+    frame_(Orthanc::Image::Clone(frame)),
+    defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())),
+    defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())),
+    photometric_(converter.GetPhotometricInterpretation())
+  {
+    if (frame_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    converter.ConvertFrameInplace(frame_);
+    assert(frame_.get() != NULL);
+
+    if (frame_->GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/GrayscaleFrameRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,48 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "FrameRenderer.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+namespace Deprecated
+{
+  class GrayscaleFrameRenderer : public FrameRenderer
+  {
+  private:
+    std::auto_ptr<Orthanc::ImageAccessor>   frame_;  // In Float32
+    float                                   defaultWindowCenter_;
+    float                                   defaultWindowWidth_;
+    Orthanc::PhotometricInterpretation      photometric_;
+
+  protected:
+    virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style);
+
+  public:
+    GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame,
+                           const Deprecated::DicomFrameConverter& converter,
+                           const OrthancStone::CoordinateSystem3D& framePlane,
+                           double pixelSpacingX,
+                           double pixelSpacingY,
+                           bool isFullQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/ILayerRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,47 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Wrappers/CairoContext.h"
+#include "../../Toolbox/CoordinateSystem3D.h"
+#include "../Toolbox/ViewportGeometry.h"
+#include "RenderStyle.h"
+
+namespace Deprecated
+{
+  class ILayerRenderer : public boost::noncopyable
+  {
+  public:
+    virtual ~ILayerRenderer()
+    {
+    }
+    
+    virtual bool RenderLayer(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view) = 0;
+
+    virtual void SetLayerStyle(const RenderStyle& style) = 0;
+
+    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() = 0;
+    
+    virtual bool IsFullQuality() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/IVolumeSlicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,139 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerRenderer.h"
+#include "../Toolbox/Slice.h"
+#include "../../Messages/IObservable.h"
+#include "../../Messages/IMessage.h"
+#include "Core/Images/Image.h"
+#include <boost/shared_ptr.hpp>
+
+namespace Deprecated
+{
+  class IVolumeSlicer : public OrthancStone::IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeSlicer);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeSlicer);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeSlicer);
+
+    class SliceContentChangedMessage : public OrthancStone::OriginMessage<IVolumeSlicer>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      const Deprecated::Slice& slice_;
+
+    public:
+      SliceContentChangedMessage(IVolumeSlicer& origin,
+                                 const Deprecated::Slice& slice) :
+        OriginMessage(origin),
+        slice_(slice)
+      {
+      }
+
+      const Deprecated::Slice& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+    
+
+    class LayerReadyMessage : public OrthancStone::OriginMessage<IVolumeSlicer>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    public:
+      class IRendererFactory : public boost::noncopyable
+      {
+      public:
+        virtual ~IRendererFactory()
+        {
+        }
+
+        virtual ILayerRenderer* CreateRenderer() const = 0;
+      };
+    
+    private:
+      const IRendererFactory&    factory_;
+      const OrthancStone::CoordinateSystem3D&  slice_;
+
+    public:
+      LayerReadyMessage(IVolumeSlicer& origin,
+                        const IRendererFactory& rendererFactory,
+                        const OrthancStone::CoordinateSystem3D& slice) :
+        OriginMessage(origin),
+        factory_(rendererFactory),
+        slice_(slice)
+      {
+      }
+
+      ILayerRenderer* CreateRenderer() const
+      {
+        return factory_.CreateRenderer();
+      }
+
+      const OrthancStone::CoordinateSystem3D& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+
+
+    class LayerErrorMessage : public OrthancStone::OriginMessage<IVolumeSlicer>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      const OrthancStone::CoordinateSystem3D&  slice_;
+
+    public:
+      LayerErrorMessage(IVolumeSlicer& origin,
+                        const OrthancStone::CoordinateSystem3D& slice) :
+        OriginMessage(origin),
+        slice_(slice)
+      {
+      }
+
+      const OrthancStone::CoordinateSystem3D& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+
+
+    IVolumeSlicer(OrthancStone::MessageBroker& broker) :
+      IObservable(broker)
+    {
+    }
+    
+    virtual ~IVolumeSlicer()
+    {
+    }
+
+    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
+                           const OrthancStone::CoordinateSystem3D& viewportSlice) = 0;
+
+    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/LineLayerRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,67 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LineLayerRenderer.h"
+
+namespace Deprecated
+{
+  LineLayerRenderer::LineLayerRenderer(double x1,
+                                       double y1,
+                                       double x2,
+                                       double y2,
+                                       const OrthancStone::CoordinateSystem3D& plane) : 
+    x1_(x1),
+    y1_(y1),
+    x2_(x2),
+    y2_(y2),
+    plane_(plane)
+  {
+    RenderStyle style;
+    SetLayerStyle(style);
+  }
+
+
+  bool LineLayerRenderer::RenderLayer(OrthancStone::CairoContext& context,
+                                      const ViewportGeometry& view)
+  {
+    if (visible_)
+    {
+      context.SetSourceColor(color_);
+
+      cairo_t *cr = context.GetObject();
+      cairo_set_line_width(cr, 1.0 / view.GetZoom());
+      cairo_move_to(cr, x1_, y1_);
+      cairo_line_to(cr, x2_, y2_);
+      cairo_stroke(cr);
+    }
+
+    return true;
+  }
+
+
+  void LineLayerRenderer::SetLayerStyle(const RenderStyle& style)
+  {
+    visible_ = style.visible_;
+    color_[0] = style.drawColor_[0];
+    color_[1] = style.drawColor_[1];
+    color_[2] = style.drawColor_[2];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/LineLayerRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,61 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerRenderer.h"
+
+namespace Deprecated
+{
+  class LineLayerRenderer : public ILayerRenderer
+  {
+  private:
+    double              x1_;
+    double              y1_;
+    double              x2_;
+    double              y2_;
+    OrthancStone::CoordinateSystem3D  plane_;
+    bool                visible_;
+    uint8_t             color_[3];
+
+  public:
+    LineLayerRenderer(double x1,
+                      double y1,
+                      double x2,
+                      double y2,
+                      const OrthancStone::CoordinateSystem3D& plane);
+
+    virtual bool RenderLayer(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view);
+
+    virtual void SetLayerStyle(const RenderStyle& style);
+
+    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane()
+    {
+      return plane_;
+    }
+    
+    virtual bool IsFullQuality()
+    {
+      return true;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/LineMeasureTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,102 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LineMeasureTracker.h"
+
+#include <stdio.h>
+
+namespace Deprecated
+{
+  LineMeasureTracker::LineMeasureTracker(IStatusBar* statusBar,
+                                         const OrthancStone::CoordinateSystem3D& slice,
+                                         double x, 
+                                         double y,
+                                         uint8_t red,
+                                         uint8_t green,
+                                         uint8_t blue,
+                                         const Orthanc::Font& font) :
+    statusBar_(statusBar),
+    slice_(slice),
+    x1_(x),
+    y1_(y),
+    x2_(x),
+    y2_(y),
+    font_(font)
+  {
+    color_[0] = red;
+    color_[1] = green;
+    color_[2] = blue;
+  }
+    
+
+  void LineMeasureTracker::Render(OrthancStone::CairoContext& context,
+                                  double zoom)
+  {
+    context.SetSourceColor(color_[0], color_[1], color_[2]);
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_line_width(cr, 2.0 / zoom);
+    cairo_move_to(cr, x1_, y1_);
+    cairo_line_to(cr, x2_, y2_);
+    cairo_stroke(cr);
+
+    if (y2_ - y1_ < 0)
+    {
+      context.DrawText(font_, FormatLength(), x2_, y2_ - 5, OrthancStone::BitmapAnchor_BottomCenter);
+    }
+    else
+    {
+      context.DrawText(font_, FormatLength(), x2_, y2_ + 5, OrthancStone::BitmapAnchor_TopCenter);
+    }
+  }
+    
+
+  double LineMeasureTracker::GetLength() const  // In millimeters
+  {
+    OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_);
+    OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_);
+    return boost::numeric::ublas::norm_2(b - a);
+  }
+
+
+  std::string LineMeasureTracker::FormatLength() const
+  {
+    char buf[64];
+    sprintf(buf, "%0.01f cm", GetLength() / 10.0);
+    return buf;
+  }
+
+  void LineMeasureTracker::MouseMove(int displayX,
+                                     int displayY,
+                                     double x,
+                                     double y,
+                                     const std::vector<Touch>& displayTouches,
+                                     const std::vector<Touch>& sceneTouches)
+  {
+    x2_ = x;
+    y2_ = y;
+
+    if (statusBar_ != NULL)
+    {
+      statusBar_->SetMessage("Line length: " + FormatLength());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/LineMeasureTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Widgets/IWorldSceneMouseTracker.h"
+
+#include "../Viewport/IStatusBar.h"
+#include "../../Toolbox/CoordinateSystem3D.h"
+
+namespace Deprecated
+{
+  class LineMeasureTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    IStatusBar*           statusBar_;
+    OrthancStone::CoordinateSystem3D    slice_;
+    double                x1_;
+    double                y1_;
+    double                x2_;
+    double                y2_;
+    uint8_t               color_[3];
+    unsigned int          fontSize_;
+    const Orthanc::Font&  font_;
+
+  public:
+    LineMeasureTracker(IStatusBar* statusBar,
+                       const OrthancStone::CoordinateSystem3D& slice,
+                       double x, 
+                       double y,
+                       uint8_t red,
+                       uint8_t green,
+                       uint8_t blue,
+                       const Orthanc::Font& font);
+
+    virtual bool HasRender() const
+    {
+      return true;
+    }
+
+    virtual void Render(OrthancStone::CairoContext& context,
+                        double zoom);
+    
+    double GetLength() const;  // In millimeters
+
+    std::string FormatLength() const;
+
+    virtual void MouseUp()
+    {
+      // Possibly create a new landmark "volume" with the line in subclasses
+    }
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/RenderStyle.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,106 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RenderStyle.h"
+
+#include "../../Volumes/ImageBuffer3D.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  RenderStyle::RenderStyle()
+  {
+    visible_ = true;
+    reverse_ = false;
+    windowing_ = OrthancStone::ImageWindowing_Custom;
+    alpha_ = 1;
+    applyLut_ = false;
+    lut_ = Orthanc::EmbeddedResources::COLORMAP_HOT;
+    drawGrid_ = false;
+    drawColor_[0] = 255;
+    drawColor_[1] = 255;
+    drawColor_[2] = 255;
+    customWindowCenter_ = 128;
+    customWindowWidth_ = 256;
+    interpolation_ = OrthancStone::ImageInterpolation_Nearest;
+    fontSize_ = 14;
+  }
+
+
+  void RenderStyle::ComputeWindowing(float& targetCenter,
+                                     float& targetWidth,
+                                     float defaultCenter,
+                                     float defaultWidth) const
+  {
+    if (windowing_ == OrthancStone::ImageWindowing_Custom)
+    {
+      targetCenter = customWindowCenter_;
+      targetWidth = customWindowWidth_;
+    }
+    else
+    {
+      return ::OrthancStone::ComputeWindowing
+        (targetCenter, targetWidth, windowing_, defaultCenter, defaultWidth);
+    }
+  }
+
+  
+  void RenderStyle::SetColor(uint8_t red,
+                             uint8_t green,
+                             uint8_t blue)
+  {
+    drawColor_[0] = red;
+    drawColor_[1] = green;
+    drawColor_[2] = blue;
+  }
+
+
+  bool RenderStyle::FitRange(const OrthancStone::ImageBuffer3D& image,
+                             const DicomFrameConverter& converter)
+  {
+    float minValue, maxValue;
+
+    windowing_ = OrthancStone::ImageWindowing_Custom;
+
+    if (image.GetRange(minValue, maxValue))
+    {  
+      // casting the narrower type to wider before calling the + operator
+      // will prevent overflowing (this is why the cast to double is only 
+      // done on the first operand)
+      customWindowCenter_ = static_cast<float>(
+        converter.Apply((static_cast<double>(minValue) + maxValue) / 2.0));
+      
+      customWindowWidth_ = static_cast<float>(
+        converter.Apply(static_cast<double>(maxValue) - minValue));
+      
+      if (customWindowWidth_ > 1)
+      {
+        return true;
+      }
+    }
+
+    customWindowCenter_ = 128.0;
+    customWindowWidth_ = 256.0;
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/RenderStyle.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,63 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../StoneEnumerations.h"
+#include "../../Volumes/ImageBuffer3D.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+#include <EmbeddedResources.h>
+
+#include <stdint.h>
+
+namespace Deprecated
+{
+  struct RenderStyle
+  {
+    bool visible_;
+    bool reverse_;
+    OrthancStone::ImageWindowing windowing_;
+    float alpha_;   // In [0,1]
+    bool applyLut_;
+    Orthanc::EmbeddedResources::FileResourceId  lut_;
+    bool drawGrid_;
+    uint8_t drawColor_[3];
+    float customWindowCenter_;
+    float customWindowWidth_;
+    OrthancStone::ImageInterpolation interpolation_;
+    unsigned int fontSize_;
+    
+    RenderStyle();
+
+    void ComputeWindowing(float& targetCenter,
+                          float& targetWidth,
+                          float defaultCenter,
+                          float defaultWidth) const;
+
+    void SetColor(uint8_t red,
+                  uint8_t green,
+                  uint8_t blue);
+
+    bool FitRange(const OrthancStone::ImageBuffer3D& image,
+                  const DicomFrameConverter& converter);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/SeriesFrameRendererFactory.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,177 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SeriesFrameRendererFactory.h"
+
+#include "FrameRenderer.h"
+
+#include <OrthancException.h>
+#include <Logging.h>
+#include <Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginException.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
+
+
+namespace Deprecated
+{
+  void SeriesFrameRendererFactory::ReadCurrentFrameDataset(size_t frame)
+  {
+    if (currentDataset_.get() != NULL &&
+        (fast_ || currentFrame_ == frame))
+    {
+      // The frame has not changed since the previous call, no need to
+      // update the DICOM dataset
+      return; 
+    }
+      
+    currentDataset_.reset(loader_->DownloadDicom(frame));
+    currentFrame_ = frame;
+
+    if (currentDataset_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void SeriesFrameRendererFactory::GetCurrentPixelSpacing(double& spacingX,
+                                                          double& spacingY) const
+  {
+    if (currentDataset_.get() == NULL)
+    {
+      // There was no previous call "ReadCurrentFrameDataset()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    
+    GeometryToolbox::GetPixelSpacing(spacingX, spacingY, *currentDataset_);
+  }
+
+
+  double SeriesFrameRendererFactory::GetCurrentSliceThickness() const
+  {
+    if (currentDataset_.get() == NULL)
+    {
+      // There was no previous call "ReadCurrentFrameDataset()"
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    
+    try
+    {
+      OrthancPlugins::DicomDatasetReader reader(*currentDataset_);
+
+      double thickness;
+      if (reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS))
+      {
+        return thickness;
+      }
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+    }
+
+    // Some arbitrary large slice thickness
+    return std::numeric_limits<double>::infinity();
+  }
+
+
+  SeriesFrameRendererFactory::SeriesFrameRendererFactory(ISeriesLoader* loader,   // Takes ownership
+                                                         bool fast) :
+    loader_(loader),
+    currentFrame_(0),
+    fast_(fast)
+  {
+    if (loader == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  bool SeriesFrameRendererFactory::GetExtent(double& x1,
+                                             double& y1,
+                                             double& x2,
+                                             double& y2,
+                                             const SliceGeometry& viewportSlice)
+  {
+    if (currentDataset_.get() == NULL)
+    {
+      // There has been no previous call to
+      // "CreateLayerRenderer". Read some arbitrary DICOM frame, the
+      // one at the middle of the series.
+      unsigned int depth = loader_->GetGeometry().GetSliceCount();
+      ReadCurrentFrameDataset(depth / 2);
+    }
+
+    double spacingX, spacingY;
+    GetCurrentPixelSpacing(spacingX, spacingY);
+
+    return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, 
+                                             viewportSlice, 
+                                             loader_->GetGeometry().GetSlice(0), 
+                                             loader_->GetWidth(), 
+                                             loader_->GetHeight(),
+                                             spacingX, spacingY);
+  }
+
+
+  ILayerRenderer* SeriesFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    size_t closest;
+    double distance;
+
+    bool isOpposite;
+    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, loader_->GetGeometry().GetNormal(), viewportSlice.GetNormal()) ||
+        !loader_->GetGeometry().ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin()))
+    {
+      // Unable to compute the slice in the series that is the
+      // closest to the slice displayed by the viewport
+      return NULL;
+    }
+
+    ReadCurrentFrameDataset(closest);
+    assert(currentDataset_.get() != NULL);
+        
+    double spacingX, spacingY;
+    GetCurrentPixelSpacing(spacingX, spacingY);
+
+    if (distance <= GetCurrentSliceThickness() / 2.0)
+    {
+      SliceGeometry frameSlice(*currentDataset_);
+      return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), 
+                                           frameSlice,
+                                           *currentDataset_, 
+                                           spacingX, spacingY,
+                                           true);
+    }
+    else
+    {
+      // The closest slice of the series is too far away from the
+      // slice displayed by the viewport
+      return NULL;
+    }
+  }
+
+
+  ISliceableVolume& SeriesFrameRendererFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/SeriesFrameRendererFactory.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerRendererFactory.h"
+
+#include "../Toolbox/ISeriesLoader.h"
+
+namespace Deprecated
+{
+  class SeriesFrameRendererFactory : public ILayerRendererFactory
+  {
+  private:
+    std::auto_ptr<ISeriesLoader>  loader_;
+    size_t                        currentFrame_;
+    bool                          fast_;
+
+    std::auto_ptr<OrthancPlugins::IDicomDataset>  currentDataset_;
+
+    void ReadCurrentFrameDataset(size_t frame);
+
+    void GetCurrentPixelSpacing(double& spacingX,
+                                double& spacingY) const;
+
+    double GetCurrentSliceThickness() const;
+
+  public:
+    SeriesFrameRendererFactory(ISeriesLoader* loader,   // Takes ownership
+                               bool fast);
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& viewportSlice);
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/SingleFrameRendererFactory.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,88 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SingleFrameRendererFactory.h"
+
+#include "FrameRenderer.h"
+#include "../Toolbox/MessagingToolbox.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+#include <OrthancException.h>
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
+
+namespace Deprecated
+{
+  SingleFrameRendererFactory::SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc,
+                                                         const std::string& instanceId,
+                                                         unsigned int frame) :
+    orthanc_(orthanc),
+    instance_(instanceId),
+    frame_(frame)
+  {
+    dicom_.reset(new OrthancPlugins::FullOrthancDataset(orthanc, "/instances/" + instanceId + "/tags"));
+
+    DicomFrameConverter converter;
+    converter.ReadParameters(*dicom_);
+    format_ = converter.GetExpectedPixelFormat();
+  }
+
+
+  bool SingleFrameRendererFactory::GetExtent(double& x1,
+                                             double& y1,
+                                             double& x2,
+                                             double& y2,
+                                             const SliceGeometry& viewportSlice)
+  {
+    // Assume that PixelSpacingX == PixelSpacingY == 1
+
+    OrthancPlugins::DicomDatasetReader reader(*dicom_);
+
+    unsigned int width, height;
+
+    if (!reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) ||
+        !reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    x1 = 0;
+    y1 = 0;
+    x2 = static_cast<double>(width);
+    y2 = static_cast<double>(height);
+
+    return true;
+  }
+
+
+  ILayerRenderer* SingleFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
+  {
+    SliceGeometry frameSlice(*dicom_);
+    return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), 
+                                         frameSlice, *dicom_, 1, 1, true);
+  }
+
+
+  ISliceableVolume& SingleFrameRendererFactory::GetSourceVolume() const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/SingleFrameRendererFactory.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerRendererFactory.h"
+#include <Plugins/Samples/Common/IOrthancConnection.h>
+
+namespace Deprecated
+{
+  class SingleFrameRendererFactory : public ILayerRendererFactory
+  {
+  private:
+    OrthancPlugins::IOrthancConnection&           orthanc_;
+    std::auto_ptr<OrthancPlugins::IDicomDataset>  dicom_;
+
+    std::string           instance_;
+    unsigned int          frame_;
+    Orthanc::PixelFormat  format_;
+
+  public:
+    SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc,
+                               const std::string& instanceId,
+                               unsigned int frame);
+
+    const OrthancPlugins::IDicomDataset& GetDataset() const
+    {
+      return *dicom_;
+    }
+
+    SliceGeometry GetSliceGeometry()
+    {
+      return SliceGeometry(*dicom_);
+    }
+
+    virtual bool GetExtent(double& x1,
+                           double& y1,
+                           double& x2,
+                           double& y2,
+                           const SliceGeometry& viewportSlice);
+
+    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
+
+    virtual bool HasSourceVolume() const
+    {
+      return false;
+    }
+
+    virtual ISliceableVolume& GetSourceVolume() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,54 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SliceOutlineRenderer.h"
+
+namespace Deprecated
+{
+  bool SliceOutlineRenderer::RenderLayer(OrthancStone::CairoContext& context,
+                                         const ViewportGeometry& view)
+  {
+    if (style_.visible_)
+    {
+      cairo_t *cr = context.GetObject();
+      cairo_save(cr);
+
+      context.SetSourceColor(style_.drawColor_);
+
+      double x1 = -0.5 * pixelSpacingX_;
+      double y1 = -0.5 * pixelSpacingY_;
+        
+      cairo_set_line_width(cr, 1.0 / view.GetZoom());
+      cairo_rectangle(cr, x1, y1,
+                      static_cast<double>(width_) * pixelSpacingX_,
+                      static_cast<double>(height_) * pixelSpacingY_);
+
+      double handleSize = 10.0f / view.GetZoom();
+      cairo_move_to(cr, x1 + handleSize, y1);
+      cairo_line_to(cr, x1, y1 + handleSize);
+
+      cairo_stroke(cr);
+      cairo_restore(cr);
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Layers/SliceOutlineRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,67 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerRenderer.h"
+#include "../Toolbox/Slice.h"
+
+namespace Deprecated
+{
+  class SliceOutlineRenderer : public ILayerRenderer
+  {
+  private:
+    OrthancStone::CoordinateSystem3D  geometry_;
+    double              pixelSpacingX_;
+    double              pixelSpacingY_;
+    unsigned int        width_;
+    unsigned int        height_;
+    RenderStyle         style_;
+
+  public:
+    SliceOutlineRenderer(const Slice& slice) :
+      geometry_(slice.GetGeometry()),
+      pixelSpacingX_(slice.GetPixelSpacingX()),
+      pixelSpacingY_(slice.GetPixelSpacingY()),
+      width_(slice.GetWidth()),
+      height_(slice.GetHeight())
+    {
+    }
+
+    virtual bool RenderLayer(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view);
+
+    virtual void SetLayerStyle(const RenderStyle& style)
+    {
+      style_ = style;
+    }
+
+    virtual const OrthancStone::CoordinateSystem3D& GetLayerSlice()
+    {
+      return geometry_;
+    }
+
+    virtual bool IsFullQuality()
+    {
+      return true;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/SmartLoader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,292 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SmartLoader.h"
+
+#include "../Messages/MessageForwarder.h"
+#include "../StoneException.h"
+#include "Core/Images/Image.h"
+#include "Core/Logging.h"
+#include "Layers/DicomSeriesVolumeSlicer.h"
+#include "Layers/FrameRenderer.h"
+#include "Widgets/SliceViewerWidget.h"
+
+namespace Deprecated
+{
+  enum CachedSliceStatus
+  {
+    CachedSliceStatus_ScheduledToLoad,
+    CachedSliceStatus_GeometryLoaded,
+    CachedSliceStatus_ImageLoaded
+  };
+
+  class SmartLoader::CachedSlice : public IVolumeSlicer
+  {
+  public:
+    class RendererFactory : public LayerReadyMessage::IRendererFactory
+    {
+    private:
+      const CachedSlice&  that_;
+
+    public:
+      RendererFactory(const CachedSlice& that) :
+        that_(that)
+      {
+      }
+
+      virtual ILayerRenderer* CreateRenderer() const
+      {
+        bool isFull = (that_.effectiveQuality_ == SliceImageQuality_FullPng ||
+                       that_.effectiveQuality_ == SliceImageQuality_FullPam);
+
+        return FrameRenderer::CreateRenderer(*that_.image_, *that_.slice_, isFull);
+      }
+    };
+    
+    unsigned int                    sliceIndex_;
+    std::auto_ptr<Slice>            slice_;
+    boost::shared_ptr<Orthanc::ImageAccessor>   image_;
+    SliceImageQuality               effectiveQuality_;
+    CachedSliceStatus               status_;
+
+  public:
+    CachedSlice(OrthancStone::MessageBroker& broker) :
+    IVolumeSlicer(broker)
+    {
+    }
+
+    virtual ~CachedSlice()
+    {
+    }
+
+    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
+                           const OrthancStone::CoordinateSystem3D& viewportSlice)
+    {
+      // TODO: viewportSlice is not used !!!!
+      slice_->GetExtent(points);
+      return true;
+    }
+
+    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
+    {
+      // TODO: viewportSlice is not used !!!!
+
+      // it has already been loaded -> trigger the "layer ready" message immediately otherwise, do nothing now.  The LayerReady will be triggered
+      // once the VolumeSlicer is ready
+      if (status_ == CachedSliceStatus_ImageLoaded)
+      {
+        LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId();
+
+        RendererFactory factory(*this);   
+        BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry()));
+      }
+      else
+      {
+        LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is not loaded yet): " << slice_->GetOrthancInstanceId();
+      }
+    }
+
+    CachedSlice* Clone() const
+    {
+      CachedSlice* output = new CachedSlice(GetBroker());
+      output->sliceIndex_ = sliceIndex_;
+      output->slice_.reset(slice_->Clone());
+      output->image_ = image_;
+      output->effectiveQuality_ = effectiveQuality_;
+      output->status_ = status_;
+
+      return output;
+    }
+
+  };
+
+
+  SmartLoader::SmartLoader(OrthancStone::MessageBroker& broker,  
+                           OrthancApiClient& orthancApiClient) :
+    IObservable(broker),
+    IObserver(broker),
+    imageQuality_(SliceImageQuality_FullPam),
+    orthancApiClient_(orthancApiClient)
+  {
+  }
+
+  void SmartLoader::SetFrameInWidget(SliceViewerWidget& sliceViewer, 
+                                     size_t layerIndex, 
+                                     const std::string& instanceId, 
+                                     unsigned int frame)
+  {
+    // TODO: check if this frame has already been loaded or is already being loaded.
+    // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately"
+    //   (it can not be immediate because Observers needs to register first and this is done after this method returns)
+    // - if currently loading, we need to return an object that will observe the existing VolumeSlicer and forward
+    //   the messages to its observables
+    // in both cases, we must be carefull about objects lifecycle !!!
+
+    std::auto_ptr<IVolumeSlicer> layerSource;
+    std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
+    SmartLoader::CachedSlice* cachedSlice = NULL;
+
+    if (cachedSlices_.find(sliceKeyId) != cachedSlices_.end()) // && cachedSlices_[sliceKeyId]->status_ == CachedSliceStatus_Loaded)
+    {
+      layerSource.reset(cachedSlices_[sliceKeyId]->Clone());
+      cachedSlice = dynamic_cast<SmartLoader::CachedSlice*>(layerSource.get());
+    }
+    else
+    {
+      layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_));
+      dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_);
+      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady));
+      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady));
+      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady));
+      dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame);
+    }
+
+    // make sure that the widget registers the events before we trigger them
+    if (sliceViewer.GetLayerCount() == layerIndex)
+    {
+      sliceViewer.AddLayer(layerSource.release());
+    }
+    else if (sliceViewer.GetLayerCount() > layerIndex)
+    {
+      sliceViewer.ReplaceLayer(layerIndex, layerSource.release());
+    }
+    else
+    {
+      throw OrthancStone::StoneException(OrthancStone::ErrorCode_CanOnlyAddOneLayerAtATime);
+    }
+
+    if (cachedSlice != NULL)
+    {
+      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice));
+    }
+
+  }
+
+  void SmartLoader::PreloadSlice(const std::string instanceId, 
+                                 unsigned int frame)
+  {
+    // TODO: reactivate -> need to be able to ScheduleLayerLoading in IVolumeSlicer without calling ScheduleLayerCreation
+    return;
+    // TODO: check if it is already in the cache
+
+
+
+    // create the slice in the cache with "empty" data
+    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
+    cachedSlice->slice_.reset(new Slice(instanceId, frame));
+    cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad;
+    std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
+
+    LOG(WARNING) << "Will preload: " << sliceKeyId;
+
+    cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice);
+
+    std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_));
+
+    dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_);
+    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady));
+    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady));
+    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady));
+    dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame);
+
+    // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache
+    preloadingInstances_[sliceKeyId] = boost::shared_ptr<IVolumeSlicer>(layerSource.release());
+  }
+
+
+//  void PreloadStudy(const std::string studyId)
+//  {
+//    /* TODO */
+//  }
+
+//  void PreloadSeries(const std::string seriesId)
+//  {
+//    /* TODO */
+//  }
+
+
+  void SmartLoader::OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message)
+  {
+    const DicomSeriesVolumeSlicer& source =
+      dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin());
+
+    // save/replace the slice in cache
+    const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount()
+    std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
+                              boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    LOG(WARNING) << "Geometry ready: " << sliceKeyId;
+
+    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
+    cachedSlice->slice_.reset(slice.Clone());
+    cachedSlice->effectiveQuality_ = source.GetImageQuality();
+    cachedSlice->status_ = CachedSliceStatus_GeometryLoaded;
+
+    cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice);
+
+    // re-emit original Layer message to observers
+    BroadcastMessage(message);
+  }
+
+
+  void SmartLoader::OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message)
+  {
+    // save/replace the slice in cache
+    const Slice& slice = message.GetSlice();
+    std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
+                              boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    LOG(WARNING) << "Image ready: " << sliceKeyId;
+
+    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
+    cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame()));
+    cachedSlice->effectiveQuality_ = message.GetImageQuality();
+    cachedSlice->slice_.reset(message.GetSlice().Clone());
+    cachedSlice->status_ = CachedSliceStatus_ImageLoaded;
+
+    cachedSlices_[sliceKeyId] = cachedSlice;
+
+    // re-emit original Layer message to observers
+    BroadcastMessage(message);
+  }
+
+
+  void SmartLoader::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message)
+  {
+    const DicomSeriesVolumeSlicer& source =
+      dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin());
+    
+    const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() ?
+    std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
+                              boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    LOG(WARNING) << "Layer ready: " << sliceKeyId;
+
+    // remove the slice from the preloading slices now that it has been fully loaded and it is referenced in the cache
+    if (preloadingInstances_.find(sliceKeyId) != preloadingInstances_.end())
+    {
+      preloadingInstances_.erase(sliceKeyId);
+    }
+
+    // re-emit original Layer message to observers
+    BroadcastMessage(message);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/SmartLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,67 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+#include <map>
+
+#include "Layers/DicomSeriesVolumeSlicer.h"
+#include "../Messages/IObservable.h"
+#include "Toolbox/OrthancApiClient.h"
+
+namespace Deprecated
+{
+  class SliceViewerWidget;
+
+  class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver
+  {
+    class CachedSlice;
+
+  protected:
+    typedef std::map<std::string, boost::shared_ptr<SmartLoader::CachedSlice> > CachedSlices;
+    CachedSlices cachedSlices_;
+
+    typedef std::map<std::string, boost::shared_ptr<IVolumeSlicer> > PreloadingInstances;
+    PreloadingInstances preloadingInstances_;
+
+    SliceImageQuality     imageQuality_;
+    OrthancApiClient&     orthancApiClient_;
+
+  public:
+    SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient);  // TODO: add maxPreloadStorageSizeInBytes
+
+//    void PreloadStudy(const std::string studyId);
+//    void PreloadSeries(const std::string seriesId);
+    void PreloadSlice(const std::string instanceId, unsigned int frame);
+
+    void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
+
+    void SetFrameInWidget(SliceViewerWidget& sliceViewer, size_t layerIndex, const std::string& instanceId, unsigned int frame);
+
+    void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId);
+
+  private:
+    void OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message);
+    void OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message);
+    void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message);
+
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,145 @@
+/**
+ * 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 "BaseWebService.h"
+
+#include "../../Messages/IObservable.h"
+#include "../../../Platforms/Generic/IOracleCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace Deprecated
+{
+
+
+  class BaseWebService::BaseWebServicePayload : public Orthanc::IDynamicObject
+  {
+  private:
+    std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage> >   userSuccessHandler_;
+    std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> >     userFailureHandler_;
+    std::auto_ptr< Orthanc::IDynamicObject>                                   userPayload_;
+
+  public:
+    BaseWebServicePayload(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* userSuccessHandler,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* userFailureHandler,
+                          Orthanc::IDynamicObject* userPayload) :
+      userSuccessHandler_(userSuccessHandler),
+      userFailureHandler_(userFailureHandler),
+      userPayload_(userPayload)
+    {
+    }
+
+    void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const
+    {
+      if (userSuccessHandler_.get() != NULL)
+      {
+        // recreate a success message with the user payload
+        IWebService::HttpRequestSuccessMessage successMessage(message.GetUri(),
+                                                              message.GetAnswer(),
+                                                              message.GetAnswerSize(),
+                                                              message.GetAnswerHttpHeaders(),
+                                                              userPayload_.get());
+        userSuccessHandler_->Apply(successMessage);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const
+    {
+      if (userFailureHandler_.get() != NULL)
+      {
+        // recreate a failure message with the user payload
+        IWebService::HttpRequestErrorMessage failureMessage(message.GetUri(),
+                                                            userPayload_.get());
+
+        userFailureHandler_->Apply(failureMessage);
+      }
+    }
+
+  };
+
+
+  void BaseWebService::GetAsync(const std::string& uri,
+                                const HttpHeaders& headers,
+                                Orthanc::IDynamicObject* payload  /* takes ownership */,
+                                OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                                OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+                                unsigned int timeoutInSeconds)
+  {
+    if (cache_.find(uri) == cache_.end())
+    {
+      GetAsyncInternal(uri, headers,
+                       new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered
+                       new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestSuccessMessage>
+                       (*this, &BaseWebService::CacheAndNotifyHttpSuccess),
+                       new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestErrorMessage>
+                       (*this, &BaseWebService::NotifyHttpError),
+                       timeoutInSeconds);
+    }
+    else
+    {
+      // create a command and "post" it to the Oracle so it is executed and commited "later"
+      NotifyHttpSuccessLater(cache_[uri], payload, successCallback);
+    }
+
+  }
+
+
+
+  void BaseWebService::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
+  {
+    if (message.HasPayload())
+    {
+      dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleSuccess(message);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
+  {
+    cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message));
+    NotifyHttpSuccess(message);
+  }
+
+  void BaseWebService::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message)
+  {
+    if (message.HasPayload())
+    {
+      dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleFailure(message);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/BaseWebService.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,131 @@
+/**
+ * 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 "IWebService.h"
+
+#include <string>
+#include <map>
+
+namespace Deprecated
+{
+  // This is an intermediate of IWebService that implements some caching on
+  // the HTTP GET requests
+  class BaseWebService : public IWebService, public OrthancStone::IObserver
+  {
+  public:
+    class CachedHttpRequestSuccessMessage
+    {
+    protected:
+      std::string                    uri_;
+      void*                          answer_;
+      size_t                         answerSize_;
+      IWebService::HttpHeaders       answerHeaders_;
+
+    public:
+      CachedHttpRequestSuccessMessage(const IWebService::HttpRequestSuccessMessage& message) :
+        uri_(message.GetUri()),
+        answerSize_(message.GetAnswerSize()),
+        answerHeaders_(message.GetAnswerHttpHeaders())
+      {
+        answer_ =  malloc(answerSize_);
+        memcpy(answer_, message.GetAnswer(), answerSize_);
+      }
+
+      ~CachedHttpRequestSuccessMessage()
+      {
+        free(answer_);
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      const void* GetAnswer() const
+      {
+        return answer_;
+      }
+
+      size_t GetAnswerSize() const
+      {
+        return answerSize_;
+      }
+
+      const IWebService::HttpHeaders&  GetAnswerHttpHeaders() const
+      {
+        return answerHeaders_;
+      }
+
+    };
+  protected:
+    class BaseWebServicePayload;
+
+    bool          cacheEnabled_;
+    std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage> > cache_;  // TODO: this is currently an infinite cache !
+
+  public:
+
+    BaseWebService(OrthancStone::MessageBroker& broker) :
+      IWebService(broker),
+      IObserver(broker),
+      cacheEnabled_(true)
+    {
+    }
+
+    virtual ~BaseWebService()
+    {
+    }
+
+    virtual void EnableCache(bool enable)
+    {
+      cacheEnabled_ = enable;
+    }
+
+    virtual void GetAsync(const std::string& uri,
+                          const HttpHeaders& headers,
+                          Orthanc::IDynamicObject* payload  /* takes ownership */,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                          unsigned int timeoutInSeconds = 60);
+
+  protected:
+    virtual void GetAsyncInternal(const std::string& uri,
+                          const HttpHeaders& headers,
+                          Orthanc::IDynamicObject* payload  /* takes ownership */,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                          unsigned int timeoutInSeconds = 60) = 0;
+
+    virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage,
+                                        Orthanc::IDynamicObject* payload, // takes ownership
+                                        OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback) = 0;
+
+  private:
+    void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
+
+    void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message);
+
+    void CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,282 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomFrameConverter.h"
+
+#include "../../Toolbox/LinearAlgebra.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace Deprecated
+{
+  static const Orthanc::DicomTag IMAGE_TAGS[] =
+  {
+    Orthanc::DICOM_TAG_BITS_STORED,
+    Orthanc::DICOM_TAG_DOSE_GRID_SCALING,
+    Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION,
+    Orthanc::DICOM_TAG_PIXEL_REPRESENTATION,
+    Orthanc::DICOM_TAG_RESCALE_INTERCEPT,
+    Orthanc::DICOM_TAG_RESCALE_SLOPE,
+    Orthanc::DICOM_TAG_WINDOW_CENTER,
+    Orthanc::DICOM_TAG_WINDOW_WIDTH
+  };
+
+  
+  void DicomFrameConverter::SetDefaultParameters()
+  {
+    isSigned_ = true;
+    isColor_ = false;
+    hasRescale_ = false;
+    rescaleIntercept_ = 0;
+    rescaleSlope_ = 1;
+    hasDefaultWindow_ = false;
+    defaultWindowCenter_ = 128;
+    defaultWindowWidth_ = 256;
+    expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+  }
+
+
+  void DicomFrameConverter::ReadParameters(const Orthanc::DicomMap& dicom)
+  {
+    SetDefaultParameters();
+
+    OrthancStone::Vector c, w;
+    if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+        OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
+        c.size() > 0 && 
+        w.size() > 0)
+    {
+      hasDefaultWindow_ = true;
+      defaultWindowCenter_ = static_cast<float>(c[0]);
+      defaultWindowWidth_ = static_cast<float>(w[0]);
+    }
+
+    int32_t tmp;
+    if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION))
+    {
+      // Type 1 tag, must be present
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    isSigned_ = (tmp == 1);
+
+    double doseGridScaling;
+    bool isRTDose = false;
+    
+    if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
+        dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
+    {
+      hasRescale_ = true;
+    }
+    else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
+    {
+      // This is for RT-DOSE
+      hasRescale_ = true;
+      isRTDose = true;
+      rescaleIntercept_ = 0;
+      rescaleSlope_ = doseGridScaling;
+
+      if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_BITS_STORED))
+      {
+        // Type 1 tag, must be present
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      switch (tmp)
+      {
+        case 16:
+          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+          break;
+
+        case 32:
+          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+
+    std::string photometric;
+    if (dicom.CopyToString(photometric, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, false))
+    {
+      photometric = Orthanc::Toolbox::StripSpaces(photometric);
+    }
+    else
+    {
+      // Type 1 tag, must be present
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    photometric_ = Orthanc::StringToPhotometricInterpretation(photometric.c_str());
+    
+    isColor_ = (photometric != "MONOCHROME1" &&
+                photometric != "MONOCHROME2");
+
+    // TODO Add more checks, e.g. on the number of bytes per value
+    // (cf. DicomImageInformation.h in Orthanc)
+
+    if (!isRTDose)
+    {
+      if (isColor_)
+      {
+        expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
+      }
+      else if (isSigned_)
+      {
+        expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
+      }
+      else
+      {
+        expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+      }
+    }
+  }
+
+  
+  void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom)
+  {
+    Orthanc::DicomMap converted;
+
+    for (size_t i = 0; i < sizeof(IMAGE_TAGS) / sizeof(Orthanc::DicomTag); i++)
+    {
+      OrthancPlugins::DicomTag tag(IMAGE_TAGS[i].GetGroup(), IMAGE_TAGS[i].GetElement());
+    
+      std::string value;
+      if (dicom.GetStringValue(value, tag))
+      {
+        converted.SetValue(IMAGE_TAGS[i], value, false);
+      }
+    }
+
+    ReadParameters(converted);
+  }
+    
+
+  void DicomFrameConverter::ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const
+  {
+    assert(sizeof(float) == 4);
+
+    if (source.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (source->GetFormat() == GetExpectedPixelFormat() &&
+        source->GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      // No conversion has to be done, check out (*)
+      return;
+    }
+    else
+    {
+      source.reset(ConvertFrame(*source));
+    }
+  }
+
+
+  Orthanc::ImageAccessor* DicomFrameConverter::ConvertFrame(const Orthanc::ImageAccessor& source) const
+  {
+    assert(sizeof(float) == 4);
+
+    Orthanc::PixelFormat sourceFormat = source.GetFormat();
+
+    if (sourceFormat != GetExpectedPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (sourceFormat == Orthanc::PixelFormat_RGB24)
+    {
+      // This is the case of a color image. No conversion has to be done (*)
+      std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_RGB24, 
+                                                                 source.GetWidth(), 
+                                                                 source.GetHeight(),
+                                                                 false));
+      Orthanc::ImageProcessing::Copy(*converted, source);
+      return converted.release();
+    }
+    else
+    {
+      assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 ||
+             sourceFormat == Orthanc::PixelFormat_Grayscale32 ||
+             sourceFormat == Orthanc::PixelFormat_SignedGrayscale16);
+
+      // This is the case of a grayscale frame. Convert it to Float32.
+      std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                                 source.GetWidth(), 
+                                                                 source.GetHeight(),
+                                                                 false));
+      Orthanc::ImageProcessing::Convert(*converted, source);
+
+      // Correct rescale slope/intercept if need be
+      ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32);
+      
+      return converted.release();
+    }
+  }
+
+
+  void DicomFrameConverter::ApplyRescale(Orthanc::ImageAccessor& image,
+                                         bool useDouble) const
+  {
+    if (image.GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+    
+    if (hasRescale_)
+    {
+      for (unsigned int y = 0; y < image.GetHeight(); y++)
+      {
+        float* p = reinterpret_cast<float*>(image.GetRow(y));
+
+        if (useDouble)
+        {
+          // Slower, accurate implementation using double
+          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+          {
+            double value = static_cast<double>(*p);
+            *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
+          }
+        }
+        else
+        {
+          // Fast, approximate implementation using float
+          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+          {
+            *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
+          }
+        }
+      }
+    }
+  }
+
+  
+  double DicomFrameConverter::Apply(double x) const
+  {
+    return x * rescaleSlope_ + rescaleIntercept_;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/DicomFrameConverter.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,169 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <Plugins/Samples/Common/IDicomDataset.h>
+#include <Core/DicomFormat/DicomMap.h>
+#include <Core/Images/ImageAccessor.h>
+
+#include <memory>
+
+namespace Deprecated
+{
+  /**
+   * This class is responsible for converting the pixel format of a
+   * DICOM frame coming from Orthanc, into a pixel format that is
+   * suitable for Stone, given the relevant DICOM tags:
+   * - Color frames will stay in the RGB24 format.
+   * - Grayscale frames will be converted to the Float32 format.
+   **/
+  class DicomFrameConverter
+  {
+  private:
+    bool    isSigned_;
+    bool    isColor_;
+    bool    hasRescale_;
+    double  rescaleIntercept_;
+    double  rescaleSlope_;
+    bool    hasDefaultWindow_;
+    double  defaultWindowCenter_;
+    double  defaultWindowWidth_;
+    
+    Orthanc::PhotometricInterpretation  photometric_;
+    Orthanc::PixelFormat                expectedPixelFormat_;
+
+    void SetDefaultParameters();
+
+  public:
+    DicomFrameConverter()
+    {
+      SetDefaultParameters();
+    }
+
+    ~DicomFrameConverter()
+    {
+      // TODO: check whether this dtor is called or not
+      // An MSVC warning explains that declaring an
+      // std::auto_ptr with a forward-declared type
+      // prevents its dtor from being called. Does not
+      // seem an issue here (only POD types inside), but
+      // definitely something to keep an eye on.
+      (void)0;
+    }
+
+    // AM: this is required to serialize/deserialize it
+    DicomFrameConverter(
+        bool isSigned,
+        bool isColor,
+        bool hasRescale,
+        double rescaleIntercept,
+        double rescaleSlope,
+        bool hasDefaultWindow,
+        double defaultWindowCenter,
+        double defaultWindowWidth,
+        Orthanc::PhotometricInterpretation photometric,
+        Orthanc::PixelFormat expectedPixelFormat
+        ):
+      isSigned_(isSigned),
+      isColor_(isColor),
+      hasRescale_(hasRescale),
+      rescaleIntercept_(rescaleIntercept),
+      rescaleSlope_(rescaleSlope),
+      hasDefaultWindow_(hasDefaultWindow),
+      defaultWindowCenter_(defaultWindowCenter),
+      defaultWindowWidth_(defaultWindowWidth),
+      photometric_(photometric),
+      expectedPixelFormat_(expectedPixelFormat)
+    {}
+
+    void GetParameters(bool& isSigned,
+                       bool& isColor,
+                       bool& hasRescale,
+                       double& rescaleIntercept,
+                       double& rescaleSlope,
+                       bool& hasDefaultWindow,
+                       double& defaultWindowCenter,
+                       double& defaultWindowWidth,
+                       Orthanc::PhotometricInterpretation& photometric,
+                       Orthanc::PixelFormat& expectedPixelFormat) const
+    {
+      isSigned = isSigned_;
+      isColor = isColor_;
+      hasRescale = hasRescale_;
+      rescaleIntercept = rescaleIntercept_;
+      rescaleSlope = rescaleSlope_;
+      hasDefaultWindow = hasDefaultWindow_;
+      defaultWindowCenter = defaultWindowCenter_;
+      defaultWindowWidth = defaultWindowWidth_;
+      photometric = photometric_;
+      expectedPixelFormat = expectedPixelFormat_;
+    }
+
+    Orthanc::PixelFormat GetExpectedPixelFormat() const
+    {
+      return expectedPixelFormat_;
+    }
+
+    Orthanc::PhotometricInterpretation GetPhotometricInterpretation() const
+    {
+      return photometric_;
+    }
+
+    void ReadParameters(const Orthanc::DicomMap& dicom);
+
+    void ReadParameters(const OrthancPlugins::IDicomDataset& dicom);
+
+    bool HasDefaultWindow() const
+    {
+      return hasDefaultWindow_;
+    }
+    
+    double GetDefaultWindowCenter() const
+    {
+      return defaultWindowCenter_;
+    }
+    
+    double GetDefaultWindowWidth() const
+    {
+      return defaultWindowWidth_;
+    }
+
+    double GetRescaleIntercept() const
+    {
+      return rescaleIntercept_;
+    }
+
+    double GetRescaleSlope() const
+    {
+      return rescaleSlope_;
+    }
+
+    void ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const;
+
+    Orthanc::ImageAccessor* ConvertFrame(const Orthanc::ImageAccessor& source) const;
+
+    void ApplyRescale(Orthanc::ImageAccessor& image,
+                      bool useDouble) const;
+
+    double Apply(double x) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/DownloadStack.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,196 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DownloadStack.h"
+
+#include <Core/OrthancException.h>
+
+#include <cassert>
+
+namespace Deprecated
+{
+  bool DownloadStack::CheckInvariants() const
+  {
+    std::vector<bool> dequeued(nodes_.size(), true);
+
+    int i = firstNode_;
+    while (i != NIL)
+    {
+      const Node& node = nodes_[i];
+
+      dequeued[i] = false;
+
+      if (node.next_ != NIL &&
+          nodes_[node.next_].prev_ != i)
+      {
+        return false;
+      }
+
+      if (node.prev_ != NIL &&
+          nodes_[node.prev_].next_ != i)
+      {
+        return false;
+      }
+
+      i = nodes_[i].next_;
+    }
+
+    for (size_t i = 0; i < nodes_.size(); i++)
+    {
+      if (nodes_[i].dequeued_ != dequeued[i])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  DownloadStack::DownloadStack(unsigned int size)
+  {
+    nodes_.resize(size);
+
+    if (size == 0)
+    {
+      firstNode_ = NIL;
+    }
+    else
+    {
+      for (size_t i = 0; i < size; i++)
+      {
+        nodes_[i].prev_ = static_cast<int>(i - 1);
+        nodes_[i].next_ = static_cast<int>(i + 1);
+        nodes_[i].dequeued_ = false;
+      }
+
+      nodes_.front().prev_ = NIL;
+      nodes_.back().next_ = NIL;
+      firstNode_ = 0;
+    }
+
+    assert(CheckInvariants());
+  }
+
+
+  DownloadStack::~DownloadStack()
+  {
+    assert(CheckInvariants());    
+  }
+
+
+  bool DownloadStack::Pop(unsigned int& value)
+  {
+    assert(CheckInvariants());
+
+    if (firstNode_ == NIL)
+    {
+      for (size_t i = 0; i < nodes_.size(); i++)
+      {
+        assert(nodes_[i].dequeued_);
+      }
+
+      return false;
+    }
+    else
+    {
+      assert(firstNode_ >= 0 && firstNode_ < static_cast<int>(nodes_.size()));
+      value = firstNode_;
+
+      Node& node = nodes_[firstNode_];
+      assert(node.prev_ == NIL);
+      assert(!node.dequeued_);
+
+      node.dequeued_ = true;
+      firstNode_ = node.next_;
+
+      if (firstNode_ != NIL)
+      {
+        nodes_[firstNode_].prev_ = NIL;
+      }
+
+      return true;
+    }
+  }
+
+
+  void DownloadStack::SetTopNodeInternal(unsigned int value)
+  {
+    assert(CheckInvariants());
+
+    Node& node = nodes_[value];
+
+    if (node.dequeued_)
+    {
+      // This node has already been processed by the download thread, nothing to do
+      return;
+    }
+
+    // Remove the node from the list
+    if (node.prev_ == NIL)
+    {
+      assert(firstNode_ == static_cast<int>(value));
+      
+      // This is already the top node in the list, nothing to do
+      return;
+    }
+
+    nodes_[node.prev_].next_ = node.next_;
+
+    if (node.next_ != NIL)
+    {
+      nodes_[node.next_].prev_ = node.prev_;
+    }
+
+    // Add back the node at the top of the list
+    assert(firstNode_ != NIL);
+
+    Node& old = nodes_[firstNode_];
+    assert(old.prev_ == NIL);
+    assert(!old.dequeued_);
+    node.prev_ = NIL;
+    node.next_ = firstNode_;
+    old.prev_ = value;
+
+    firstNode_ = value;
+  }
+
+  
+  void DownloadStack::SetTopNode(unsigned int value)
+  {
+    if (value >= nodes_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    SetTopNodeInternal(value);
+  }
+
+
+  void DownloadStack::SetTopNodePermissive(int value)
+  {
+    if (value >= 0 &&
+        value < static_cast<int>(nodes_.size()))
+    {
+      SetTopNodeInternal(value);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/DownloadStack.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,60 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <vector>
+#include <boost/noncopyable.hpp>
+
+namespace Deprecated
+{
+  class DownloadStack : public boost::noncopyable
+  {
+  private:
+    static const int NIL = -1;
+
+    // This is a doubly-linked list
+    struct Node
+    {
+      int   next_;
+      int   prev_;
+      bool  dequeued_;
+    };
+
+    std::vector<Node>   nodes_;
+    int                 firstNode_;
+
+    bool CheckInvariants() const;
+
+    void SetTopNodeInternal(unsigned int value);  
+
+  public:
+    DownloadStack(unsigned int size);
+
+    ~DownloadStack();
+
+    bool Pop(unsigned int& value);
+
+    void SetTopNode(unsigned int value);  
+
+    void SetTopNodePermissive(int value);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,58 @@
+/**
+ * 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 "../../Messages/IObserver.h"
+#include "../../Messages/ICallable.h"
+
+#include <Core/IDynamicObject.h>
+#include <Core/Logging.h>
+
+#include <string>
+#include <map>
+
+namespace Deprecated
+{
+  // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript).
+  class IDelayedCallExecutor : public boost::noncopyable
+  {
+  protected:
+    OrthancStone::MessageBroker& broker_;
+    
+  public:
+    ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage);
+
+    IDelayedCallExecutor(OrthancStone::MessageBroker& broker) :
+      broker_(broker)
+    {
+    }
+
+    
+    virtual ~IDelayedCallExecutor()
+    {
+    }
+
+    
+    virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,
+                          unsigned int timeoutInMs = 1000) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ISeriesLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,59 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ParallelSlices.h"
+
+#include <Images/ImageAccessor.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
+
+namespace Deprecated
+{
+  class ISeriesLoader : public boost::noncopyable
+  {
+  public:
+    virtual ~ISeriesLoader()
+    {
+    }
+    
+    virtual ParallelSlices& GetGeometry() = 0;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() = 0;
+
+    virtual unsigned int GetWidth() = 0;
+
+    virtual unsigned int GetHeight() = 0;
+
+    virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index) = 0;
+
+    // This downloads the frame from Orthanc. The resulting pixel
+    // format must be Grayscale8, Grayscale16, SignedGrayscale16 or
+    // RGB24. Orthanc Stone assumes the conversion of the photometric
+    // interpretation is done by Orthanc.
+    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0;
+
+    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
+                                                      unsigned int quality) = 0;
+
+    virtual bool IsJpegAvailable() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/IWebService.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "IWebService.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace Deprecated
+{
+  const Orthanc::IDynamicObject&
+  IWebService::HttpRequestSuccessMessage::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+    
+
+  const Orthanc::IDynamicObject&
+  IWebService::HttpRequestErrorMessage::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/IWebService.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,166 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Messages/IObserver.h"
+#include "../../Messages/ICallable.h"
+
+#include <Core/IDynamicObject.h>
+#include <Core/Logging.h>
+
+#include <string>
+#include <map>
+
+namespace Deprecated
+{
+  // The IWebService performs HTTP requests.
+  // Since applications can run in native or WASM environment and, since
+  // in a WASM environment, the WebService is asynchronous, the IWebservice
+  // also implements an asynchronous interface: you must schedule a request
+  // and you'll be notified when the response/error is ready.
+  class IWebService : public boost::noncopyable
+  {
+  protected:
+    OrthancStone::MessageBroker& broker_;
+    
+  public:
+    typedef std::map<std::string, std::string> HttpHeaders;
+
+    class HttpRequestSuccessMessage : public OrthancStone::IMessage
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      const std::string&             uri_;
+      const void*                    answer_;
+      size_t                         answerSize_;
+      const HttpHeaders&             answerHeaders_;
+      const Orthanc::IDynamicObject* payload_;
+
+    public:
+      HttpRequestSuccessMessage(const std::string& uri,
+                                const void* answer,
+                                size_t answerSize,
+                                const HttpHeaders& answerHeaders,
+                                const Orthanc::IDynamicObject* payload) :
+        uri_(uri),
+        answer_(answer),
+        answerSize_(answerSize),
+        answerHeaders_(answerHeaders),
+        payload_(payload)
+      {
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      const void* GetAnswer() const
+      {
+        return answer_;
+      }
+
+      size_t GetAnswerSize() const
+      {
+        return answerSize_;
+      }
+
+      const HttpHeaders&  GetAnswerHttpHeaders() const
+      {
+        return answerHeaders_;
+      }
+
+      bool HasPayload() const
+      {
+        return payload_ != NULL;
+      }
+
+      const Orthanc::IDynamicObject& GetPayload() const;
+    };
+    
+
+    class HttpRequestErrorMessage : public OrthancStone::IMessage
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      const std::string&              uri_;
+      const Orthanc::IDynamicObject*  payload_;
+
+    public:
+      HttpRequestErrorMessage(const std::string& uri,
+                              const Orthanc::IDynamicObject* payload) :
+        uri_(uri),
+        payload_(payload)
+      {
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      bool HasPayload() const
+      {
+        return payload_ != NULL;
+      }
+
+      const Orthanc::IDynamicObject& GetPayload() const;
+    };
+
+
+    IWebService(OrthancStone::MessageBroker& broker) :
+      broker_(broker)
+    {
+    }
+
+    
+    virtual ~IWebService()
+    {
+    }
+
+    virtual void EnableCache(bool enable) = 0;
+    
+    virtual void GetAsync(const std::string& uri,
+                          const HttpHeaders& headers,
+                          Orthanc::IDynamicObject* payload  /* takes ownership */,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                          unsigned int timeoutInSeconds = 60) = 0;
+
+    virtual void PostAsync(const std::string& uri,
+                           const HttpHeaders& headers,
+                           const std::string& body,
+                           Orthanc::IDynamicObject* payload  /* takes ownership */,
+                           OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                           OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                           unsigned int timeoutInSeconds = 60) = 0;
+
+    virtual void DeleteAsync(const std::string& uri,
+                             const HttpHeaders& headers,
+                             Orthanc::IDynamicObject* payload  /* takes ownership */,
+                             OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                             OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                             unsigned int timeoutInSeconds = 60) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/MessagingToolbox.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,456 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MessagingToolbox.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PngReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+#include <Core/Logging.h>
+
+#include <boost/lexical_cast.hpp>
+#include <json/reader.h>
+#include <json/writer.h>
+
+namespace Deprecated
+{
+  namespace MessagingToolbox
+  {
+    static bool ParseVersion(std::string& version,
+                             unsigned int& major,
+                             unsigned int& minor,
+                             unsigned int& patch,
+                             const Json::Value& info)
+    {
+      if (info.type() != Json::objectValue ||
+          !info.isMember("Version") ||
+          info["Version"].type() != Json::stringValue)
+      {
+        return false;
+      }
+
+      version = info["Version"].asString();
+      if (version == "mainline")
+      {
+        // Some arbitrary high values Orthanc versions will never reach ;)
+        major = 999;
+        minor = 999;
+        patch = 999;
+        return true;
+      }
+
+      std::vector<std::string> tokens;
+      Orthanc::Toolbox::TokenizeString(tokens, version, '.');
+      
+      if (tokens.size() != 2 &&
+          tokens.size() != 3)
+      {
+        return false;
+      }
+
+      int a, b, c;
+      try
+      {
+        a = boost::lexical_cast<int>(tokens[0]);
+        b = boost::lexical_cast<int>(tokens[1]);
+
+        if (tokens.size() == 3)
+        {
+          c = boost::lexical_cast<int>(tokens[2]);
+        }
+        else
+        {
+          c = 0;
+        }
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return false;
+      }
+
+      if (a < 0 ||
+          b < 0 ||
+          c < 0)
+      {
+        return false;
+      }
+      else
+      {
+        major = static_cast<unsigned int>(a);
+        minor = static_cast<unsigned int>(b);
+        patch = static_cast<unsigned int>(c);
+        return true;
+      }         
+    }
+
+
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size)
+    {
+      Json::Reader reader;
+      return reader.parse(reinterpret_cast<const char*>(content),
+                          reinterpret_cast<const char*>(content) + size,
+                          target);
+    }
+
+    void JsonToString(std::string& target,
+                      const Json::Value& source)
+    {
+      Json::FastWriter writer;
+      target = writer.write(source);
+    }
+
+    static void ParseJsonException(Json::Value& target,
+                                   const std::string& source)
+    {
+      Json::Reader reader;
+      if (!reader.parse(source, target))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+
+
+    void RestApiGet(Json::Value& target,
+                    OrthancPlugins::IOrthancConnection& orthanc,
+                    const std::string& uri)
+    {
+      std::string tmp;
+      orthanc.RestApiGet(tmp, uri);
+      ParseJsonException(target, tmp);
+    }
+
+
+    void RestApiPost(Json::Value& target,
+                     OrthancPlugins::IOrthancConnection& orthanc,
+                     const std::string& uri,
+                     const std::string& body)
+    {
+      std::string tmp;
+      orthanc.RestApiPost(tmp, uri, body);
+      ParseJsonException(target, tmp);
+    }
+
+
+    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc)
+    {
+      try
+      {
+        Json::Value json;
+        RestApiGet(json, orthanc, "/plugins/web-viewer");
+        return json.type() == Json::objectValue;
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        return false;
+      }
+    }
+
+
+    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc)
+    {
+      Json::Value json;
+      std::string version;
+      unsigned int major, minor, patch;
+
+      try
+      {
+        RestApiGet(json, orthanc, "/system");
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(ERROR) << "Cannot connect to your Orthanc server";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+        
+      if (!ParseVersion(version, major, minor, patch, json))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version;
+
+      // Stone is only compatible with Orthanc >= 1.3.1
+      if (major < 1 ||
+          (major == 1 && minor < 3) ||
+          (major == 1 && minor == 3 && patch < 1))
+      {
+        return false;
+      }
+
+      try
+      {
+        RestApiGet(json, orthanc, "/plugins/web-viewer");       
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        // The Web viewer is not installed, this is OK
+        LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled";
+        return true;
+      }
+
+      if (!ParseVersion(version, major, minor, patch, json))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version;
+
+      return (major >= 3 ||
+              (major == 2 && minor >= 2));
+    }
+
+
+    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                        const std::string& instance,
+                                        unsigned int frame,
+                                        Orthanc::PixelFormat targetFormat)
+    {
+      std::string uri = ("instances/" + instance + "/frames/" + 
+                         boost::lexical_cast<std::string>(frame));
+
+      std::string compressed;
+
+      switch (targetFormat)
+      {
+        case Orthanc::PixelFormat_RGB24:
+          orthanc.RestApiGet(compressed, uri + "/preview");
+          break;
+
+        case Orthanc::PixelFormat_Grayscale16:
+          orthanc.RestApiGet(compressed, uri + "/image-uint16");
+          break;
+
+        case Orthanc::PixelFormat_SignedGrayscale16:
+          orthanc.RestApiGet(compressed, uri + "/image-int16");
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader);
+      result->ReadFromMemory(compressed);
+
+      if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16)
+      {
+        if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+        {
+          result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+      }
+
+      return result.release();
+    }
+
+
+    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                            const std::string& instance,
+                                            unsigned int frame,
+                                            unsigned int quality,
+                                            Orthanc::PixelFormat targetFormat)
+    {
+      if (quality <= 0 || 
+          quality > 100)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      // This requires the official Web viewer plugin to be installed!
+      std::string uri = ("web-viewer/instances/jpeg" + 
+                         boost::lexical_cast<std::string>(quality) + 
+                         "-" + instance + "_" + 
+                         boost::lexical_cast<std::string>(frame));
+
+      Json::Value encoded;
+      RestApiGet(encoded, orthanc, uri);
+
+      if (encoded.type() != Json::objectValue ||
+          !encoded.isMember("Orthanc") ||
+          encoded["Orthanc"].type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      Json::Value& info = encoded["Orthanc"];
+      if (!info.isMember("PixelData") ||
+          !info.isMember("Stretched") ||
+          !info.isMember("Compression") ||
+          info["Compression"].type() != Json::stringValue ||
+          info["PixelData"].type() != Json::stringValue ||
+          info["Stretched"].type() != Json::booleanValue ||
+          info["Compression"].asString() != "Jpeg")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }          
+
+      bool isSigned = false;
+      bool isStretched = info["Stretched"].asBool();
+
+      if (info.isMember("IsSigned"))
+      {
+        if (info["IsSigned"].type() != Json::booleanValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }          
+        else
+        {
+          isSigned = info["IsSigned"].asBool();
+        }
+      }
+
+      std::string jpeg;
+      Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+
+      std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
+      reader->ReadFromMemory(jpeg);
+
+      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+      {
+        if (targetFormat != Orthanc::PixelFormat_RGB24)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+
+        if (isSigned || isStretched)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        else
+        {
+          return reader.release();
+        }
+      }
+
+      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      if (!isStretched)
+      {
+        if (targetFormat != reader->GetFormat())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+
+        return reader.release();
+      }
+
+      int32_t stretchLow = 0;
+      int32_t stretchHigh = 0;
+
+      if (!info.isMember("StretchLow") ||
+          !info.isMember("StretchHigh") ||
+          info["StretchLow"].type() != Json::intValue ||
+          info["StretchHigh"].type() != Json::intValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      stretchLow = info["StretchLow"].asInt();
+      stretchHigh = info["StretchHigh"].asInt();
+
+      if (stretchLow < -32768 ||
+          stretchHigh > 65535 ||
+          (stretchLow < 0 && stretchHigh > 32767))
+      {
+        // This range cannot be represented with a uint16_t or an int16_t
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+      }
+
+      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+      std::auto_ptr<Orthanc::ImageAccessor> image
+        (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false));
+
+      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+      float offset = static_cast<float>(stretchLow) / scaling;
+      
+      Orthanc::ImageProcessing::Convert(*image, *reader);
+      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+
+#if 0
+      /*info.removeMember("PixelData");
+        std::cout << info.toStyledString();*/
+      
+      int64_t a, b;
+      Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image);
+      std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl;
+#endif
+
+      return image.release();
+    }
+
+
+    static void AddTag(Orthanc::DicomMap& target,
+                       const OrthancPlugins::IDicomDataset& source,
+                       const Orthanc::DicomTag& tag)
+    {
+      OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement());
+      
+      std::string value;
+      if (source.GetStringValue(value, key))
+      {
+        target.SetValue(tag, value, false);
+      }
+    }
+
+    
+    void ConvertDataset(Orthanc::DicomMap& target,
+                        const OrthancPlugins::IDicomDataset& source)
+    {
+      target.Clear();
+
+      AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED);
+      AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED);
+      AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS);
+      AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING);
+      AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER);
+      AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR);
+      AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT);
+      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT);
+      AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES);
+      AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION);
+      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION);
+      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING);
+      AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION);
+      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
+      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE);
+      AddTag(target, source, Orthanc::DICOM_TAG_ROWS);
+      AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL);
+      AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
+      AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS);
+      AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID);
+      AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID);
+      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER);
+      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/MessagingToolbox.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,75 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../StoneEnumerations.h"
+
+#include <Core/DicomFormat/DicomMap.h>
+#include <Core/Images/ImageAccessor.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
+#include <Plugins/Samples/Common/IOrthancConnection.h>
+
+#include <json/value.h>
+
+namespace Deprecated
+{
+  namespace MessagingToolbox
+  {
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size);
+
+    void JsonToString(std::string& target,
+                      const Json::Value& source);
+
+
+    void RestApiGet(Json::Value& target,
+                    OrthancPlugins::IOrthancConnection& orthanc,
+                    const std::string& uri);
+
+    void RestApiPost(Json::Value& target,
+                     OrthancPlugins::IOrthancConnection& orthanc,
+                     const std::string& uri,
+                     const std::string& body);
+
+    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc);
+
+    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc);
+
+    // This downloads the image from Orthanc and keeps its pixel
+    // format unchanged (will be either Grayscale8, Grayscale16,
+    // SignedGrayscale16, or RGB24)
+    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                        const std::string& instance,
+                                        unsigned int frame,
+                                        Orthanc::PixelFormat targetFormat);
+
+    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                            const std::string& instance,
+                                            unsigned int frame,
+                                            unsigned int quality,
+                                            Orthanc::PixelFormat targetFormat);
+
+    void ConvertDataset(Orthanc::DicomMap& target,
+                        const OrthancPlugins::IDicomDataset& source);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,336 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "OrthancApiClient.h"
+
+#include "../Toolbox/MessagingToolbox.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  const Orthanc::IDynamicObject& OrthancApiClient::JsonResponseReadyMessage::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+  
+  const Orthanc::IDynamicObject& OrthancApiClient::BinaryResponseReadyMessage::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+  
+  const Orthanc::IDynamicObject& OrthancApiClient::EmptyResponseReadyMessage::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+  
+  class OrthancApiClient::WebServicePayload : public Orthanc::IDynamicObject
+  {
+  private:
+    std::auto_ptr< OrthancStone::MessageHandler<EmptyResponseReadyMessage> >             emptyHandler_;
+    std::auto_ptr< OrthancStone::MessageHandler<JsonResponseReadyMessage> >              jsonHandler_;
+    std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> >            binaryHandler_;
+    std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> >  failureHandler_;
+    std::auto_ptr< Orthanc::IDynamicObject >                               userPayload_;
+
+    void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const
+    {
+      if (failureHandler_.get() != NULL)
+      {
+        failureHandler_->Apply(IWebService::HttpRequestErrorMessage
+                               (message.GetUri(), userPayload_.get()));
+      }
+    }
+    
+  public:
+    WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler,
+                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
+                      Orthanc::IDynamicObject* userPayload) :
+      emptyHandler_(handler),
+      failureHandler_(failureHandler),
+      userPayload_(userPayload)
+    {
+      if (handler == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler,
+                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
+                      Orthanc::IDynamicObject* userPayload) :
+      binaryHandler_(handler),
+      failureHandler_(failureHandler),
+      userPayload_(userPayload)
+    {
+      if (handler == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler,
+                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
+                      Orthanc::IDynamicObject* userPayload) :
+      jsonHandler_(handler),
+      failureHandler_(failureHandler),
+      userPayload_(userPayload)
+    {
+      if (handler == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const
+    {
+      if (emptyHandler_.get() != NULL)
+      {
+        emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage
+                             (message.GetUri(), userPayload_.get()));
+      }
+      else if (binaryHandler_.get() != NULL)
+      {
+        binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage
+                              (message.GetUri(), message.GetAnswer(),
+                               message.GetAnswerSize(), userPayload_.get()));
+      }
+      else if (jsonHandler_.get() != NULL)
+      {
+        Json::Value response;
+        if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize()))
+        {
+          jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage
+                              (message.GetUri(), response, userPayload_.get()));
+        }
+        else
+        {
+          NotifyConversionError(message);
+        }
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const
+    {
+      if (failureHandler_.get() != NULL)
+      {
+        failureHandler_->Apply(IWebService::HttpRequestErrorMessage
+                               (message.GetUri(), userPayload_.get()));
+      }
+    }
+  };
+
+
+  OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker,
+                                     IWebService& web,
+                                     const std::string& baseUrl) :
+    IObservable(broker),
+    IObserver(broker),
+    web_(web),
+    baseUrl_(baseUrl)
+  {
+  }
+
+
+  void OrthancApiClient::GetJsonAsync(
+      const std::string& uri,
+      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload)
+  {
+    IWebService::HttpHeaders emptyHeaders;
+    web_.GetAsync(baseUrl_ + uri,
+                  emptyHeaders,
+                  new WebServicePayload(successCallback, failureCallback, payload),
+                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                  (*this, &OrthancApiClient::NotifyHttpSuccess),
+                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                  (*this, &OrthancApiClient::NotifyHttpError));
+  }
+
+
+  void OrthancApiClient::GetBinaryAsync(
+      const std::string& uri,
+      const std::string& contentType,
+      OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload)
+  {
+    IWebService::HttpHeaders headers;
+    headers["Accept"] = contentType;
+    GetBinaryAsync(uri, headers, successCallback, failureCallback, payload);
+  }
+
+  void OrthancApiClient::GetBinaryAsync(
+      const std::string& uri,
+      const IWebService::HttpHeaders& headers,
+      OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload)
+  {
+    // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str());
+
+    web_.GetAsync(baseUrl_ + uri, headers,
+                  new WebServicePayload(successCallback, failureCallback, payload),
+                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                  (*this, &OrthancApiClient::NotifyHttpSuccess),
+                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                  (*this, &OrthancApiClient::NotifyHttpError));
+  }
+
+  
+  void OrthancApiClient::PostBinaryAsyncExpectJson(
+      const std::string& uri,
+      const std::string& body,
+      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload)
+  {
+    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
+                   new WebServicePayload(successCallback, failureCallback, payload),
+                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                   (*this, &OrthancApiClient::NotifyHttpSuccess),
+                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                   (*this, &OrthancApiClient::NotifyHttpError));
+
+  }
+
+  void OrthancApiClient::PostBinaryAsync(
+      const std::string& uri,
+      const std::string& body)
+  {
+    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL);
+  }
+
+  void OrthancApiClient::PostBinaryAsync(
+      const std::string& uri,
+      const std::string& body,
+      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload   /* takes ownership */)
+  {
+    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
+                   new WebServicePayload(successCallback, failureCallback, payload),
+                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                   (*this, &OrthancApiClient::NotifyHttpSuccess),
+                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                   (*this, &OrthancApiClient::NotifyHttpError));
+  }
+
+  void OrthancApiClient::PostJsonAsyncExpectJson(
+      const std::string& uri,
+      const Json::Value& data,
+      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload)
+  {
+    std::string body;
+    MessagingToolbox::JsonToString(body, data);
+    return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload);
+  }
+
+  void OrthancApiClient::PostJsonAsync(
+      const std::string& uri,
+      const Json::Value& data)
+  {
+    std::string body;
+    MessagingToolbox::JsonToString(body, data);
+    return PostBinaryAsync(uri, body);
+  }
+
+  void OrthancApiClient::PostJsonAsync(
+      const std::string& uri,
+      const Json::Value& data,
+      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload   /* takes ownership */)
+  {
+    std::string body;
+    MessagingToolbox::JsonToString(body, data);
+    return PostBinaryAsync(uri, body, successCallback, failureCallback, payload);
+  }
+
+  void OrthancApiClient::DeleteAsync(
+      const std::string& uri,
+      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
+      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+      Orthanc::IDynamicObject* payload)
+  {
+    web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(),
+                     new WebServicePayload(successCallback, failureCallback, payload),
+                     new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                     (*this, &OrthancApiClient::NotifyHttpSuccess),
+                     new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                     (*this, &OrthancApiClient::NotifyHttpError));
+  }
+
+
+  void OrthancApiClient::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
+  {
+    if (message.HasPayload())
+    {
+      dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleSuccess(message);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  void OrthancApiClient::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message)
+  {
+    if (message.HasPayload())
+    {
+      dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleFailure(message);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/OrthancApiClient.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,243 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <json/json.h>
+
+#include "IWebService.h"
+#include "../../Messages/IObservable.h"
+#include "../../Messages/Promise.h"
+
+namespace Deprecated
+{
+  class OrthancApiClient :
+      public OrthancStone::IObservable,
+      public OrthancStone::IObserver
+  {
+  public:
+    class JsonResponseReadyMessage : public OrthancStone::IMessage
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      const std::string&              uri_;
+      const Json::Value&              json_;
+      const Orthanc::IDynamicObject*  payload_;
+
+    public:
+      JsonResponseReadyMessage(const std::string& uri,
+                               const Json::Value& json,
+                               const Orthanc::IDynamicObject* payload) :
+        uri_(uri),
+        json_(json),
+        payload_(payload)
+      {
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      const Json::Value& GetJson() const
+      {
+        return json_;
+      }
+
+      bool HasPayload() const
+      {
+        return payload_ != NULL;
+      }
+
+      const Orthanc::IDynamicObject& GetPayload() const;
+    };
+    
+
+    class BinaryResponseReadyMessage : public OrthancStone::IMessage
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      const std::string&              uri_;
+      const void*                     answer_;
+      size_t                          answerSize_;
+      const Orthanc::IDynamicObject*  payload_;
+
+    public:
+      BinaryResponseReadyMessage(const std::string& uri,
+                                 const void* answer,
+                                 size_t answerSize,
+                                 const Orthanc::IDynamicObject* payload) :
+        uri_(uri),
+        answer_(answer),
+        answerSize_(answerSize),
+        payload_(payload)
+      {
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      const void* GetAnswer() const
+      {
+        return answer_;
+      }
+
+      size_t GetAnswerSize() const
+      {
+        return answerSize_;
+      }
+
+      bool HasPayload() const
+      {
+        return payload_ != NULL;
+      }
+
+      const Orthanc::IDynamicObject& GetPayload() const;
+    };
+
+
+    class EmptyResponseReadyMessage : public OrthancStone::IMessage
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+    private:
+      const std::string&              uri_;
+      const Orthanc::IDynamicObject*  payload_;
+
+    public:
+      EmptyResponseReadyMessage(const std::string& uri,
+                                const Orthanc::IDynamicObject* payload) :
+        uri_(uri),
+        payload_(payload)
+      {
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      bool HasPayload() const
+      {
+        return payload_ != NULL;
+      }
+
+      const Orthanc::IDynamicObject& GetPayload() const;
+    };
+
+    
+
+  private:
+    class WebServicePayload;
+
+  protected:
+    IWebService&  web_;
+    std::string   baseUrl_;
+
+  public:
+    OrthancApiClient(OrthancStone::MessageBroker& broker,
+                     IWebService& web,
+                     const std::string& baseUrl);
+    
+    virtual ~OrthancApiClient()
+    {
+    }
+
+    const std::string& GetBaseUrl() const {return baseUrl_;}
+
+    // schedule a GET request expecting a JSON response.
+    void GetJsonAsync(const std::string& uri,
+                      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
+                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                      Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    // schedule a GET request expecting a binary response.
+    void GetBinaryAsync(const std::string& uri,
+                        const std::string& contentType,
+                        OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                        OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                        Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    // schedule a GET request expecting a binary response.
+    void GetBinaryAsync(const std::string& uri,
+                        const IWebService::HttpHeaders& headers,
+                        OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                        OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                        Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    // schedule a POST request expecting a JSON response.
+    void PostBinaryAsyncExpectJson(const std::string& uri,
+                                   const std::string& body,
+                                   OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                   OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                                   Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    // schedule a POST request expecting a JSON response.
+    void PostJsonAsyncExpectJson(const std::string& uri,
+                                 const Json::Value& data,
+                                 OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                 OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                                 Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    // schedule a POST request and don't mind the response.
+    void PostJsonAsync(const std::string& uri,
+                       const Json::Value& data);
+
+    // schedule a POST request and don't expect any response.
+    void PostJsonAsync(const std::string& uri,
+                       const Json::Value& data,
+                       OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
+                       OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                       Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+
+    // schedule a POST request and don't mind the response.
+    void PostBinaryAsync(const std::string& uri,
+                         const std::string& body);
+
+    // schedule a POST request and don't expect any response.
+    void PostBinaryAsync(const std::string& uri,
+                         const std::string& body,
+                         OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
+                         OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                         Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    // schedule a DELETE request expecting an empty response.
+    void DeleteAsync(const std::string& uri,
+                     OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
+                     OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                     Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
+
+    void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
+
+    void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message);
+
+  private:
+    void HandleFromCache(const std::string& uri,
+                         const IWebService::HttpHeaders& headers,
+                         Orthanc::IDynamicObject* payload /* takes ownership */);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,904 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancSlicesLoader.h"
+
+#include "../Toolbox/MessagingToolbox.h"
+
+#include <Core/Compression/GzipCompressor.h>
+#include <Core/Endianness.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PngReader.h>
+#include <Core/Images/PamReader.h>
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+
+#include <boost/lexical_cast.hpp>
+
+
+
+/**
+ * TODO This is a SLOW implementation of base64 decoding, because
+ * "Orthanc::Toolbox::DecodeBase64()" does not work properly with
+ * WASM. UNDERSTAND WHY.
+ * https://stackoverflow.com/a/34571089/881731
+ **/
+static std::string base64_decode(const std::string &in)
+{
+  std::string out;
+  
+  std::vector<int> T(256,-1);
+  for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
+  
+  int val=0, valb=-8;
+  for (size_t i = 0; i < in.size(); i++) {
+    unsigned char c = in[i];
+    if (T[c] == -1) break;
+    val = (val<<6) + T[c];
+    valb += 6;
+    if (valb>=0) {
+      out.push_back(char((val>>valb)&0xFF));
+      valb-=8;
+    }
+  }
+  return out;
+}
+
+
+
+namespace Deprecated
+{
+  class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
+  {
+  private:
+    Mode               mode_;
+    unsigned int       frame_;
+    unsigned int       sliceIndex_;
+    const Slice*       slice_;
+    std::string        instanceId_;
+    SliceImageQuality  quality_;
+
+    Operation(Mode mode) :
+      mode_(mode)
+    {
+    }
+
+  public:
+    Mode GetMode() const
+    {
+      return mode_;
+    }
+
+    SliceImageQuality GetQuality() const
+    {
+      assert(mode_ == Mode_LoadImage ||
+             mode_ == Mode_LoadRawImage);
+      return quality_;
+    }
+
+    unsigned int GetSliceIndex() const
+    {
+      assert(mode_ == Mode_LoadImage ||
+             mode_ == Mode_LoadRawImage);
+      return sliceIndex_;
+    }
+
+    const Slice& GetSlice() const
+    {
+      assert(mode_ == Mode_LoadImage ||
+             mode_ == Mode_LoadRawImage);
+      assert(slice_ != NULL);
+      return *slice_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      assert(mode_ == Mode_FrameGeometry);
+      return frame_;
+    }
+
+    const std::string& GetInstanceId() const
+    {
+      assert(mode_ == Mode_FrameGeometry ||
+             mode_ == Mode_InstanceGeometry);
+      return instanceId_;
+    }
+
+    static Operation* DownloadInstanceGeometry(const std::string& instanceId)
+    {
+      std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
+      operation->instanceId_ = instanceId;
+      return operation.release();
+    }
+
+    static Operation* DownloadFrameGeometry(const std::string& instanceId,
+                                            unsigned int frame)
+    {
+      std::auto_ptr<Operation> operation(new Operation(Mode_FrameGeometry));
+      operation->instanceId_ = instanceId;
+      operation->frame_ = frame;
+      return operation.release();
+    }
+
+    static Operation* DownloadSliceImage(unsigned int  sliceIndex,
+                                         const Slice&  slice,
+                                         SliceImageQuality quality)
+    {
+      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
+      tmp->sliceIndex_ = sliceIndex;
+      tmp->slice_ = &slice;
+      tmp->quality_ = quality;
+      return tmp.release();
+    }
+
+    static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
+                                            const Slice&  slice)
+    {
+      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage));
+      tmp->sliceIndex_ = sliceIndex;
+      tmp->slice_ = &slice;
+      tmp->quality_ = SliceImageQuality_InternalRaw;
+      return tmp.release();
+    }
+
+    static Operation* DownloadDicomFile(const Slice&  slice)
+    {
+      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile));
+      tmp->slice_ = &slice;
+      return tmp.release();
+    }
+
+  };
+
+  void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
+                                                    const Orthanc::ImageAccessor& image)
+  {
+    OrthancSlicesLoader::SliceImageReadyMessage msg
+      (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+    BroadcastMessage(msg);
+  }
+  
+  
+  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation)
+  {
+    OrthancSlicesLoader::SliceImageErrorMessage msg
+      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+    BroadcastMessage(msg);
+  }
+  
+  
+  void OrthancSlicesLoader::SortAndFinalizeSlices()
+  {
+    bool ok = slices_.Sort();
+    
+    state_ = State_GeometryReady;
+    
+    if (ok)
+    {
+      LOG(INFO) << "Loaded a series with " << slices_.GetSlicesCount() << " slice(s)";
+      BroadcastMessage(SliceGeometryReadyMessage(*this));
+    }
+    else
+    {
+      LOG(ERROR) << "This series is empty";
+      BroadcastMessage(SliceGeometryErrorMessage(*this));
+    }
+  }
+  
+  void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message)
+  {
+    BroadcastMessage(SliceGeometryErrorMessage(*this));
+    state_ = State_Error;
+  }
+
+  void OrthancSlicesLoader::OnSliceImageError(const IWebService::HttpRequestErrorMessage& message)
+  {
+    NotifySliceImageError(dynamic_cast<const Operation&>(message.GetPayload()));
+    state_ = State_Error;
+  }
+
+  void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    const Json::Value& series = message.GetJson();
+    Json::Value::Members instances = series.getMemberNames();
+    
+    slices_.Reserve(instances.size());
+    
+    for (size_t i = 0; i < instances.size(); i++)
+    {
+      OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
+      
+      Orthanc::DicomMap dicom;
+      MessagingToolbox::ConvertDataset(dicom, dataset);
+      
+      unsigned int frames;
+      if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
+      {
+        frames = 1;
+      }
+      
+      for (unsigned int frame = 0; frame < frames; frame++)
+      {
+        std::auto_ptr<Slice> slice(new Slice);
+        if (slice->ParseOrthancFrame(dicom, instances[i], frame))
+        {
+          OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
+          slices_.AddSlice(geometry, slice.release());
+        }
+        else
+        {
+          LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i];
+        }
+      }
+    }
+    
+    SortAndFinalizeSlices();
+  }
+  
+  void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    const Json::Value& tags = message.GetJson();
+    const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId();
+
+    OrthancPlugins::FullOrthancDataset dataset(tags);
+    
+    Orthanc::DicomMap dicom;
+    MessagingToolbox::ConvertDataset(dicom, dataset);
+
+    unsigned int frames;
+    if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
+    {
+      frames = 1;
+    }
+    
+    LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)";
+    
+    for (unsigned int frame = 0; frame < frames; frame++)
+    {
+      std::auto_ptr<Slice> slice(new Slice);
+      if (slice->ParseOrthancFrame(dicom, instanceId, frame))
+      {
+        OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
+        slices_.AddSlice(geometry, slice.release());
+      }
+      else
+      {
+        LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
+        BroadcastMessage(SliceGeometryErrorMessage(*this));
+        return;
+      }
+    }
+    
+    SortAndFinalizeSlices();
+  }
+  
+  
+  void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    const Json::Value& tags = message.GetJson();
+    const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId();
+    unsigned int frame = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetFrame();
+
+    OrthancPlugins::FullOrthancDataset dataset(tags);
+    
+    state_ = State_GeometryReady;
+    
+    Orthanc::DicomMap dicom;
+    MessagingToolbox::ConvertDataset(dicom, dataset);
+    
+    std::auto_ptr<Slice> slice(new Slice);
+    if (slice->ParseOrthancFrame(dicom, instanceId, frame))
+    {
+      LOG(INFO) << "Loaded instance geometry " << instanceId;
+
+      OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
+      slices_.AddSlice(geometry, slice.release());
+      
+      BroadcastMessage(SliceGeometryReadyMessage(*this));
+    }
+    else
+    {
+      LOG(WARNING) << "Skipping invalid instance " << instanceId;
+      BroadcastMessage(SliceGeometryErrorMessage(*this));
+    }
+  }
+  
+  
+  void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message)
+  {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
+    std::auto_ptr<Orthanc::ImageAccessor>  image;
+    
+    try
+    {
+      image.reset(new Orthanc::PngReader);
+      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize());
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
+        image->GetHeight() != operation.GetSlice().GetHeight())
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+        Orthanc::PixelFormat_SignedGrayscale16)
+    {
+      if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+      }
+      else
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+    
+    NotifySliceImageSuccess(operation, *image);
+  }
+  
+  void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message)
+  {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
+    std::auto_ptr<Orthanc::ImageAccessor>  image;
+
+    try
+    {
+      image.reset(new Orthanc::PamReader);
+      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize());
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
+        image->GetHeight() != operation.GetSlice().GetHeight())
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+        Orthanc::PixelFormat_SignedGrayscale16)
+    {
+      if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+      }
+      else
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+
+    NotifySliceImageSuccess(operation, *image);
+  }
+
+
+  void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
+
+    const Json::Value& encoded = message.GetJson();
+    if (encoded.type() != Json::objectValue ||
+        !encoded.isMember("Orthanc") ||
+        encoded["Orthanc"].type() != Json::objectValue)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    const Json::Value& info = encoded["Orthanc"];
+    if (!info.isMember("PixelData") ||
+        !info.isMember("Stretched") ||
+        !info.isMember("Compression") ||
+        info["Compression"].type() != Json::stringValue ||
+        info["PixelData"].type() != Json::stringValue ||
+        info["Stretched"].type() != Json::booleanValue ||
+        info["Compression"].asString() != "Jpeg")
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    bool isSigned = false;
+    bool isStretched = info["Stretched"].asBool();
+    
+    if (info.isMember("IsSigned"))
+    {
+      if (info["IsSigned"].type() != Json::booleanValue)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        isSigned = info["IsSigned"].asBool();
+      }
+    }
+    
+    std::auto_ptr<Orthanc::ImageAccessor> reader;
+    
+    {
+      std::string jpeg;
+      //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+      jpeg = base64_decode(info["PixelData"].asString());
+      
+      try
+      {
+        reader.reset(new Orthanc::JpegReader);
+        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+    
+    Orthanc::PixelFormat expectedFormat =
+      operation.GetSlice().GetConverter().GetExpectedPixelFormat();
+    
+    if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+    {
+      if (expectedFormat != Orthanc::PixelFormat_RGB24)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      
+      if (isSigned || isStretched)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        NotifySliceImageSuccess(operation, *reader);
+        return;
+      }
+    }
+    
+    if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    if (!isStretched)
+    {
+      if (expectedFormat != reader->GetFormat())
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        NotifySliceImageSuccess(operation, *reader);
+        return;
+      }
+    }
+    
+    int32_t stretchLow = 0;
+    int32_t stretchHigh = 0;
+    
+    if (!info.isMember("StretchLow") ||
+        !info.isMember("StretchHigh") ||
+        info["StretchLow"].type() != Json::intValue ||
+        info["StretchHigh"].type() != Json::intValue)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    stretchLow = info["StretchLow"].asInt();
+    stretchHigh = info["StretchHigh"].asInt();
+    
+    if (stretchLow < -32768 ||
+        stretchHigh > 65535 ||
+        (stretchLow < 0 && stretchHigh > 32767))
+    {
+      // This range cannot be represented with a uint16_t or an int16_t
+      NotifySliceImageError(operation);
+      return;
+    }
+    
+    // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+    std::auto_ptr<Orthanc::ImageAccessor> image
+      (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
+
+    Orthanc::ImageProcessing::Convert(*image, *reader);
+    reader.reset();
+    
+    float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    
+    if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
+    {
+      float offset = static_cast<float>(stretchLow) / scaling;
+      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+    }
+    
+    NotifySliceImageSuccess(operation, *image);
+  }
+
+
+  class StringImage : public Orthanc::ImageAccessor
+  {
+  private:
+    std::string  buffer_;
+    
+  public:
+    StringImage(Orthanc::PixelFormat format,
+                unsigned int width,
+                unsigned int height,
+                std::string& buffer)
+    {
+      if (buffer.size() != Orthanc::GetBytesPerPixel(format) * width * height)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+      
+      buffer_.swap(buffer);  // The source buffer is now empty
+      
+      void* data = (buffer_.empty() ? NULL : &buffer_[0]);
+      
+      AssignWritable(format, width, height,
+                     Orthanc::GetBytesPerPixel(format) * width, data);
+    }
+  };
+  
+  void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message)
+  {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
+    Orthanc::GzipCompressor compressor;
+    
+    std::string raw;
+    compressor.Uncompress(raw, message.GetAnswer(), message.GetAnswerSize());
+    
+    const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation();
+    
+    if (info.GetBitsAllocated() == 32 &&
+        info.GetBitsStored() == 32 &&
+        info.GetHighBit() == 31 &&
+        info.GetChannelCount() == 1 &&
+        !info.IsSigned() &&
+        info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 &&
+        raw.size() == info.GetWidth() * info.GetHeight() * 4)
+    {
+      // This is the case of RT-DOSE (uint32_t values)
+      
+      std::auto_ptr<Orthanc::ImageAccessor> image
+        (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
+                         info.GetHeight(), raw));
+      
+      // TODO - Only for big endian
+      for (unsigned int y = 0; y < image->GetHeight(); y++)
+      {
+        uint32_t *p = reinterpret_cast<uint32_t*>(image->GetRow(y));
+        for (unsigned int x = 0; x < image->GetWidth(); x++, p++)
+        {
+          *p = le32toh(*p);
+        }
+      }
+      
+      NotifySliceImageSuccess(operation, *image);
+    }
+    else if (info.GetBitsAllocated() == 16 &&
+             info.GetBitsStored() == 16 &&
+             info.GetHighBit() == 15 &&
+             info.GetChannelCount() == 1 &&
+             !info.IsSigned() &&
+             info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 &&
+             raw.size() == info.GetWidth() * info.GetHeight() * 2)
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> image
+        (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
+                         info.GetHeight(), raw));
+      
+      // TODO - Big endian ?
+      
+      NotifySliceImageSuccess(operation, *image);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+  }
+  
+  
+  OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
+                                           OrthancApiClient& orthanc) :
+    OrthancStone::IObservable(broker),
+    OrthancStone::IObserver(broker),
+    orthanc_(orthanc),
+    state_(State_Initialization)
+  {
+  }
+  
+  
+  void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
+  {
+    if (state_ != State_Initialization)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_LoadingGeometry;
+      orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags",
+                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry),
+                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            NULL);
+    }
+  }
+  
+  void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
+  {
+    if (state_ != State_Initialization)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_LoadingGeometry;
+      
+      // Tag "3004-000c" is "Grid Frame Offset Vector", which is
+      // mandatory to read RT DOSE, but is too long to be returned by default
+      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
+                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry),
+                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            Operation::DownloadInstanceGeometry(instanceId));
+    }
+  }
+  
+  
+  void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId,
+                                              unsigned int frame)
+  {
+    if (state_ != State_Initialization)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_LoadingGeometry;
+
+      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags",
+                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry),
+                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            Operation::DownloadFrameGeometry(instanceId, frame));
+    }
+  }
+  
+  
+  bool OrthancSlicesLoader::IsGeometryReady() const
+  {
+    return state_ == State_GeometryReady;
+  }
+  
+  
+  size_t OrthancSlicesLoader::GetSlicesCount() const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    return slices_.GetSlicesCount();
+  }
+  
+  
+  const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return dynamic_cast<const Slice&>(slices_.GetSlicePayload(index));
+  }
+  
+  
+  bool OrthancSlicesLoader::LookupSlice(size_t& index,
+                                        const OrthancStone::CoordinateSystem3D& plane) const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    double distance;
+    return (slices_.LookupClosestSlice(index, distance, plane) &&
+            distance <= GetSlice(index).GetThickness() / 2.0);
+  }
+  
+  
+  void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
+                                                  size_t index)
+  {
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+    
+    switch (slice.GetConverter().GetExpectedPixelFormat())
+    {
+      case Orthanc::PixelFormat_RGB24:
+        uri += "/preview";
+        break;
+      
+      case Orthanc::PixelFormat_Grayscale16:
+        uri += "/image-uint16";
+        break;
+      
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        uri += "/image-int16";
+        break;
+      
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    orthanc_.GetBinaryAsync(uri, "image/png",
+      new OrthancStone::Callable<OrthancSlicesLoader, 
+        OrthancApiClient::BinaryResponseReadyMessage>
+          (*this, &OrthancSlicesLoader::ParseSliceImagePng),
+      new OrthancStone::Callable<OrthancSlicesLoader, 
+        IWebService::HttpRequestErrorMessage>
+          (*this, &OrthancSlicesLoader::OnSliceImageError),
+      Operation::DownloadSliceImage(
+        static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng));
+}
+  
+  void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
+                                                  size_t index)
+  {
+    std::string uri = 
+      ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
+      boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    switch (slice.GetConverter().GetExpectedPixelFormat())
+    {
+      case Orthanc::PixelFormat_RGB24:
+        uri += "/preview";
+        break;
+
+      case Orthanc::PixelFormat_Grayscale16:
+        uri += "/image-uint16";
+        break;
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        uri += "/image-int16";
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
+      new OrthancStone::Callable<OrthancSlicesLoader, 
+        OrthancApiClient::BinaryResponseReadyMessage>
+          (*this, &OrthancSlicesLoader::ParseSliceImagePam),
+      new OrthancStone::Callable<OrthancSlicesLoader, 
+        IWebService::HttpRequestErrorMessage>
+          (*this, &OrthancSlicesLoader::OnSliceImageError),
+      Operation::DownloadSliceImage(static_cast<unsigned int>(index), 
+                                    slice, SliceImageQuality_FullPam));
+  }
+
+
+  
+  void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
+                                                   size_t index,
+                                                   SliceImageQuality quality)
+  {
+    unsigned int value;
+    
+    switch (quality)
+    {
+      case SliceImageQuality_Jpeg50:
+        value = 50;
+        break;
+
+      case SliceImageQuality_Jpeg90:
+        value = 90;
+        break;
+
+      case SliceImageQuality_Jpeg95:
+        value = 95;
+        break;
+      
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    // This requires the official Web viewer plugin to be installed!
+    std::string uri = ("/web-viewer/instances/jpeg" +
+                       boost::lexical_cast<std::string>(value) +
+                       "-" + slice.GetOrthancInstanceId() + "_" +
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    orthanc_.GetJsonAsync(uri,
+      new OrthancStone::Callable<OrthancSlicesLoader, 
+        OrthancApiClient::JsonResponseReadyMessage>
+          (*this, &OrthancSlicesLoader::ParseSliceImageJpeg),
+      new OrthancStone::Callable<OrthancSlicesLoader, 
+        IWebService::HttpRequestErrorMessage>
+          (*this, &OrthancSlicesLoader::OnSliceImageError),
+        Operation::DownloadSliceImage(
+          static_cast<unsigned int>(index), slice, quality));
+  }
+  
+  
+  
+  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
+                                                   SliceImageQuality quality)
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    const Slice& slice = GetSlice(index);
+    
+    if (slice.HasOrthancDecoding())
+    {
+      switch (quality)
+      {
+        case SliceImageQuality_FullPng:
+          ScheduleSliceImagePng(slice, index);
+          break;
+        case SliceImageQuality_FullPam:
+          ScheduleSliceImagePam(slice, index);
+          break;
+        default:
+          ScheduleSliceImageJpeg(slice, index, quality);
+      }
+    }
+    else
+    {
+      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
+                         boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
+      orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(),
+        new OrthancStone::Callable<OrthancSlicesLoader, 
+          OrthancApiClient::BinaryResponseReadyMessage>
+            (*this, &OrthancSlicesLoader::ParseSliceRawImage),
+        new OrthancStone::Callable<OrthancSlicesLoader,
+          IWebService::HttpRequestErrorMessage>
+            (*this, &OrthancSlicesLoader::OnSliceImageError),
+        Operation::DownloadSliceRawImage(
+          static_cast<unsigned int>(index), slice));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,209 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Messages/IObservable.h"
+#include "../../StoneEnumerations.h"
+#include "../../Toolbox/SlicesSorter.h"
+#include "IWebService.h"
+#include "OrthancApiClient.h"
+#include "Slice.h"
+
+#include <Core/Images/Image.h>
+
+
+namespace Deprecated
+{
+  class OrthancSlicesLoader : public OrthancStone::IObservable, public OrthancStone::IObserver
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryErrorMessage, OrthancSlicesLoader);
+
+    
+    class SliceImageReadyMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      unsigned int                   sliceIndex_;
+      const Slice&                   slice_;
+      const Orthanc::ImageAccessor&  image_;
+      SliceImageQuality              effectiveQuality_;
+
+    public:
+      SliceImageReadyMessage(const OrthancSlicesLoader& origin,
+                             unsigned int sliceIndex,
+                             const Slice& slice,
+                             const Orthanc::ImageAccessor& image,
+                             SliceImageQuality effectiveQuality) :
+        OriginMessage(origin),
+        sliceIndex_(sliceIndex),
+        slice_(slice),
+        image_(image),
+        effectiveQuality_(effectiveQuality)
+      {
+      }
+
+      unsigned int GetSliceIndex() const
+      {
+        return sliceIndex_;
+      }
+
+      const Slice& GetSlice() const
+      {
+        return slice_;
+      }
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return image_;
+      }
+
+      SliceImageQuality GetEffectiveQuality() const
+      {
+        return effectiveQuality_;
+      }        
+    };
+    
+
+    class SliceImageErrorMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      const Slice&       slice_;
+      unsigned int       sliceIndex_;
+      SliceImageQuality  effectiveQuality_;
+
+    public:
+      SliceImageErrorMessage(const OrthancSlicesLoader& origin,
+                             unsigned int sliceIndex,
+                             const Slice& slice,
+                             SliceImageQuality effectiveQuality) :
+        OriginMessage(origin),
+        slice_(slice),
+        sliceIndex_(sliceIndex),
+        effectiveQuality_(effectiveQuality)
+      {
+      }
+      unsigned int GetSliceIndex() const
+      {
+        return sliceIndex_;
+      }
+
+      const Slice& GetSlice() const
+      {
+        return slice_;
+      }
+
+      SliceImageQuality GetEffectiveQuality() const
+      {
+        return effectiveQuality_;
+      }        
+    };
+    
+  private:
+    enum State
+    {
+      State_Error,
+      State_Initialization,
+      State_LoadingGeometry,
+      State_GeometryReady
+    };
+    
+    enum Mode
+    {
+      Mode_SeriesGeometry,
+      Mode_InstanceGeometry,
+      Mode_FrameGeometry,
+      Mode_LoadImage,
+      Mode_LoadRawImage,
+      Mode_LoadDicomFile
+    };
+
+    class Operation;
+
+    OrthancApiClient&  orthanc_;
+    State         state_;
+    OrthancStone::SlicesSorter  slices_;
+
+    void NotifySliceImageSuccess(const Operation& operation,
+                                 const Orthanc::ImageAccessor& image);
+    
+    void NotifySliceImageError(const Operation& operation);
+
+    void OnGeometryError(const IWebService::HttpRequestErrorMessage& message);
+
+    void OnSliceImageError(const IWebService::HttpRequestErrorMessage& message);
+
+    void ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message);
+
+    void ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message);
+
+    void ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message);
+
+    void ScheduleSliceImagePng(const Slice& slice,
+                               size_t index);
+
+    void ScheduleSliceImagePam(const Slice& slice,
+                               size_t index);
+
+    void ScheduleSliceImageJpeg(const Slice& slice,
+                                size_t index,
+                                SliceImageQuality quality);
+
+    void SortAndFinalizeSlices();
+    
+  public:
+    OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
+                        //ISliceLoaderObserver& callback,
+                        OrthancApiClient& orthancApi);
+
+    void ScheduleLoadSeries(const std::string& seriesId);
+
+    void ScheduleLoadInstance(const std::string& instanceId);
+
+    void ScheduleLoadFrame(const std::string& instanceId,
+                           unsigned int frame);
+
+    bool IsGeometryReady() const;
+
+    size_t GetSlicesCount() const;
+
+    const Slice& GetSlice(size_t index) const;
+
+    bool LookupSlice(size_t& index,
+                     const OrthancStone::CoordinateSystem3D& plane) const;
+
+    void ScheduleLoadSliceImage(size_t index,
+                                SliceImageQuality requestedQuality);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlices.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,215 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ParallelSlices.h"
+
+#include "../../Toolbox/GeometryToolbox.h"
+#include "../../Volumes/ImageBuffer3D.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  ParallelSlices::ParallelSlices()
+  {
+    Clear();
+  }
+
+
+  ParallelSlices::ParallelSlices(const ParallelSlices& other)
+  {
+    normal_ = other.normal_;
+
+    slices_.resize(other.slices_.size());
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(other.slices_[i] != NULL);
+      slices_[i] = new OrthancStone::CoordinateSystem3D(*other.slices_[i]);
+    }
+  }
+
+
+  void ParallelSlices::Clear()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      if (slices_[i] != NULL)
+      {
+        delete slices_[i];
+        slices_[i] = NULL;
+      }
+    }
+
+    slices_.clear();
+    OrthancStone::LinearAlgebra::AssignVector(normal_, 0, 0, 1);
+  }
+  
+
+  ParallelSlices::~ParallelSlices()
+  {
+    Clear();
+  }
+
+
+  void ParallelSlices::AddSlice(const OrthancStone::CoordinateSystem3D& slice)
+  {
+    if (slices_.empty())
+    {
+      normal_ = slice.GetNormal();
+      slices_.push_back(new OrthancStone::CoordinateSystem3D(slice));
+    }
+    else if (OrthancStone::GeometryToolbox::IsParallel(slice.GetNormal(), normal_))
+    {
+      slices_.push_back(new OrthancStone::CoordinateSystem3D(slice));
+    }
+    else
+    {
+      LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void ParallelSlices::AddSlice(const OrthancStone::Vector& origin,
+                                const OrthancStone::Vector& axisX,
+                                const OrthancStone::Vector& axisY)
+  {
+    OrthancStone::CoordinateSystem3D slice(origin, axisX, axisY);
+    AddSlice(slice);
+  }
+
+
+  const OrthancStone::CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const
+  {
+    if (index >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return *slices_[index];
+    }
+  }
+
+
+  bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice,
+                                           double& closestDistance,
+                                           const OrthancStone::Vector& origin) const
+  {
+    if (slices_.empty())
+    {
+      return false;
+    }
+
+    double reference = boost::numeric::ublas::inner_prod(origin, normal_);
+
+    closestSlice = 0;
+    closestDistance = std::numeric_limits<double>::infinity();
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference);
+
+      if (distance < closestDistance)
+      {
+        closestSlice = i;
+        closestDistance = distance;
+      }
+    }
+
+    return true;
+  }
+
+
+  ParallelSlices* ParallelSlices::Reverse() const
+  {
+    std::auto_ptr<ParallelSlices> reversed(new ParallelSlices);
+
+    for (size_t i = slices_.size(); i > 0; i--)
+    {
+      const OrthancStone::CoordinateSystem3D& slice = *slices_[i - 1];
+
+      reversed->AddSlice(slice.GetOrigin(),
+                         -slice.GetAxisX(),
+                         slice.GetAxisY());
+    }
+
+    return reversed.release();
+  }
+
+
+  ParallelSlices* ParallelSlices::FromVolumeImage(const OrthancStone::VolumeImageGeometry& geometry,
+                                                  OrthancStone::VolumeProjection projection)
+  {
+    const OrthancStone::Vector dimensions = geometry.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
+    const OrthancStone::CoordinateSystem3D& axial = geometry.GetAxialGeometry();
+    
+    std::auto_ptr<ParallelSlices> result(new ParallelSlices);
+
+    switch (projection)
+    {
+      case OrthancStone::VolumeProjection_Axial:
+        for (unsigned int z = 0; z < geometry.GetDepth(); z++)
+        {
+          OrthancStone::Vector origin = axial.GetOrigin();
+          origin += static_cast<double>(z) * dimensions[2] * axial.GetNormal();
+
+          result->AddSlice(origin,
+                           axial.GetAxisX(),
+                           axial.GetAxisY());
+        }
+        break;
+
+      case OrthancStone::VolumeProjection_Coronal:
+        for (unsigned int y = 0; y < geometry.GetHeight(); y++)
+        {
+          OrthancStone::Vector origin = axial.GetOrigin();
+          origin += static_cast<double>(y) * dimensions[1] * axial.GetAxisY();
+          origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal();
+
+          result->AddSlice(origin,
+                           axial.GetAxisX(),
+                           -axial.GetNormal());
+        }
+        break;
+
+      case OrthancStone::VolumeProjection_Sagittal:
+        for (unsigned int x = 0; x < geometry.GetWidth(); x++)
+        {
+          OrthancStone::Vector origin = axial.GetOrigin();
+          origin += static_cast<double>(x) * dimensions[0] * axial.GetAxisX();
+          origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal();
+
+          result->AddSlice(origin,
+                           axial.GetAxisY(),
+                           -axial.GetNormal());
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return result.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlices.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,73 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Toolbox/CoordinateSystem3D.h"
+#include "../../Volumes/VolumeImageGeometry.h"
+
+namespace Deprecated
+{
+  class ParallelSlices : public boost::noncopyable
+  {
+  private:
+    OrthancStone::Vector  normal_;
+    std::vector<OrthancStone::CoordinateSystem3D*>  slices_;
+    
+    ParallelSlices& operator= (const ParallelSlices& other);  // Forbidden
+
+    void Clear();
+
+  public:
+    ParallelSlices();
+
+    ParallelSlices(const ParallelSlices& other);
+
+    ~ParallelSlices();
+
+    const OrthancStone::Vector& GetNormal() const
+    {
+      return normal_;
+    }
+
+    void AddSlice(const OrthancStone::CoordinateSystem3D& slice);
+
+    void AddSlice(const OrthancStone::Vector& origin,
+                  const OrthancStone::Vector& axisX,
+                  const OrthancStone::Vector& axisY);
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const OrthancStone::CoordinateSystem3D& GetSlice(size_t index) const;
+
+    bool ComputeClosestSlice(size_t& closestSlice,
+                             double& closestDistance,
+                             const OrthancStone::Vector& origin) const;
+
+    ParallelSlices* Reverse() const;
+
+    static ParallelSlices* FromVolumeImage(const OrthancStone::VolumeImageGeometry& geometry,
+                                           OrthancStone::VolumeProjection projection);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,221 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ParallelSlicesCursor.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  size_t ParallelSlicesCursor::GetDefaultSlice()
+  {
+    if (slices_.get() == NULL)
+    {
+      return 0;
+    }
+    else
+    {
+      return slices_->GetSliceCount() / 2;
+    }
+  }
+
+
+  size_t ParallelSlicesCursor::GetSliceCount()
+  {
+    if (slices_.get() == NULL)
+    {
+      return 0;
+    }
+    else
+    {
+      return slices_->GetSliceCount();
+    }
+  }
+
+
+  OrthancStone::CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice)
+  {
+    if (slices_.get() == NULL)
+    {
+      return OrthancStone::CoordinateSystem3D();
+    }
+    else
+    {
+      return slices_->GetSlice(slice);
+    }
+  }
+
+
+  void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices)
+  {
+    slices_.reset(new ParallelSlices(slices));
+
+    currentSlice_ = GetDefaultSlice();
+  }
+
+
+  OrthancStone::CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice()
+  {
+    if (slices_.get() != NULL &&
+        currentSlice_ < slices_->GetSliceCount())
+    {
+      return slices_->GetSlice(currentSlice_);
+    }
+    else
+    {
+      return OrthancStone::CoordinateSystem3D();  // No slice is available, return the canonical geometry
+    }
+  }
+
+
+  bool ParallelSlicesCursor::SetDefaultSlice()
+  {
+    size_t slice = GetDefaultSlice();
+
+    if (currentSlice_ != slice)
+    {
+      currentSlice_ = slice;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::ApplyOffset(OrthancStone::SliceOffsetMode mode,
+                                         int offset)
+  {
+    if (slices_.get() == NULL)
+    {
+      return false;
+    }
+
+    int count = static_cast<int>(slices_->GetSliceCount());
+    if (count == 0)
+    {
+      return false;
+    }
+
+    int slice;
+    if (static_cast<int>(currentSlice_) >= count)
+    {
+      slice = count - 1;
+    }
+    else
+    {
+      slice = static_cast<int>(currentSlice_);
+    }
+
+    switch (mode)
+    {
+      case OrthancStone::SliceOffsetMode_Absolute:
+      {
+        slice = offset;
+        break;
+      }
+
+      case OrthancStone::SliceOffsetMode_Relative:
+      {
+        slice += offset;
+        break;
+      }
+
+      case OrthancStone::SliceOffsetMode_Loop:
+      {
+        slice += offset;
+        while (slice < 0)
+        {
+          slice += count;
+        }
+
+        while (slice >= count)
+        {
+          slice -= count;
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (slice < 0)
+    {
+      slice = 0;
+    }
+
+    if (slice >= count)
+    {
+      slice = count - 1;
+    }
+
+    if (slice != static_cast<int>(currentSlice_))
+    {
+      currentSlice_ = static_cast<int>(slice);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::ApplyWheelEvent(OrthancStone::MouseWheelDirection direction,
+                                             OrthancStone::KeyboardModifiers modifiers)
+  {
+    int offset = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1);
+
+    switch (direction)
+    {
+      case OrthancStone::MouseWheelDirection_Down:
+        return ApplyOffset(OrthancStone::SliceOffsetMode_Relative, -offset);
+
+      case OrthancStone::MouseWheelDirection_Up:
+        return ApplyOffset(OrthancStone::SliceOffsetMode_Relative, offset);
+
+      default:
+        return false;
+    }
+  }
+
+
+  bool ParallelSlicesCursor::LookupSliceContainingPoint(const OrthancStone::Vector& p)
+  {
+    size_t slice;
+    double distance;
+
+    if (slices_.get() != NULL &&
+        slices_->ComputeClosestSlice(slice, distance, p))
+    {
+      if (currentSlice_ != slice)
+      {
+        currentSlice_ = slice;
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ParallelSlicesCursor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ParallelSlices.h"
+#include "../../StoneEnumerations.h"
+
+namespace Deprecated
+{
+  class ParallelSlicesCursor : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<ParallelSlices>  slices_;
+    size_t                         currentSlice_;
+
+    size_t GetDefaultSlice();
+
+  public:
+    ParallelSlicesCursor() :
+      currentSlice_(0)
+    {
+    }
+
+    void SetGeometry(const ParallelSlices& slices);
+
+    size_t GetSliceCount();
+
+    OrthancStone::CoordinateSystem3D GetSlice(size_t slice);
+
+    OrthancStone::CoordinateSystem3D GetCurrentSlice();
+
+    // Returns "true" iff. the slice has actually changed
+    bool SetDefaultSlice();
+
+    // Returns "true" iff. the slice has actually changed
+    bool ApplyOffset(OrthancStone::SliceOffsetMode mode,
+                     int offset);
+
+    // Returns "true" iff. the slice has actually changed
+    bool ApplyWheelEvent(OrthancStone::MouseWheelDirection direction,
+                         OrthancStone::KeyboardModifiers modifiers);
+
+    // Returns "true" iff. the slice has actually changed
+    bool LookupSliceContainingPoint(const OrthancStone::Vector& p);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/Slice.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,367 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Slice.h"
+
+#include "../../StoneEnumerations.h"
+#include "../../Toolbox/GeometryToolbox.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace Deprecated
+{
+  static bool ParseDouble(double& target,
+                          const std::string& source)
+  {
+    try
+    {
+      target = boost::lexical_cast<double>(source);
+      return true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      return false;
+    }
+  }
+
+  Slice* Slice::Clone() const
+  {
+    std::auto_ptr<Slice> target(new Slice());
+
+    target->type_ = type_;
+    target->orthancInstanceId_ = orthancInstanceId_;
+    target->sopClassUid_ = sopClassUid_;
+    target->frame_ = frame_;
+    target->frameCount_ = frameCount_;
+    target->geometry_ = geometry_;
+    target->pixelSpacingX_ = pixelSpacingX_;
+    target->pixelSpacingY_ = pixelSpacingY_;
+    target->thickness_ = thickness_;
+    target->width_ = width_;
+    target->height_ = height_;
+    target->converter_ = converter_;
+    if (imageInformation_.get() != NULL)
+      target->imageInformation_.reset(imageInformation_->Clone());
+
+    return target.release();
+  }
+  
+  bool Slice::ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset,
+                                    unsigned int frame)
+  {
+    // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
+
+    {
+      std::string increment;
+
+      if (dataset.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
+      {
+        Orthanc::Toolbox::ToUpperCase(increment);
+        if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
+        {
+          LOG(ERROR) << "Bad value for the \"FrameIncrementPointer\" tag";
+          return false;
+        }
+      }
+    }
+
+    std::string offsetTag;
+
+    if (!dataset.CopyToString(offsetTag, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, false) ||
+        offsetTag.empty())
+    {
+      LOG(ERROR) << "Cannot read the \"GridFrameOffsetVector\" tag, check you are using Orthanc >= 1.3.1";
+      return false;
+    }
+
+    std::vector<std::string> offsets;
+    Orthanc::Toolbox::TokenizeString(offsets, offsetTag, '\\');
+
+    if (frameCount_ <= 1 ||
+        offsets.size() < frameCount_ ||
+        offsets.size() < 2 ||
+        frame >= frameCount_)
+    {
+      LOG(ERROR) << "No information about the 3D location of some slice(s) in a RT DOSE";
+      return false;
+    }
+
+    double offset0, offset1, z;
+
+    if (!ParseDouble(offset0, offsets[0]) ||
+        !ParseDouble(offset1, offsets[1]) ||
+        !ParseDouble(z, offsets[frame]))
+    {
+      LOG(ERROR) << "Invalid syntax";
+      return false;
+    }
+
+    if (!OrthancStone::LinearAlgebra::IsCloseToZero(offset0))
+    {
+      LOG(ERROR) << "Invalid syntax";
+      return false;
+    }
+
+    geometry_ = OrthancStone::CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(),
+                                                 //+ 650 * geometry_.GetAxisX(),
+                                                 geometry_.GetAxisX(),
+                                                 geometry_.GetAxisY());
+
+    thickness_ = offset1 - offset0;
+    if (thickness_ < 0)
+    {
+      thickness_ = -thickness_;
+    }
+
+    return true;
+  }
+
+  
+  bool Slice::ParseOrthancFrame(const Orthanc::DicomMap& dataset,
+                                const std::string& instanceId,
+                                unsigned int frame)
+  {
+    orthancInstanceId_ = instanceId;
+    frame_ = frame;
+    type_ = Type_OrthancDecodableFrame;
+    imageInformation_.reset(new Orthanc::DicomImageInformation(dataset));
+
+    if (!dataset.CopyToString(sopClassUid_, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) ||
+        sopClassUid_.empty())
+    {
+      LOG(ERROR) << "Instance without a SOP class UID";
+      return false; 
+    }
+
+    if (!dataset.ParseUnsignedInteger32(frameCount_, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
+    {
+      frameCount_ = 1;   // Assume instance with one frame
+    }
+
+    if (frame >= frameCount_)
+    {
+      return false;
+    }
+
+    if (!dataset.ParseUnsignedInteger32(width_, Orthanc::DICOM_TAG_COLUMNS) ||
+        !dataset.ParseUnsignedInteger32(height_, Orthanc::DICOM_TAG_ROWS))
+    {
+      return false;
+    }
+
+    thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
+
+    std::string tmp;
+    if (dataset.CopyToString(tmp, Orthanc::DICOM_TAG_SLICE_THICKNESS, false))
+    {
+      if (!tmp.empty() &&
+          !ParseDouble(thickness_, tmp))
+      {
+        return false;  // Syntax error
+      }
+    }
+    
+    converter_.ReadParameters(dataset);
+
+    OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset);
+
+    std::string position, orientation;
+    if (dataset.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
+        dataset.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
+    {
+      geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
+
+      bool ok = true;
+
+      switch (OrthancStone::StringToSopClassUid(sopClassUid_))
+      {
+        case OrthancStone::SopClassUid_RTDose:
+          type_ = Type_OrthancRawFrame;
+          ok = ComputeRTDoseGeometry(dataset, frame);
+          break;
+            
+        default:
+          break;
+      }
+
+      if (!ok)
+      {
+        LOG(ERROR) << "Cannot deduce the 3D location of frame " << frame
+                   << " in instance " << instanceId << ", whose SOP class UID is: " << sopClassUid_;
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  
+  const std::string Slice::GetOrthancInstanceId() const
+  {
+    if (type_ == Type_OrthancDecodableFrame ||
+        type_ == Type_OrthancRawFrame)
+    {
+      return orthancInstanceId_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }   
+  }
+
+  
+  unsigned int Slice::GetFrame() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return frame_;
+  }
+
+  
+  const OrthancStone::CoordinateSystem3D& Slice::GetGeometry() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return geometry_;
+  }
+
+  
+  double Slice::GetThickness() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return thickness_;
+  }
+
+  
+  double Slice::GetPixelSpacingX() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return pixelSpacingX_;
+  }
+
+  
+  double Slice::GetPixelSpacingY() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return pixelSpacingY_;
+  }
+
+  
+  unsigned int Slice::GetWidth() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return width_;
+  }
+
+  
+  unsigned int Slice::GetHeight() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return height_;
+  }
+
+
+  const DicomFrameConverter& Slice::GetConverter() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return converter_;
+  }
+
+
+  bool Slice::ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    bool opposite;
+    return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite,
+                                                                GetGeometry().GetNormal(),
+                                                                plane.GetNormal()) &&
+            OrthancStone::LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
+                                                GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
+                                                thickness_ / 2.0));
+  }
+
+  
+  void Slice::GetExtent(std::vector<OrthancStone::Vector>& points) const
+  {
+    double sx = GetPixelSpacingX();
+    double sy = GetPixelSpacingY();
+    double w = static_cast<double>(GetWidth());
+    double h = static_cast<double>(GetHeight());
+
+    points.clear();
+    points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5      * sx, -0.5      * sy));
+    points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, -0.5      * sy));
+    points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5      * sx, (h - 0.5) * sy));
+    points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy));
+  }
+
+
+  const Orthanc::DicomImageInformation& Slice::GetImageInformation() const
+  {
+    if (imageInformation_.get() == NULL)
+    {
+      // Only available if constructing the "Slice" object with a DICOM map
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *imageInformation_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/Slice.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,155 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Toolbox/CoordinateSystem3D.h"
+#include "DicomFrameConverter.h"
+
+#include <Core/DicomFormat/DicomImageInformation.h>
+#include <Core/IDynamicObject.h>
+
+namespace Deprecated
+{
+  // TODO - Remove this class
+  class Slice :
+    public Orthanc::IDynamicObject  /* to be used as a payload of SlicesSorter */
+  {
+  private:
+    enum Type
+    {
+      Type_Invalid,
+      Type_Standalone,
+      Type_OrthancDecodableFrame,
+      Type_OrthancRawFrame
+      // TODO A slice could come from some DICOM file (URL)
+    };
+
+    bool ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset,
+                               unsigned int frame);
+
+    Type                 type_;
+    std::string          orthancInstanceId_;
+    std::string          sopClassUid_;
+    unsigned int         frame_;
+    unsigned int         frameCount_;   // TODO : Redundant with "imageInformation_"
+    OrthancStone::CoordinateSystem3D   geometry_;
+    double               pixelSpacingX_;
+    double               pixelSpacingY_;
+    double               thickness_;
+    unsigned int         width_;   // TODO : Redundant with "imageInformation_"
+    unsigned int         height_;   // TODO : Redundant with "imageInformation_"
+    DicomFrameConverter  converter_;   // TODO : Partially redundant with "imageInformation_"
+
+    std::auto_ptr<Orthanc::DicomImageInformation>  imageInformation_;
+
+  public:
+    Slice() :
+      type_(Type_Invalid)
+    {
+    }
+
+
+    // this constructor is used to reference, i.e, a slice that is being loaded
+    Slice(const std::string& orthancInstanceId,
+          unsigned int frame) :
+      type_(Type_Invalid),
+      orthancInstanceId_(orthancInstanceId),
+      frame_(frame)
+    {        
+    }
+
+    // TODO Is this constructor the best way to go to tackle missing
+    // layers within SliceViewerWidget?
+    Slice(const OrthancStone::CoordinateSystem3D& plane,
+          double thickness) :
+      type_(Type_Standalone),
+      frame_(0),
+      frameCount_(0),
+      geometry_(plane),
+      pixelSpacingX_(1),
+      pixelSpacingY_(1),
+      thickness_(thickness),
+      width_(0),
+      height_(0)
+    {      
+    }
+
+    Slice(const OrthancStone::CoordinateSystem3D& plane,
+          double pixelSpacingX,
+          double pixelSpacingY,
+          double thickness,
+          unsigned int width,
+          unsigned int height,
+          const DicomFrameConverter& converter) :
+      type_(Type_Standalone),
+      frameCount_(1),
+      geometry_(plane),
+      pixelSpacingX_(pixelSpacingX),
+      pixelSpacingY_(pixelSpacingY),
+      thickness_(thickness),
+      width_(width),
+      height_(height),
+      converter_(converter)
+    {
+    }
+
+    bool IsValid() const
+    {
+      return type_ != Type_Invalid;
+    } 
+
+    bool ParseOrthancFrame(const Orthanc::DicomMap& dataset,
+                           const std::string& instanceId,
+                           unsigned int frame);
+
+    bool HasOrthancDecoding() const
+    {
+      return type_ == Type_OrthancDecodableFrame;
+    }
+
+    const std::string GetOrthancInstanceId() const;
+
+    unsigned int GetFrame() const;
+
+    const OrthancStone::CoordinateSystem3D& GetGeometry() const;
+
+    double GetThickness() const;
+
+    double GetPixelSpacingX() const;
+
+    double GetPixelSpacingY() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    const DicomFrameConverter& GetConverter() const;
+
+    bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const;
+
+    void GetExtent(std::vector<OrthancStone::Vector>& points) const;
+
+    const Orthanc::DicomImageInformation& GetImageInformation() const;
+
+    Slice* Clone() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ViewportGeometry.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,217 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ViewportGeometry.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+namespace Deprecated
+{
+  void ViewportGeometry::ComputeTransform()
+  {
+    // The following lines must be read in reverse order!
+    cairo_matrix_t tmp;
+    
+    // Bring the center of the scene to the center of the view
+    cairo_matrix_init_translate(&transform_, 
+                                panX_ + static_cast<double>(width_) / 2.0, 
+                                panY_ + static_cast<double>(height_) / 2.0);
+
+    // Apply the zoom around (0,0)
+    cairo_matrix_init_scale(&tmp, zoom_, zoom_);
+    cairo_matrix_multiply(&transform_, &tmp, &transform_);
+
+    // Bring the center of the scene to (0,0)
+    cairo_matrix_init_translate(&tmp,
+                                -(sceneExtent_.GetX1() + sceneExtent_.GetX2()) / 2.0,
+                                -(sceneExtent_.GetY1() + sceneExtent_.GetY2()) / 2.0);
+    cairo_matrix_multiply(&transform_, &tmp, &transform_);
+  }
+
+
+  ViewportGeometry::ViewportGeometry()
+  {
+    width_ = 0;
+    height_ = 0;
+
+    zoom_ = 1;
+    panX_ = 0;
+    panY_ = 0;
+
+    ComputeTransform();
+  }
+
+
+  void ViewportGeometry::SetDisplaySize(unsigned int width,
+                                        unsigned int height)
+  {
+    if (width_ != width ||
+        height_ != height)
+    {
+      LOG(INFO) << "New display size: " << width << "x" << height;
+
+      width_ = width;
+      height_ = height;
+
+      ComputeTransform();
+    }
+  }
+
+
+  void ViewportGeometry::SetSceneExtent(const OrthancStone::Extent2D& extent)
+  {
+    LOG(INFO) << "New scene extent: ("
+              << extent.GetX1() << "," << extent.GetY1() << ") => ("
+              << extent.GetX2() << "," << extent.GetY2() << ")";
+
+    sceneExtent_ = extent;
+    ComputeTransform();
+  }
+
+
+  void ViewportGeometry::MapDisplayToScene(double& sceneX /* out */,
+                                           double& sceneY /* out */,
+                                           double x,
+                                           double y) const
+  {
+    cairo_matrix_t transform = transform_;
+
+    if (cairo_matrix_invert(&transform) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Cannot invert singular matrix";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    sceneX = x;
+    sceneY = y;
+    cairo_matrix_transform_point(&transform, &sceneX, &sceneY);
+  }
+
+
+  void ViewportGeometry::MapSceneToDisplay(int& displayX /* out */,
+                                           int& displayY /* out */,
+                                           double x,
+                                           double y) const
+  {
+    cairo_matrix_transform_point(&transform_, &x, &y);
+
+    displayX = static_cast<int>(boost::math::iround(x));
+    displayY = static_cast<int>(boost::math::iround(y));
+  }
+
+
+  void ViewportGeometry::MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
+                                               const std::vector<Touch>& displayTouches) const
+  {
+    double sceneX, sceneY;
+    sceneTouches.clear();
+    for (size_t t = 0; t < displayTouches.size(); t++)
+    {
+      MapPixelCenterToScene(
+        sceneX,
+        sceneY, 
+        static_cast<int>(displayTouches[t].x), 
+        static_cast<int>(displayTouches[t].y));
+      
+      sceneTouches.push_back(Touch((float)sceneX, (float)sceneY));
+    }
+  }
+
+  void ViewportGeometry::MapPixelCenterToScene(double& sceneX,
+                                               double& sceneY,
+                                               int x,
+                                               int y) const
+  {
+    // Take the center of the pixel
+    MapDisplayToScene(sceneX, sceneY,
+                      static_cast<double>(x) + 0.5,
+                      static_cast<double>(y) + 0.5);
+  }
+
+
+  void ViewportGeometry::FitContent()
+  {
+    if (width_ > 0 &&
+        height_ > 0 &&
+        !sceneExtent_.IsEmpty())
+    {
+      double zoomX = static_cast<double>(width_) / (sceneExtent_.GetX2() - sceneExtent_.GetX1());
+      double zoomY = static_cast<double>(height_) / (sceneExtent_.GetY2() - sceneExtent_.GetY1());
+      zoom_ = zoomX < zoomY ? zoomX : zoomY;
+
+      panX_ = 0;
+      panY_ = 0;
+
+      ComputeTransform();
+    }
+  }
+
+
+  void ViewportGeometry::ApplyTransform(OrthancStone::CairoContext& context) const
+  {
+    cairo_set_matrix(context.GetObject(), &transform_);
+  }
+
+
+  void ViewportGeometry::GetPan(double& x,
+                                double& y) const
+  {
+    x = panX_;
+    y = panY_;
+  }
+
+
+  void ViewportGeometry::SetPan(double x,
+                                double y)
+  {
+    panX_ = x;
+    panY_ = y;
+    ComputeTransform();
+  }
+
+
+  void ViewportGeometry::SetZoom(double zoom)
+  {
+    zoom_ = zoom;
+    ComputeTransform();
+  }
+
+
+  OrthancStone::Matrix ViewportGeometry::GetMatrix() const
+  {
+    OrthancStone::Matrix m(3, 3);
+
+    m(0, 0) = transform_.xx;
+    m(0, 1) = transform_.xy;
+    m(0, 2) = transform_.x0;
+    m(1, 0) = transform_.yx;
+    m(1, 1) = transform_.yy;
+    m(1, 2) = transform_.y0;
+    m(2, 0) = 0;
+    m(2, 1) = 0;
+    m(2, 2) = 1;
+    
+    return m;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/ViewportGeometry.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,110 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Wrappers/CairoContext.h"
+#include "../../Toolbox/Extent2D.h"
+#include "../../Toolbox/LinearAlgebra.h"
+#include "../Viewport/IMouseTracker.h"  // to include "Touch" definition
+
+namespace Deprecated
+{
+  class ViewportGeometry
+  {
+  private:
+    // Extent of the scene (in world units)
+    OrthancStone::Extent2D   sceneExtent_;
+
+    // Size of the display (in pixels)
+    unsigned int  width_;
+    unsigned int  height_;
+
+    // Zoom/pan
+    double   zoom_;
+    double   panX_;  // In pixels (display units)
+    double   panY_;
+
+    cairo_matrix_t  transform_;  // Scene-to-display transformation
+
+    void ComputeTransform();
+
+  public:
+    ViewportGeometry();
+
+    void SetDisplaySize(unsigned int width,
+                        unsigned int height);
+
+    void SetSceneExtent(const OrthancStone::Extent2D& extent);
+
+    const OrthancStone::Extent2D& GetSceneExtent() const
+    {
+      return sceneExtent_;
+    }
+
+    void MapDisplayToScene(double& sceneX /* out */,
+                           double& sceneY /* out */,
+                           double x,
+                           double y) const;
+
+    void MapPixelCenterToScene(double& sceneX /* out */,
+                               double& sceneY /* out */,
+                               int x,
+                               int y) const;
+
+    void MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
+                               const std::vector<Touch>& displayTouches) const;
+
+    void MapSceneToDisplay(int& displayX /* out */,
+                           int& displayY /* out */,
+                           double x,
+                           double y) const;
+
+    unsigned int GetDisplayWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetDisplayHeight() const
+    {
+      return height_;
+    }
+
+    double GetZoom() const
+    {
+      return zoom_;
+    }
+
+    void FitContent();
+
+    void ApplyTransform(OrthancStone::CairoContext& context) const;
+
+    void GetPan(double& x,
+                double& y) const;
+
+    void SetPan(double x,
+                double y);
+
+    void SetZoom(double zoom);
+
+    OrthancStone::Matrix GetMatrix() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/CairoFont.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CairoFont.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  CairoFont::CairoFont(const char* family,
+                       cairo_font_slant_t slant,
+                       cairo_font_weight_t weight)
+  {
+    font_ = cairo_toy_font_face_create(family, slant, weight);
+    if (font_ == NULL)
+    {
+      LOG(ERROR) << "Unknown font: " << family;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+
+  CairoFont::~CairoFont()
+  {
+    if (font_ != NULL)
+    {
+      cairo_font_face_destroy(font_);
+    }
+  }
+
+
+  void CairoFont::Draw(OrthancStone::CairoContext& context,
+                       const std::string& text,
+                       double size)
+  {
+    if (size <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    cairo_t* cr = context.GetObject();
+    cairo_set_font_face(cr, font_);
+    cairo_set_font_size(cr, size);
+    cairo_show_text(cr, text.c_str());    
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/CairoFont.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class CairoFont cannot be used in sandboxed environments
+#endif
+
+#include "../../Wrappers/CairoContext.h"
+
+namespace Deprecated
+{
+  class CairoFont : public boost::noncopyable
+  {
+  private:
+    cairo_font_face_t*  font_;
+
+  public:
+    CairoFont(const char* family,
+              cairo_font_slant_t slant,
+              cairo_font_weight_t weight);
+
+    ~CairoFont();
+
+    void Draw(OrthancStone::CairoContext& context,
+              const std::string& text,
+              double size);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/IMouseTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Wrappers/CairoSurface.h"
+#include <vector>
+
+namespace Deprecated
+{
+  struct Touch
+  {
+    float x;
+    float y;
+
+    Touch(float x, float y)
+    : x(x),
+      y(y)
+    {
+    }
+    Touch()
+      : x(0.0f),
+        y(0.0f)
+    {
+    }
+  };
+
+
+  // this is tracking a mouse in screen coordinates/pixels unlike
+  // the IWorldSceneMouseTracker that is tracking a mouse
+  // in scene coordinates/mm.
+  class IMouseTracker : public boost::noncopyable
+  {
+  public:
+    virtual ~IMouseTracker()
+    {
+    }
+    
+    virtual void Render(Orthanc::ImageAccessor& surface) = 0;
+
+    virtual void MouseUp() = 0;
+
+    // Returns "true" iff. the background scene must be repainted
+    virtual void MouseMove(int x, 
+                           int y,
+                           const std::vector<Touch>& displayTouches) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/IStatusBar.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,40 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Deprecated
+{
+  class IStatusBar : public boost::noncopyable
+  {
+  public:
+    virtual ~IStatusBar()
+    {
+    }
+    
+    virtual void ClearMessage() = 0;
+
+    virtual void SetMessage(const std::string& message) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/IViewport.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,95 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IStatusBar.h"
+#include "../../StoneEnumerations.h"
+#include "../../Messages/IObservable.h"
+
+#include <Core/Images/ImageAccessor.h>
+#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
+
+namespace Deprecated
+{
+  class IWidget;   // Forward declaration
+  
+  class IViewport : public OrthancStone::IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport);
+
+    IViewport(OrthancStone::MessageBroker& broker) :
+      IObservable(broker)
+    {
+    }
+    
+    virtual ~IViewport()
+    {
+    }
+
+    virtual void FitContent() = 0;
+
+    virtual void SetStatusBar(IStatusBar& statusBar) = 0;
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height) = 0;
+
+    // The function returns "true" iff. a new frame was rendered
+    virtual bool Render(Orthanc::ImageAccessor& surface) = 0;
+
+    virtual void MouseDown(OrthancStone::MouseButton button,
+                           int x,
+                           int y,
+                           OrthancStone::KeyboardModifiers modifiers,
+                           const std::vector<Touch>& touches) = 0;
+
+    virtual void MouseUp() = 0;
+
+    virtual void MouseMove(int x, 
+                           int y,
+                           const std::vector<Touch>& displayTouches) = 0;
+
+    virtual void MouseEnter() = 0;
+
+    virtual void MouseLeave() = 0;
+
+    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            OrthancStone::KeyboardModifiers modifiers) = 0;
+
+    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers) = 0;
+
+    virtual bool HasAnimation() = 0;
+
+    virtual void DoAnimation() = 0;
+
+    // Should only be called from IWidget
+    // TODO Why should this be virtual?
+    virtual void NotifyContentChanged()
+    {
+      BroadcastMessage(ViewportChangedMessage(*this));
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/WidgetViewport.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,289 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WidgetViewport.h"
+
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  WidgetViewport::WidgetViewport(OrthancStone::MessageBroker& broker) :
+    IViewport(broker),
+    statusBar_(NULL),
+    isMouseOver_(false),
+    lastMouseX_(0),
+    lastMouseY_(0),
+    backgroundChanged_(false)
+  {
+  }
+
+
+  void WidgetViewport::FitContent()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->FitContent();
+    }
+  }
+
+
+  void WidgetViewport::SetStatusBar(IStatusBar& statusBar)
+  {
+    statusBar_ = &statusBar;
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->SetStatusBar(statusBar);
+    }
+  }
+
+
+  IWidget& WidgetViewport::SetCentralWidget(IWidget* widget)
+  {
+    if (widget == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    mouseTracker_.reset(NULL);
+
+    centralWidget_.reset(widget);
+    centralWidget_->SetViewport(*this);
+
+    if (statusBar_ != NULL)
+    {
+      centralWidget_->SetStatusBar(*statusBar_);
+    }
+
+    NotifyBackgroundChanged();
+
+    return *widget;
+  }
+
+
+  void WidgetViewport::NotifyBackgroundChanged()
+  {
+    backgroundChanged_ = true;
+    NotifyContentChanged();
+  }
+
+
+  void WidgetViewport::SetSize(unsigned int width,
+                               unsigned int height)
+  {
+    background_.SetSize(width, height, false /* no alpha */);
+
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->SetSize(width, height);
+    }
+
+    NotifyBackgroundChanged();
+  }
+
+
+  bool WidgetViewport::Render(Orthanc::ImageAccessor& surface)
+  {
+    if (centralWidget_.get() == NULL)
+    {
+      return false;
+    }
+
+    Orthanc::ImageAccessor background;
+    background_.GetWriteableAccessor(background);
+
+    if (backgroundChanged_ &&
+        !centralWidget_->Render(background))
+    {
+      return false;
+    }
+
+    if (background.GetWidth() != surface.GetWidth() ||
+        background.GetHeight() != surface.GetHeight())
+    {
+      return false;
+    }
+
+    Orthanc::ImageProcessing::Convert(surface, background);
+    
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->Render(surface);
+    }
+    else if (isMouseOver_)
+    {
+      centralWidget_->RenderMouseOver(surface, lastMouseX_, lastMouseY_);
+    }
+
+    return true;
+  }
+
+  void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches)
+  {
+    MouseDown(OrthancStone::MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, OrthancStone::KeyboardModifiers_None, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
+  }
+      
+  void WidgetViewport::TouchMove(const std::vector<Touch>& displayTouches)
+  {
+    MouseMove((int)displayTouches[0].x, (int)displayTouches[0].y, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
+  }
+      
+  void WidgetViewport::TouchEnd(const std::vector<Touch>& displayTouches)
+  {
+    // note: TouchEnd is not triggered when a single touch gesture ends (it is only triggered when
+    // going from 2 touches to 1 touch, ...)
+    MouseUp();
+  }
+
+  void WidgetViewport::MouseDown(OrthancStone::MouseButton button,
+                                 int x,
+                                 int y,
+                                 OrthancStone::KeyboardModifiers modifiers,
+                                 const std::vector<Touch>& displayTouches
+                                 )
+  {
+    lastMouseX_ = x;
+    lastMouseY_ = y;
+
+    if (centralWidget_.get() != NULL)
+    {
+      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches));
+    }
+    else
+    {
+      mouseTracker_.reset(NULL);
+    }      
+
+    NotifyContentChanged();
+  }
+
+
+  void WidgetViewport::MouseUp()
+  {
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->MouseUp();
+      mouseTracker_.reset(NULL);
+      NotifyContentChanged();
+    }
+  }
+
+
+  void WidgetViewport::MouseMove(int x, 
+                                 int y,
+                                 const std::vector<Touch>& displayTouches)
+  {
+    if (centralWidget_.get() == NULL)
+    {
+      return;
+    }
+
+    lastMouseX_ = x;
+    lastMouseY_ = y;
+
+    bool repaint = false;
+    
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->MouseMove(x, y, displayTouches);
+      repaint = true;
+    }
+    else
+    {
+      repaint = centralWidget_->HasRenderMouseOver();
+    }
+
+    if (repaint)
+    {
+      // The scene must be repainted, notify the observers
+      NotifyContentChanged();
+    }
+  }
+
+
+  void WidgetViewport::MouseEnter()
+  {
+    isMouseOver_ = true;
+    NotifyContentChanged();
+  }
+
+
+  void WidgetViewport::MouseLeave()
+  {
+    isMouseOver_ = false;
+
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->MouseUp();
+      mouseTracker_.reset(NULL);
+    }
+
+    NotifyContentChanged();
+  }
+
+
+  void WidgetViewport::MouseWheel(OrthancStone::MouseWheelDirection direction,
+                                  int x,
+                                  int y,
+                                  OrthancStone::KeyboardModifiers modifiers)
+  {
+    if (centralWidget_.get() != NULL &&
+        mouseTracker_.get() == NULL)
+    {
+      centralWidget_->MouseWheel(direction, x, y, modifiers);
+    }
+  }
+
+
+  void WidgetViewport::KeyPressed(OrthancStone::KeyboardKeys key,
+                                  char keyChar,
+                                  OrthancStone::KeyboardModifiers modifiers)
+  {
+    if (centralWidget_.get() != NULL &&
+        mouseTracker_.get() == NULL)
+    {
+      centralWidget_->KeyPressed(key, keyChar, modifiers);
+    }
+  }
+
+
+  bool WidgetViewport::HasAnimation()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      return centralWidget_->HasAnimation();
+    }
+    else
+    {
+      return false;
+    }
+  }
+   
+
+  void WidgetViewport::DoAnimation()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->DoAnimation();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Viewport/WidgetViewport.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,94 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IViewport.h"
+#include "../Widgets/IWidget.h"
+
+#include <memory>
+
+namespace Deprecated
+{
+  class WidgetViewport : public IViewport
+  {
+  private:
+    std::auto_ptr<IWidget>        centralWidget_;
+    IStatusBar*                   statusBar_;
+    std::auto_ptr<IMouseTracker>  mouseTracker_;
+    bool                          isMouseOver_;
+    int                           lastMouseX_;
+    int                           lastMouseY_;
+    OrthancStone::CairoSurface    background_;
+    bool                          backgroundChanged_;
+
+  public:
+    WidgetViewport(OrthancStone::MessageBroker& broker);
+
+    virtual void FitContent();
+
+    virtual void SetStatusBar(IStatusBar& statusBar);
+
+    IWidget& SetCentralWidget(IWidget* widget);  // Takes ownership
+
+    virtual void NotifyBackgroundChanged();
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+
+    virtual void MouseDown(OrthancStone::MouseButton button,
+                           int x,
+                           int y,
+                           OrthancStone::KeyboardModifiers modifiers,
+                           const std::vector<Touch>& displayTouches);
+
+    virtual void MouseUp();
+
+    virtual void MouseMove(int x, 
+                           int y,
+                           const std::vector<Touch>& displayTouches);
+
+    virtual void MouseEnter();
+
+    virtual void MouseLeave();
+
+    virtual void TouchStart(const std::vector<Touch>& touches);
+    
+    virtual void TouchMove(const std::vector<Touch>& touches);
+    
+    virtual void TouchEnd(const std::vector<Touch>& touches);
+
+    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            OrthancStone::KeyboardModifiers modifiers);
+
+    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers);
+
+    virtual bool HasAnimation();
+
+    virtual void DoAnimation();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Volumes/ISlicedVolume.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,77 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Messages/IObservable.h"
+#include "../Toolbox/Slice.h"
+
+namespace Deprecated
+{
+  class ISlicedVolume : public OrthancStone::IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, ISlicedVolume);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, ISlicedVolume);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, ISlicedVolume);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeReadyMessage, ISlicedVolume);
+
+
+    class SliceContentChangedMessage : public OrthancStone::OriginMessage<ISlicedVolume>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      size_t        sliceIndex_;
+      const Slice&  slice_;
+      
+    public:
+      SliceContentChangedMessage(ISlicedVolume& origin,
+                                 size_t sliceIndex,
+                                 const Slice& slice) :
+        OriginMessage(origin),
+        sliceIndex_(sliceIndex),
+        slice_(slice)
+      {
+      }
+
+      size_t GetSliceIndex() const
+      {
+        return sliceIndex_;
+      }
+
+      const Slice& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+
+
+    ISlicedVolume(OrthancStone::MessageBroker& broker) :
+      IObservable(broker)
+    {
+    }
+    
+    virtual size_t GetSliceCount() const = 0;
+
+    virtual const Slice& GetSlice(size_t slice) const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Volumes/IVolumeLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,40 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Messages/IObservable.h"
+
+namespace Deprecated
+{
+  class IVolumeLoader : public OrthancStone::IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeLoader);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeLoader);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeLoader);
+
+    IVolumeLoader(OrthancStone::MessageBroker& broker) :
+      IObservable(broker)
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,159 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "StructureSetLoader.h"
+
+#include "../Toolbox/MessagingToolbox.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  StructureSetLoader::StructureSetLoader(OrthancStone::MessageBroker& broker,
+                                         OrthancApiClient& orthanc) :
+    IVolumeLoader(broker),
+    IObserver(broker),
+    orthanc_(orthanc)
+  {
+  }
+  
+
+  void StructureSetLoader::OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    OrthancPlugins::FullOrthancDataset dataset(message.GetJson());
+
+    Orthanc::DicomMap slice;
+    MessagingToolbox::ConvertDataset(slice, dataset);
+    structureSet_->AddReferencedSlice(slice);
+
+    BroadcastMessage(ContentChangedMessage(*this));
+  }
+  
+
+  void StructureSetLoader::OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    OrthancPlugins::FullOrthancDataset dataset(message.GetJson());
+    structureSet_.reset(new OrthancStone::DicomStructureSet(dataset));
+
+    std::set<std::string> instances;
+    structureSet_->GetReferencedInstances(instances);
+
+    for (std::set<std::string>::const_iterator it = instances.begin();
+         it != instances.end(); ++it)
+    {
+      orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it,
+                                         new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted));
+    }
+
+    BroadcastMessage(GeometryReadyMessage(*this));
+  }
+
+  
+  void StructureSetLoader::OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    const Json::Value& lookup = message.GetJson();
+
+    if (lookup.type() != Json::arrayValue ||
+        lookup.size() != 1 ||
+        !lookup[0].isMember("Type") ||
+        !lookup[0].isMember("Path") ||
+        lookup[0]["Type"].type() != Json::stringValue ||
+        lookup[0]["ID"].type() != Json::stringValue ||
+        lookup[0]["Type"].asString() != "Instance")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    const std::string& instance = lookup[0]["ID"].asString();
+    orthanc_.GetJsonAsync("/instances/" + instance + "/tags",
+                          new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded));
+  }
+
+  
+  void StructureSetLoader::ScheduleLoadInstance(const std::string& instance)
+  {
+    if (structureSet_.get() != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050",
+                            new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded));
+    }
+  }
+
+
+  OrthancStone::DicomStructureSet& StructureSetLoader::GetStructureSet()
+  {
+    if (structureSet_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *structureSet_;
+    }
+  }
+
+
+  OrthancStone::DicomStructureSet* StructureSetLoader::SynchronousLoad(
+    OrthancPlugins::IOrthancConnection& orthanc,
+    const std::string& instanceId)
+  {
+    const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
+    OrthancPlugins::FullOrthancDataset dataset(orthanc, uri);
+
+    std::auto_ptr<OrthancStone::DicomStructureSet> result
+      (new OrthancStone::DicomStructureSet(dataset));
+
+    std::set<std::string> instances;
+    result->GetReferencedInstances(instances);
+
+    for (std::set<std::string>::const_iterator it = instances.begin();
+         it != instances.end(); ++it)
+    {
+      Json::Value lookup;
+      MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it);
+
+      if (lookup.type() != Json::arrayValue ||
+          lookup.size() != 1 ||
+          !lookup[0].isMember("Type") ||
+          !lookup[0].isMember("Path") ||
+          lookup[0]["Type"].type() != Json::stringValue ||
+          lookup[0]["ID"].type() != Json::stringValue ||
+          lookup[0]["Type"].asString() != "Instance")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
+      }
+
+      OrthancPlugins::FullOrthancDataset slice
+        (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags");
+      Orthanc::DicomMap m;
+      MessagingToolbox::ConvertDataset(m, slice);
+      result->AddReferencedSlice(m);
+    }
+
+    result->CheckReferencedSlices();
+
+    return result.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Volumes/StructureSetLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,61 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Toolbox/DicomStructureSet.h"
+#include "../Toolbox/OrthancApiClient.h"
+#include "IVolumeLoader.h"
+
+namespace Deprecated
+{
+  class StructureSetLoader :
+    public IVolumeLoader,
+    public OrthancStone::IObserver
+  {
+  private:
+    OrthancApiClient&                 orthanc_;
+    std::auto_ptr<OrthancStone::DicomStructureSet>  structureSet_;
+
+    void OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+  public:
+    StructureSetLoader(OrthancStone::MessageBroker& broker,
+                       OrthancApiClient& orthanc);
+
+    void ScheduleLoadInstance(const std::string& instance);
+
+    bool HasStructureSet() const
+    {
+      return structureSet_.get() != NULL;
+    }
+
+    OrthancStone::DicomStructureSet& GetStructureSet();
+
+    static OrthancStone::DicomStructureSet* SynchronousLoad(
+      OrthancPlugins::IOrthancConnection& orthanc,
+      const std::string& instanceId);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/CairoWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,101 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CairoWidget.h"
+
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  static bool IsAligned(const Orthanc::ImageAccessor& target)
+  {
+    // TODO
+    return true;
+  }
+
+  CairoWidget::CairoWidget(const std::string& name) :
+    WidgetBase(name)
+  {
+  }
+
+  void CairoWidget::SetSize(unsigned int width,
+                            unsigned int height)
+  {
+    surface_.SetSize(width, height, false /* no alpha */);
+  }
+  
+
+  bool CairoWidget::Render(Orthanc::ImageAccessor& target)
+  {
+    // Don't call the base class here, as
+    // "ClearBackgroundCairo()" is a faster alternative
+
+    if (IsAligned(target))
+    {
+      OrthancStone::CairoSurface surface(target, false /* no alpha */);
+      OrthancStone::CairoContext context(surface);
+      ClearBackgroundCairo(context);
+      return RenderCairo(context);
+    }
+    else
+    {
+      OrthancStone::CairoContext context(surface_);
+      ClearBackgroundCairo(context);
+
+      if (RenderCairo(context))
+      {
+        Orthanc::ImageAccessor surface;
+        surface_.GetReadOnlyAccessor(surface);
+        Orthanc::ImageProcessing::Copy(target, surface);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+  void CairoWidget::RenderMouseOver(Orthanc::ImageAccessor& target,
+                                    int x,
+                                    int y)
+  {
+    if (IsAligned(target))
+    {
+      OrthancStone::CairoSurface surface(target, false /* no alpha */);
+      OrthancStone::CairoContext context(surface);
+      RenderMouseOverCairo(context, x, y);
+    }
+    else
+    {
+      Orthanc::ImageAccessor accessor;
+      surface_.GetWriteableAccessor(accessor);
+      Orthanc::ImageProcessing::Copy(accessor, target);
+
+      OrthancStone::CairoContext context(surface_);
+      RenderMouseOverCairo(context, x, y);
+
+      Orthanc::ImageProcessing::Copy(target, accessor);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/CairoWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WidgetBase.h"
+
+namespace Deprecated
+{
+  class CairoWidget : public WidgetBase
+  {
+  private:
+    OrthancStone::CairoSurface   surface_;
+
+  protected:
+    virtual bool RenderCairo(OrthancStone::CairoContext& context) = 0;
+    
+    virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context,
+                                      int x,
+                                      int y) = 0;
+    
+  public:
+    CairoWidget(const std::string& name);
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual bool Render(Orthanc::ImageAccessor& target);
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/EmptyWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,41 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "EmptyWidget.h"
+
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
+  {
+    // Note: This call is slow
+    Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
+    return true;
+  }
+
+
+  void EmptyWidget::DoAnimation()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/EmptyWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,116 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWidget.h"
+
+namespace Deprecated
+{
+  /**
+   * This is a test widget that simply fills its surface with an
+   * uniform color.
+   **/
+  class EmptyWidget : public IWidget
+  {
+  private:
+    uint8_t  red_;
+    uint8_t  green_;
+    uint8_t  blue_;
+
+  public:
+    EmptyWidget(uint8_t red,
+                uint8_t green,
+                uint8_t blue) :
+      red_(red),
+      green_(green),
+      blue_(blue)
+    {
+    }
+
+    virtual void FitContent()
+    {
+    }
+
+    virtual void SetParent(IWidget& widget)
+    {
+    }
+
+    virtual void SetViewport(WidgetViewport& viewport)
+    {
+    }
+
+    virtual void NotifyContentChanged()
+    {
+    }
+
+    virtual void SetStatusBar(IStatusBar& statusBar)
+    {
+    }
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height)
+    {
+    }
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+
+    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
+                                              int x,
+                                              int y,
+                                              OrthancStone::KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches)
+    {
+      return NULL;
+    }
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y)
+    {
+    }
+
+    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            OrthancStone::KeyboardModifiers modifiers)
+    {
+    }
+
+    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers)
+    {
+    }
+
+    virtual bool HasAnimation() const
+    {
+      return false;
+    }
+
+    virtual void DoAnimation();
+
+    virtual bool HasRenderMouseOver()
+    {
+      return false;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/IWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,81 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../StoneEnumerations.h"
+#include "../Viewport/IMouseTracker.h"
+#include "../Viewport/IStatusBar.h"
+
+namespace Deprecated
+{
+  class WidgetViewport;  // Forward declaration
+  
+  class IWidget : public boost::noncopyable
+  {
+  public:
+    virtual ~IWidget()
+    {
+    }
+
+    virtual void FitContent() = 0;
+
+    virtual void SetParent(IWidget& parent) = 0;
+    
+    virtual void SetViewport(WidgetViewport& viewport) = 0;
+
+    virtual void SetStatusBar(IStatusBar& statusBar) = 0;
+
+    virtual void SetSize(unsigned int width, 
+                         unsigned int height) = 0;
+ 
+    virtual bool Render(Orthanc::ImageAccessor& surface) = 0;
+
+    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
+                                              int x,
+                                              int y,
+                                              OrthancStone::KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches) = 0;
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y) = 0;
+
+    virtual bool HasRenderMouseOver() = 0;
+
+    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            OrthancStone::KeyboardModifiers modifiers) = 0;
+
+    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers) = 0;
+
+    virtual bool HasAnimation() const = 0;
+
+    virtual void DoAnimation() = 0;
+
+    // Subclasses can call this method to signal the display of the
+    // widget must be refreshed
+    virtual void NotifyContentChanged() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/IWorldSceneInteractor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWorldSceneMouseTracker.h"
+
+#include "../Toolbox/ViewportGeometry.h"
+#include "../../StoneEnumerations.h"
+#include "../Viewport/IStatusBar.h"
+
+namespace Deprecated
+{
+    class WorldSceneWidget;
+
+    class IWorldSceneInteractor : public boost::noncopyable
+    {
+    public:
+        virtual ~IWorldSceneInteractor()
+        {
+        }
+
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            OrthancStone::MouseButton button,
+                                                            OrthancStone::KeyboardModifiers modifiers,
+                                                            int viewportX,
+                                                            int viewportY,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& touches) = 0;
+
+        virtual void MouseOver(OrthancStone::CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar) = 0;
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+                                OrthancStone::MouseWheelDirection direction,
+                                OrthancStone::KeyboardModifiers modifiers,
+                                IStatusBar* statusBar) = 0;
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                OrthancStone::KeyboardKeys key,
+                                char keyChar,
+                                OrthancStone::KeyboardModifiers modifiers,
+                                IStatusBar* statusBar) = 0;
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/IWorldSceneMouseTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,54 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Wrappers/CairoContext.h"
+#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
+
+namespace Deprecated
+{
+
+  // this is tracking a mouse in scene coordinates/mm unlike
+  // the IMouseTracker that is tracking a mouse
+  // in screen coordinates/pixels.
+  class IWorldSceneMouseTracker : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorldSceneMouseTracker()
+    {
+    }
+
+    virtual bool HasRender() const = 0;
+
+    virtual void Render(OrthancStone::CairoContext& context,
+                        double zoom) = 0;
+
+    virtual void MouseUp() = 0;
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double sceneX,
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/LayoutWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,503 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LayoutWidget.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+namespace Deprecated
+{
+  class LayoutWidget::LayoutMouseTracker : public IMouseTracker
+  {
+  private:
+    std::auto_ptr<IMouseTracker>   tracker_;
+    int                            left_;
+    int                            top_;
+    unsigned int                   width_;
+    unsigned int                   height_;
+
+  public:
+    LayoutMouseTracker(IMouseTracker* tracker,
+                       int left,
+                       int top,
+                       unsigned int width,
+                       unsigned int height) :
+      tracker_(tracker),
+      left_(left),
+      top_(top),
+      width_(width),
+      height_(height)
+    {
+      if (tracker == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& surface)
+    {
+      Orthanc::ImageAccessor accessor;
+      surface.GetRegion(accessor, left_, top_, width_, height_);
+      tracker_->Render(accessor);
+    }
+
+    virtual void MouseUp()
+    {
+      tracker_->MouseUp();
+    }
+
+    virtual void MouseMove(int x, 
+                           int y,
+                           const std::vector<Touch>& displayTouches)
+    {
+      std::vector<Touch> relativeTouches;
+      for (size_t t = 0; t < displayTouches.size(); t++)
+      {
+        relativeTouches.push_back(Touch(displayTouches[t].x - left_, displayTouches[t].y - top_));
+      }
+
+      tracker_->MouseMove(x - left_, y - top_, relativeTouches);
+    }
+  };
+
+
+  class LayoutWidget::ChildWidget : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<IWidget>  widget_;
+    int                     left_;
+    int                     top_;
+    unsigned int            width_;
+    unsigned int            height_;
+
+  public:
+    ChildWidget(IWidget* widget) :
+      widget_(widget)
+    {
+      assert(widget != NULL);
+      SetEmpty();
+    }
+
+    void DoAnimation()
+    {
+      if (widget_->HasAnimation())
+      {
+        widget_->DoAnimation();
+      }
+    }
+
+    IWidget& GetWidget() const
+    {
+      return *widget_;
+    }
+
+    void SetRectangle(unsigned int left, 
+                      unsigned int top,
+                      unsigned int width,
+                      unsigned int height)
+    {
+      left_ = left;
+      top_ = top;
+      width_ = width;
+      height_ = height;
+
+      widget_->SetSize(width, height);
+    }
+
+    void SetEmpty()
+    {
+      SetRectangle(0, 0, 0, 0);
+    }
+
+    bool Contains(int x, 
+                  int y) const
+    {
+      return (x >= left_ && 
+              y >= top_ &&
+              x < left_ + static_cast<int>(width_) &&
+              y < top_ + static_cast<int>(height_));
+    }      
+
+    bool Render(Orthanc::ImageAccessor& target)
+    {
+      if (width_ == 0 ||
+          height_ == 0)
+      {
+        return true;
+      }
+      else 
+      {
+        Orthanc::ImageAccessor accessor;
+        target.GetRegion(accessor, left_, top_, width_, height_);
+        return widget_->Render(accessor);
+      }
+    }
+
+    IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
+                                      int x,
+                                      int y,
+                                      OrthancStone::KeyboardModifiers modifiers,
+                                      const std::vector<Touch>& touches)
+    {
+      if (Contains(x, y))
+      {
+        IMouseTracker* tracker = widget_->CreateMouseTracker(button, 
+                                                             x - left_, 
+                                                             y - top_, 
+                                                             modifiers,
+                                                             touches);
+        if (tracker)
+        {
+          return new LayoutMouseTracker(tracker, left_, top_, width_, height_);
+        }
+      }
+
+      return NULL;
+    }
+
+    void RenderMouseOver(Orthanc::ImageAccessor& target,
+                         int x,
+                         int y)
+    {
+      if (Contains(x, y))
+      {
+        Orthanc::ImageAccessor accessor;
+        target.GetRegion(accessor, left_, top_, width_, height_);
+
+        widget_->RenderMouseOver(accessor, x - left_, y - top_);
+      }
+    }
+
+    void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                    int x,
+                    int y,
+                    OrthancStone::KeyboardModifiers modifiers)
+    {
+      if (Contains(x, y))
+      {
+        widget_->MouseWheel(direction, x - left_, y - top_, modifiers);
+      }
+    }
+    
+    bool HasRenderMouseOver()
+    {
+      return widget_->HasRenderMouseOver();
+    }
+  };
+
+
+  void LayoutWidget::ComputeChildrenExtents()
+  {
+    if (children_.size() == 0)
+    {
+      return;
+    }
+
+    float internal = static_cast<float>(paddingInternal_);
+
+    if (width_ <= paddingLeft_ + paddingRight_ ||
+        height_ <= paddingTop_ + paddingBottom_)
+    {
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        children_[i]->SetEmpty();          
+      }
+    }
+    else if (isHorizontal_)
+    {
+      unsigned int padding = paddingLeft_ + paddingRight_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_;
+      float childWidth = ((static_cast<float>(width_) - static_cast<float>(padding)) / 
+                          static_cast<float>(children_.size()));
+        
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        float left = static_cast<float>(paddingLeft_) + static_cast<float>(i) * (childWidth + internal);
+        float right = left + childWidth;
+
+        if (left >= right)
+        {
+          children_[i]->SetEmpty();
+        }
+        else
+        {
+          children_[i]->SetRectangle(static_cast<unsigned int>(left), 
+                                     paddingTop_, 
+                                     boost::math::iround(right - left),
+                                     height_ - paddingTop_ - paddingBottom_);
+        }
+      }
+    }
+    else
+    {
+      unsigned int padding = paddingTop_ + paddingBottom_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_;
+      float childHeight = ((static_cast<float>(height_) - static_cast<float>(padding)) / 
+                           static_cast<float>(children_.size()));
+        
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        float top = static_cast<float>(paddingTop_) + static_cast<float>(i) * (childHeight + internal);
+        float bottom = top + childHeight;
+
+        if (top >= bottom)
+        {
+          children_[i]->SetEmpty();
+        }
+        else
+        {
+          children_[i]->SetRectangle(paddingTop_, 
+                                     static_cast<unsigned int>(top), 
+                                     width_ - paddingLeft_ - paddingRight_,
+                                     boost::math::iround(bottom - top));
+        }
+      }
+    }
+
+    NotifyContentChanged(*this);
+  }
+
+
+  LayoutWidget::LayoutWidget(const std::string& name) :
+    WidgetBase(name),
+    isHorizontal_(true),
+    width_(0),
+    height_(0),
+    paddingLeft_(0),
+    paddingTop_(0),
+    paddingRight_(0),
+    paddingBottom_(0),
+    paddingInternal_(0)
+  {
+  }
+
+
+  LayoutWidget::~LayoutWidget()
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      delete children_[i];
+    }
+  }
+
+
+  void LayoutWidget::FitContent()
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().FitContent();
+    }
+  }
+  
+
+  void LayoutWidget::NotifyContentChanged(const IWidget& widget)
+  {
+    // One of the children has changed
+    WidgetBase::NotifyContentChanged();
+  }
+
+
+  void LayoutWidget::SetHorizontal()
+  {
+    isHorizontal_ = true;
+    ComputeChildrenExtents();
+  }
+
+
+  void LayoutWidget::SetVertical()
+  {
+    isHorizontal_ = false;
+    ComputeChildrenExtents();
+  }
+
+
+  void LayoutWidget::SetPadding(unsigned int left,
+                                unsigned int top,
+                                unsigned int right,
+                                unsigned int bottom,
+                                unsigned int spacing)
+  {
+    paddingLeft_ = left;
+    paddingTop_ = top;
+    paddingRight_ = right;
+    paddingBottom_ = bottom;
+    paddingInternal_ = spacing;
+  }
+    
+
+  void LayoutWidget::SetPadding(unsigned int padding)
+  {
+    paddingLeft_ = padding;
+    paddingTop_ = padding;
+    paddingRight_ = padding;
+    paddingBottom_ = padding;
+    paddingInternal_ = padding;
+  }
+
+
+  IWidget& LayoutWidget::AddWidget(IWidget* widget)  // Takes ownership
+  {
+    if (widget == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (GetStatusBar() != NULL)
+    {
+      widget->SetStatusBar(*GetStatusBar());
+    }
+
+    children_.push_back(new ChildWidget(widget));
+    widget->SetParent(*this);
+
+    ComputeChildrenExtents();
+
+    if (widget->HasAnimation())
+    {
+      hasAnimation_ = true;
+    }
+
+    return *widget;
+  }
+
+
+  void LayoutWidget::SetStatusBar(IStatusBar& statusBar)
+  {
+    WidgetBase::SetStatusBar(statusBar);
+
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().SetStatusBar(statusBar);
+    }
+  }
+
+
+  void LayoutWidget::SetSize(unsigned int width,
+                             unsigned int height)
+  {
+    width_ = width;
+    height_ = height;
+    ComputeChildrenExtents();
+  }
+
+
+  bool LayoutWidget::Render(Orthanc::ImageAccessor& surface)
+  {
+    if (!WidgetBase::Render(surface))
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      if (!children_[i]->Render(surface))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+    
+  IMouseTracker* LayoutWidget::CreateMouseTracker(OrthancStone::MouseButton button,
+                                                  int x,
+                                                  int y,
+                                                  OrthancStone::KeyboardModifiers modifiers,
+                                                  const std::vector<Touch>& touches)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers, touches);
+      if (tracker != NULL)
+      {
+        return tracker;
+      }
+    }
+
+    return NULL;
+  }
+
+
+  void LayoutWidget::RenderMouseOver(Orthanc::ImageAccessor& target,
+                                     int x,
+                                     int y)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->RenderMouseOver(target, x, y);
+    }
+  }
+
+
+  void LayoutWidget::MouseWheel(OrthancStone::MouseWheelDirection direction,
+                                int x,
+                                int y,
+                                OrthancStone::KeyboardModifiers modifiers)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->MouseWheel(direction, x, y, modifiers);
+    }
+  }
+
+
+  void LayoutWidget::KeyPressed(OrthancStone::KeyboardKeys key,
+                                char keyChar,
+                                OrthancStone::KeyboardModifiers modifiers)
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().KeyPressed(key, keyChar, modifiers);
+    }
+  }
+
+  
+  void LayoutWidget::DoAnimation()
+  {
+    if (hasAnimation_)
+    {
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        children_[i]->DoAnimation();
+      }
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  bool LayoutWidget::HasRenderMouseOver()
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      if (children_[i]->HasRenderMouseOver())
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/LayoutWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,134 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WidgetBase.h"
+
+#include <vector>
+#include <memory>
+
+namespace Deprecated
+{
+  class LayoutWidget : public WidgetBase
+  {
+  private:
+    class LayoutMouseTracker;
+    class ChildWidget;
+
+    std::vector<ChildWidget*>     children_;
+    bool                          isHorizontal_;
+    unsigned int                  width_;
+    unsigned int                  height_;
+    std::auto_ptr<IMouseTracker>  mouseTracker_;
+    unsigned int                  paddingLeft_;
+    unsigned int                  paddingTop_;
+    unsigned int                  paddingRight_;
+    unsigned int                  paddingBottom_;
+    unsigned int                  paddingInternal_;
+    bool                          hasAnimation_;
+
+    void ComputeChildrenExtents();
+
+  public:
+    LayoutWidget(const std::string& name);
+
+    virtual ~LayoutWidget();
+
+    virtual void FitContent();
+
+    virtual void NotifyContentChanged(const IWidget& widget);
+
+    void SetHorizontal();
+
+    void SetVertical();
+
+    void SetPadding(unsigned int left,
+                    unsigned int top,
+                    unsigned int right,
+                    unsigned int bottom,
+                    unsigned int spacing);
+    
+    void SetPadding(unsigned int padding);
+
+    unsigned int GetPaddingLeft() const
+    {
+      return paddingLeft_;
+    }
+
+    unsigned int GetPaddingTop() const
+    {
+      return paddingTop_;
+    }
+
+    unsigned int GetPaddingRight() const
+    {
+      return paddingRight_;
+    }
+
+    unsigned int GetPaddingBottom() const
+    {
+      return paddingBottom_;
+    }
+
+    unsigned int GetPaddingInternal() const
+    {
+      return paddingInternal_;
+    }
+
+    IWidget& AddWidget(IWidget* widget);  // Takes ownership
+
+    virtual void SetStatusBar(IStatusBar& statusBar);
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+    
+    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
+                                              int x,
+                                              int y,
+                                              OrthancStone::KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches);
+
+    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                 int x,
+                                 int y);
+
+    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            OrthancStone::KeyboardModifiers modifiers);
+
+    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers);
+
+    virtual bool HasAnimation() const
+    {
+      return hasAnimation_;
+    }
+
+    virtual void DoAnimation();
+
+    virtual bool HasRenderMouseOver();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/PanMouseTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,58 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PanMouseTracker.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  PanMouseTracker::PanMouseTracker(WorldSceneWidget& that,
+                                   int x,
+                                   int y) :
+    that_(that)
+  {
+    that.GetView().GetPan(originalPanX_, originalPanY_);
+    that.GetView().MapPixelCenterToScene(downX_, downY_, x, y);
+  }
+    
+
+  void PanMouseTracker::Render(OrthancStone::CairoContext& context,
+                               double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void PanMouseTracker::MouseMove(int displayX,
+                                  int displayY,
+                                  double x,
+                                  double y,
+                                  const std::vector<Touch>& displayTouches,
+                                  const std::vector<Touch>& sceneTouches)
+  {
+    ViewportGeometry view = that_.GetView();
+    view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(),
+                originalPanY_ + (y - downY_) * view.GetZoom());
+    that_.SetView(view);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/PanMouseTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,61 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+
+namespace Deprecated
+{
+  class PanMouseTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;
+    double             originalPanX_;
+    double             originalPanY_;
+    double             downX_;
+    double             downY_;
+    
+  public:
+    PanMouseTracker(WorldSceneWidget& that,
+                    int x,
+                    int y);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void MouseUp()
+    {
+    }
+
+    virtual void Render(OrthancStone::CairoContext& context,
+                        double zoom);
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/PanZoomMouseTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,137 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PanZoomMouseTracker.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <math.h>
+
+namespace Deprecated
+{
+  Touch GetCenter(const std::vector<Touch>& touches)
+  {
+    return Touch((touches[0].x + touches[1].x) / 2.0f, (touches[0].y + touches[1].y) / 2.0f);
+  }
+
+  double GetDistance(const std::vector<Touch>& touches)
+  {
+    float dx = touches[0].x - touches[1].x;
+    float dy = touches[0].y - touches[1].y;
+    return sqrt((double)(dx * dx) + (double)(dy * dy));
+  }
+
+
+  PanZoomMouseTracker::PanZoomMouseTracker(WorldSceneWidget& that,
+                                           const std::vector<Touch>& startTouches)
+    : that_(that),
+      originalZoom_(that.GetView().GetZoom())
+  {
+    that.GetView().GetPan(originalPanX_, originalPanY_);
+    that.GetView().MapPixelCenterToScene(originalSceneTouches_, startTouches);
+
+    originalDisplayCenter_ = GetCenter(startTouches);
+    originalSceneCenter_ = GetCenter(originalSceneTouches_);
+    originalDisplayDistanceBetweenTouches_ = GetDistance(startTouches);
+
+//    printf("original Pan %f %f\n", originalPanX_, originalPanY_);
+//    printf("original Zoom %f \n", originalZoom_);
+//    printf("original distance %f \n", (float)originalDisplayDistanceBetweenTouches_);
+//    printf("original display touches 0 %f %f\n", startTouches[0].x, startTouches[0].y);
+//    printf("original display touches 1 %f %f\n", startTouches[1].x, startTouches[1].y);
+//    printf("original Scene center %f %f\n", originalSceneCenter_.x, originalSceneCenter_.y);
+
+    unsigned int height = that.GetView().GetDisplayHeight();
+
+    if (height <= 3)
+    {
+      idle_ = true;
+      LOG(WARNING) << "image is too small to zoom (current height = " << height << ")";
+    }
+    else
+    {
+      idle_ = false;
+      normalization_ = 1.0 / static_cast<double>(height - 1);
+    }
+
+  }
+
+
+  void PanZoomMouseTracker::Render(OrthancStone::CairoContext& context,
+                                   double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void PanZoomMouseTracker::MouseMove(int displayX,
+                                      int displayY,
+                                      double x,
+                                      double y,
+                                      const std::vector<Touch>& displayTouches,
+                                      const std::vector<Touch>& sceneTouches)
+  {
+    ViewportGeometry view = that_.GetView();
+
+//    printf("Display touches 0 %f %f\n", displayTouches[0].x, displayTouches[0].y);
+//    printf("Display touches 1 %f %f\n", displayTouches[1].x, displayTouches[1].y);
+//    printf("Scene touches 0 %f %f\n", sceneTouches[0].x, sceneTouches[0].y);
+//    printf("Scene touches 1 %f %f\n", sceneTouches[1].x, sceneTouches[1].y);
+
+//    printf("zoom = %f\n", view.GetZoom());
+    Touch currentSceneCenter = GetCenter(sceneTouches);
+    double panX = originalPanX_ + (currentSceneCenter.x - originalSceneCenter_.x) * view.GetZoom();
+    double panY = originalPanY_ + (currentSceneCenter.y - originalSceneCenter_.y) * view.GetZoom();
+
+    view.SetPan(panX, panY);
+
+    static const double MIN_ZOOM = -4;
+    static const double MAX_ZOOM = 4;
+
+    if (!idle_)
+    {
+      double currentDistanceBetweenTouches = GetDistance(displayTouches);
+
+      double dy = static_cast<double>(currentDistanceBetweenTouches - originalDisplayDistanceBetweenTouches_) * normalization_;  // In the range [-1,1]
+      double z;
+
+      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+      if (dy < -1.0)
+      {
+        z = MIN_ZOOM;
+      }
+      else if (dy > 1.0)
+      {
+        z = MAX_ZOOM;
+      }
+      else
+      {
+        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
+      }
+
+      z = pow(2.0, z);
+
+      view.SetZoom(z * originalZoom_);
+    }
+
+    that_.SetView(view);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/PanZoomMouseTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+
+namespace Deprecated
+{
+  class PanZoomMouseTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;
+    std::vector<Touch> originalSceneTouches_;
+    Touch              originalSceneCenter_;
+    Touch              originalDisplayCenter_;
+    double             originalPanX_;
+    double             originalPanY_;
+    double             originalZoom_;
+    double             originalDisplayDistanceBetweenTouches_;
+    bool               idle_;
+    double             normalization_;
+
+  public:
+    PanZoomMouseTracker(WorldSceneWidget& that,
+                        const std::vector<Touch>& startTouches);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void MouseUp()
+    {
+    }
+
+    virtual void Render(OrthancStone::CairoContext& context,
+                        double zoom);
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/SliceViewerWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,654 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SliceViewerWidget.h"
+
+#include "../Layers/SliceOutlineRenderer.h"
+#include "../../Toolbox/GeometryToolbox.h"
+#include "../Layers/FrameRenderer.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/constants/constants.hpp>
+
+
+static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon();
+
+namespace Deprecated
+{
+  class SliceViewerWidget::Scene : public boost::noncopyable
+  {
+  private:
+    OrthancStone::CoordinateSystem3D            plane_;
+    double                        thickness_;
+    size_t                        countMissing_;
+    std::vector<ILayerRenderer*>  renderers_;
+
+  public:
+    void DeleteLayer(size_t index)
+    {
+      if (index >= renderers_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      assert(countMissing_ <= renderers_.size());
+
+      if (renderers_[index] != NULL)
+      {
+        assert(countMissing_ < renderers_.size());
+        delete renderers_[index];
+        renderers_[index] = NULL;
+        countMissing_++;
+      }
+    }
+
+    Scene(const OrthancStone::CoordinateSystem3D& plane,
+          double thickness,
+          size_t countLayers) :
+      plane_(plane),
+      thickness_(thickness),
+      countMissing_(countLayers),
+      renderers_(countLayers, NULL)
+    {
+      if (thickness <= 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    ~Scene()
+    {
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        DeleteLayer(i);
+      }
+    }
+
+    void SetLayer(size_t index,
+                  ILayerRenderer* renderer)  // Takes ownership
+    {
+      if (renderer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+      DeleteLayer(index);
+
+      renderers_[index] = renderer;
+      countMissing_--;
+    }
+
+    const OrthancStone::CoordinateSystem3D& GetPlane() const
+    {
+      return plane_;
+    }
+
+    bool HasRenderer(size_t index)
+    {
+      return renderers_[index] != NULL;
+    }
+
+    bool IsComplete() const
+    {
+      return countMissing_ == 0;
+    }
+
+    unsigned int GetCountMissing() const
+    {
+      return static_cast<unsigned int>(countMissing_);
+    }
+
+    bool RenderScene(OrthancStone::CairoContext& context,
+                     const ViewportGeometry& view,
+                     const OrthancStone::CoordinateSystem3D& viewportPlane)
+    {
+      bool fullQuality = true;
+      cairo_t *cr = context.GetObject();
+
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        if (renderers_[i] != NULL)
+        {
+          const OrthancStone::CoordinateSystem3D& framePlane = renderers_[i]->GetLayerPlane();
+          
+          double x0, y0, x1, y1, x2, y2;
+          viewportPlane.ProjectPoint(x0, y0, framePlane.GetOrigin());
+          viewportPlane.ProjectPoint(x1, y1, framePlane.GetOrigin() + framePlane.GetAxisX());
+          viewportPlane.ProjectPoint(x2, y2, framePlane.GetOrigin() + framePlane.GetAxisY());
+
+          /**
+           * Now we solve the system of linear equations Ax + b = x', given:
+           *   A [0 ; 0] + b = [x0 ; y0]
+           *   A [1 ; 0] + b = [x1 ; y1]
+           *   A [0 ; 1] + b = [x2 ; y2]
+           * <=>
+           *   b = [x0 ; y0]
+           *   A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0]
+           *   A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0]
+           * <=>
+           *   b = [x0 ; y0]
+           *   [a11 ; a21] = [x1 - x0 ; y1 - y0]
+           *   [a12 ; a22] = [x2 - x0 ; y2 - y0]
+           **/
+
+          cairo_matrix_t transform;
+          cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0);
+
+          cairo_save(cr);
+          cairo_transform(cr, &transform);
+          
+          if (!renderers_[i]->RenderLayer(context, view))
+          {
+            cairo_restore(cr);
+            return false;
+          }
+
+          cairo_restore(cr);
+        }
+
+        if (renderers_[i] != NULL &&
+            !renderers_[i]->IsFullQuality())
+        {
+          fullQuality = false;
+        }
+      }
+
+      if (!fullQuality)
+      {
+        double x, y;
+        view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10);
+
+        cairo_translate(cr, x, y);
+
+#if 1
+        double s = 5.0 / view.GetZoom();
+        cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s);
+#else
+        // TODO Drawing filled circles makes WebAssembly crash!
+        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>());
+#endif
+        
+        cairo_set_line_width(cr, 2.0 / view.GetZoom());
+        cairo_set_source_rgb(cr, 1, 1, 1);
+        cairo_stroke_preserve(cr);
+        cairo_set_source_rgb(cr, 1, 0, 0);
+        cairo_fill(cr);
+      }
+
+      return true;
+    }
+
+    void SetLayerStyle(size_t index,
+                       const RenderStyle& style)
+    {
+      if (renderers_[index] != NULL)
+      {
+        renderers_[index]->SetLayerStyle(style);
+      }
+    }
+
+    bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const
+    {
+      bool isOpposite;
+      if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                               plane.GetNormal(),
+                                                               plane_.GetNormal()))
+      {
+        return false;
+      }
+      else
+      {
+        double z = (plane_.ProjectAlongNormal(plane.GetOrigin()) -
+                    plane_.ProjectAlongNormal(plane_.GetOrigin()));
+
+        if (z < 0)
+        {
+          z = -z;
+        }
+
+        return z <= thickness_;
+      }
+    }
+
+    double GetThickness() const
+    {
+      return thickness_;
+    }
+  };
+
+  
+  bool SliceViewerWidget::LookupLayer(size_t& index /* out */,
+                                      const IVolumeSlicer& layer) const
+  {
+    LayersIndex::const_iterator found = layersIndex_.find(&layer);
+
+    if (found == layersIndex_.end())
+    {
+      return false;
+    }
+    else
+    {
+      index = found->second;
+      assert(index < layers_.size() &&
+             layers_[index] == &layer);
+      return true;
+    }
+  }
+
+
+  void SliceViewerWidget::GetLayerExtent(OrthancStone::Extent2D& extent,
+                                         IVolumeSlicer& source) const
+  {
+    extent.Reset();
+
+    std::vector<OrthancStone::Vector> points;
+    if (source.GetExtent(points, plane_))
+    {
+      for (size_t i = 0; i < points.size(); i++)
+      {
+        double x, y;
+        plane_.ProjectPoint(x, y, points[i]);
+        extent.AddPoint(x, y);
+      }
+    }
+  }
+
+
+  OrthancStone::Extent2D SliceViewerWidget::GetSceneExtent()
+  {
+    OrthancStone::Extent2D sceneExtent;
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      OrthancStone::Extent2D layerExtent;
+      GetLayerExtent(layerExtent, *layers_[i]);
+
+      sceneExtent.Union(layerExtent);
+    }
+
+    return sceneExtent;
+  }
+
+  
+  bool SliceViewerWidget::RenderScene(OrthancStone::CairoContext& context,
+                                      const ViewportGeometry& view)
+  {
+    if (currentScene_.get() != NULL)
+    {
+      return currentScene_->RenderScene(context, view, plane_);
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  
+  void SliceViewerWidget::ResetPendingScene()
+  {
+    double thickness;
+    if (pendingScene_.get() == NULL)
+    {
+      thickness = 1.0;
+    }
+    else
+    {
+      thickness = pendingScene_->GetThickness();
+    }
+    
+    pendingScene_.reset(new Scene(plane_, thickness, layers_.size()));
+  }
+  
+
+  void SliceViewerWidget::UpdateLayer(size_t index,
+                                      ILayerRenderer* renderer,
+                                      const OrthancStone::CoordinateSystem3D& plane)
+  {
+    LOG(INFO) << "Updating layer " << index;
+    
+    std::auto_ptr<ILayerRenderer> tmp(renderer);
+
+    if (renderer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    renderer->SetLayerStyle(styles_[index]);
+
+    if (currentScene_.get() != NULL &&
+        currentScene_->ContainsPlane(plane))
+    {
+      currentScene_->SetLayer(index, tmp.release());
+      NotifyContentChanged();
+    }
+    else if (pendingScene_.get() != NULL &&
+             pendingScene_->ContainsPlane(plane))
+    {
+      pendingScene_->SetLayer(index, tmp.release());
+
+      if (currentScene_.get() == NULL ||
+          !currentScene_->IsComplete() ||
+          pendingScene_->IsComplete())
+      {
+        currentScene_ = pendingScene_;
+        NotifyContentChanged();
+      }
+    }
+  }
+
+  
+  SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, 
+                                       const std::string& name) :
+    WorldSceneWidget(name),
+    IObserver(broker),
+    IObservable(broker),
+    started_(false)
+  {
+    SetBackgroundCleared(true);
+  }
+  
+  
+  SliceViewerWidget::~SliceViewerWidget()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      delete layers_[i];
+    }
+  }
+  
+  void SliceViewerWidget::ObserveLayer(IVolumeSlicer& layer)
+  {
+    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage>
+                                   (*this, &SliceViewerWidget::OnGeometryReady));
+    // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...));
+    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage>
+                                   (*this, &SliceViewerWidget::OnSliceChanged));
+    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage>
+                                   (*this, &SliceViewerWidget::OnContentChanged));
+    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage>
+                                   (*this, &SliceViewerWidget::OnLayerReady));
+    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage>
+                                   (*this, &SliceViewerWidget::OnLayerError));
+  }
+
+
+  size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer)  // Takes ownership
+  {
+    if (layer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    size_t index = layers_.size();
+    layers_.push_back(layer);
+    styles_.push_back(RenderStyle());
+    layersIndex_[layer] = index;
+
+    ResetPendingScene();
+
+    ObserveLayer(*layer);
+
+    ResetChangedLayers();
+
+    return index;
+  }
+
+
+  void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer)  // Takes ownership
+  {
+    if (layer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    delete layers_[index];
+    layers_[index] = layer;
+    layersIndex_[layer] = index;
+
+    ResetPendingScene();
+
+    ObserveLayer(*layer);
+
+    InvalidateLayer(index);
+  }
+
+
+  void SliceViewerWidget::RemoveLayer(size_t index)
+  {
+    if (index >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    IVolumeSlicer* previousLayer = layers_[index];
+    layersIndex_.erase(layersIndex_.find(previousLayer));
+    layers_.erase(layers_.begin() + index);
+    changedLayers_.erase(changedLayers_.begin() + index);
+    styles_.erase(styles_.begin() + index);
+
+    delete layers_[index];
+
+    currentScene_->DeleteLayer(index);
+    ResetPendingScene();
+
+    NotifyContentChanged();
+  }
+
+
+  const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    return styles_[layer];
+  }
+  
+
+  void SliceViewerWidget::SetLayerStyle(size_t layer,
+                                        const RenderStyle& style)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    styles_[layer] = style;
+
+    if (currentScene_.get() != NULL)
+    {
+      currentScene_->SetLayerStyle(layer, style);
+    }
+
+    if (pendingScene_.get() != NULL)
+    {
+      pendingScene_->SetLayerStyle(layer, style);
+    }
+
+    NotifyContentChanged();
+  }
+  
+
+  void SliceViewerWidget::SetSlice(const OrthancStone::CoordinateSystem3D& plane)
+  {
+    LOG(INFO) << "Setting slice origin: (" << plane.GetOrigin()[0]
+              << "," << plane.GetOrigin()[1]
+              << "," << plane.GetOrigin()[2] << ")";
+    
+    Deprecated::Slice displayedSlice(plane_, THIN_SLICE_THICKNESS);
+
+    //if (!displayedSlice.ContainsPlane(slice))
+    {
+      if (currentScene_.get() == NULL ||
+          (pendingScene_.get() != NULL &&
+           pendingScene_->IsComplete()))
+      {
+        currentScene_ = pendingScene_;
+      }
+
+      plane_ = plane;
+      ResetPendingScene();
+
+      InvalidateAllLayers();   // TODO Removing this line avoid loading twice the image in WASM
+    }
+
+    BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice));
+  }
+
+
+  void SliceViewerWidget::OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message)
+  {
+    size_t i;
+    if (LookupLayer(i, message.GetOrigin()))
+    {
+      LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
+
+      changedLayers_[i] = true;
+      //layers_[i]->ScheduleLayerCreation(plane_);
+    }
+    BroadcastMessage(GeometryChangedMessage(*this));
+  }
+  
+
+  void SliceViewerWidget::InvalidateAllLayers()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      changedLayers_[i] = true;
+      
+      //layers_[i]->ScheduleLayerCreation(plane_);
+    }
+  }
+
+
+  void SliceViewerWidget::InvalidateLayer(size_t layer)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_[layer] != NULL);
+    changedLayers_[layer] = true;
+
+    //layers_[layer]->ScheduleLayerCreation(plane_);
+  }
+
+
+  void SliceViewerWidget::OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message)
+  {
+    size_t index;
+    if (LookupLayer(index, message.GetOrigin()))
+    {
+      InvalidateLayer(index);
+    }
+    
+    BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
+  }
+  
+
+  void SliceViewerWidget::OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message)
+  {
+    if (message.GetSlice().ContainsPlane(plane_))
+    {
+      size_t index;
+      if (LookupLayer(index, message.GetOrigin()))
+      {
+        InvalidateLayer(index);
+      }
+    }
+    
+    BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
+  }
+  
+  
+  void SliceViewerWidget::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message)
+  {
+    size_t index;
+    if (LookupLayer(index, message.GetOrigin()))
+    {
+      LOG(INFO) << "Renderer ready for layer " << index;
+      UpdateLayer(index, message.CreateRenderer(), message.GetSlice());
+    }
+    
+    BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
+  }
+
+
+  void SliceViewerWidget::OnLayerError(const IVolumeSlicer::LayerErrorMessage& message)
+  {
+    size_t index;
+    if (LookupLayer(index, message.GetOrigin()))
+    {
+      LOG(ERROR) << "Using error renderer on layer " << index;
+
+      // TODO
+      //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
+
+      BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
+    }
+  }
+
+
+  void SliceViewerWidget::ResetChangedLayers()
+  {
+    changedLayers_.resize(layers_.size());
+
+    for (size_t i = 0; i < changedLayers_.size(); i++)
+    {
+      changedLayers_[i] = false;
+    }
+  }
+
+
+  void SliceViewerWidget::DoAnimation()
+  {
+    assert(changedLayers_.size() <= layers_.size());
+    
+    for (size_t i = 0; i < changedLayers_.size(); i++)
+    {
+      if (changedLayers_[i])
+      {
+        layers_[i]->ScheduleLayerCreation(plane_);
+      }
+    }
+    
+    ResetChangedLayers();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/SliceViewerWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,155 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+#include "../Layers/IVolumeSlicer.h"
+#include "../../Toolbox/Extent2D.h"
+#include "../../Messages/IObserver.h"
+
+#include <map>
+
+namespace Deprecated
+{
+  class SliceViewerWidget :
+    public WorldSceneWidget,
+    public OrthancStone::IObserver,
+    public OrthancStone::IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryChangedMessage, SliceViewerWidget);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, SliceViewerWidget);
+
+
+    // TODO - Use this message in ReferenceLineSource
+    class DisplayedSliceMessage : public OrthancStone::OriginMessage<SliceViewerWidget>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      const Deprecated::Slice& slice_;
+
+    public:
+      DisplayedSliceMessage(SliceViewerWidget& origin,
+                            const Deprecated::Slice& slice) :
+        OriginMessage(origin),
+        slice_(slice)
+      {
+      }
+
+      const Deprecated::Slice& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+
+  private:
+    SliceViewerWidget(const SliceViewerWidget&);
+    SliceViewerWidget& operator=(const SliceViewerWidget&);
+
+    class Scene;
+    
+    typedef std::map<const IVolumeSlicer*, size_t>  LayersIndex;
+
+    bool                         started_;
+    LayersIndex                  layersIndex_;
+    std::vector<IVolumeSlicer*>  layers_;
+    std::vector<RenderStyle>     styles_;
+    OrthancStone::CoordinateSystem3D           plane_;
+    std::auto_ptr<Scene>         currentScene_;
+    std::auto_ptr<Scene>         pendingScene_;
+    std::vector<bool>            changedLayers_;
+
+    bool LookupLayer(size_t& index /* out */,
+                     const IVolumeSlicer& layer) const;
+
+    void GetLayerExtent(OrthancStone::Extent2D& extent,
+                        IVolumeSlicer& source) const;
+
+    void OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message);
+
+    virtual void OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message);
+
+    virtual void OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message);
+
+    virtual void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message);
+
+    virtual void OnLayerError(const IVolumeSlicer::LayerErrorMessage& message);
+
+    void ObserveLayer(IVolumeSlicer& source);
+
+    void ResetChangedLayers();
+
+  public:
+    SliceViewerWidget(OrthancStone::MessageBroker& broker, 
+                      const std::string& name);
+
+    virtual OrthancStone::Extent2D GetSceneExtent();
+
+  protected:
+    virtual bool RenderScene(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view);
+
+    void ResetPendingScene();
+
+    void UpdateLayer(size_t index,
+                     ILayerRenderer* renderer,
+                     const OrthancStone::CoordinateSystem3D& plane);
+
+    void InvalidateAllLayers();
+
+    void InvalidateLayer(size_t layer);
+    
+  public:
+    virtual ~SliceViewerWidget();
+
+    size_t AddLayer(IVolumeSlicer* layer);  // Takes ownership
+
+    void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership
+
+    void RemoveLayer(size_t layerIndex);
+
+    size_t GetLayerCount() const
+    {
+      return layers_.size();
+    }
+
+    const RenderStyle& GetLayerStyle(size_t layer) const;
+
+    void SetLayerStyle(size_t layer,
+                       const RenderStyle& style);
+
+    void SetSlice(const OrthancStone::CoordinateSystem3D& plane);
+
+    const OrthancStone::CoordinateSystem3D& GetSlice() const
+    {
+      return plane_;
+    }
+
+    virtual bool HasAnimation() const
+    {
+      return true;
+    }
+
+    virtual void DoAnimation();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/TestCairoWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,126 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "TestCairoWidget.h"
+
+#include <stdio.h>
+
+
+namespace Deprecated
+{
+  namespace Samples
+  {
+    void TestCairoWidget::DoAnimation() 
+    {
+      value_ -= 0.01f;
+      if (value_ < 0)
+      {
+        value_ = 1;
+      }
+
+      NotifyContentChanged();
+    }
+
+
+    bool TestCairoWidget::RenderCairo(OrthancStone::CairoContext& context)
+    {
+      cairo_t* cr = context.GetObject();
+
+      cairo_set_source_rgb (cr, .3, 0, 0);
+      cairo_paint(cr);
+
+      cairo_set_source_rgb(cr, 0, 1, 0);
+      cairo_rectangle(cr, width_ / 4, height_ / 4, width_ / 2, height_ / 2);
+      cairo_set_line_width(cr, 1.0);
+      cairo_fill(cr);
+
+      cairo_set_source_rgb(cr, 0, 1, value_);
+      cairo_rectangle(cr, width_ / 2 - 50, height_ / 2 - 50, 100, 100);
+      cairo_fill(cr);
+
+      return true;
+    }
+
+
+    void TestCairoWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context,
+                                               int x,
+                                               int y)
+    {
+      cairo_t* cr = context.GetObject();
+
+      cairo_set_source_rgb (cr, 1, 0, 0);
+      cairo_rectangle(cr, x - 5, y - 5, 10, 10);
+      cairo_set_line_width(cr, 1.0);
+      cairo_stroke(cr);
+
+      char buf[64];
+      sprintf(buf, "(%d,%d)", x, y);
+      UpdateStatusBar(buf);
+    }
+
+
+    TestCairoWidget::TestCairoWidget(const std::string& name, bool animate) :
+      CairoWidget(name),
+      width_(0),
+      height_(0),
+      value_(1),
+      animate_(animate)
+    {
+    }
+
+
+    void TestCairoWidget::SetSize(unsigned int width, 
+                                  unsigned int height)
+    {
+      CairoWidget::SetSize(width, height);
+      width_ = width;
+      height_ = height;
+    }
+ 
+
+    IMouseTracker* TestCairoWidget::CreateMouseTracker(OrthancStone::MouseButton button,
+                                                       int x,
+                                                       int y,
+                                                       OrthancStone::KeyboardModifiers modifiers,
+                                                       const std::vector<Touch>& touches)
+    {
+      UpdateStatusBar("Click");
+      return NULL;
+    }
+
+
+    void TestCairoWidget::MouseWheel(OrthancStone::MouseWheelDirection direction,
+                                     int x,
+                                     int y,
+                                     OrthancStone::KeyboardModifiers modifiers) 
+    {
+      UpdateStatusBar(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up");
+    }
+
+    
+    void TestCairoWidget::KeyPressed(OrthancStone::KeyboardKeys key,
+                                     char keyChar,
+                                     OrthancStone::KeyboardModifiers modifiers)
+    {
+      UpdateStatusBar("Key pressed: \"" + std::string(1, keyChar) + "\"");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/TestCairoWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,79 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "CairoWidget.h"
+
+namespace Deprecated
+{
+  namespace Samples
+  {
+    class TestCairoWidget : public CairoWidget
+    {
+    private:
+      unsigned int  width_;
+      unsigned int  height_;
+      float         value_;
+      bool          animate_;
+
+    protected:
+      virtual bool RenderCairo(OrthancStone::CairoContext& context);
+
+      virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context,
+                                        int x,
+                                        int y);
+
+    public:
+      TestCairoWidget(const std::string& name, bool animate);
+
+      virtual void SetSize(unsigned int width, 
+                           unsigned int height);
+ 
+      virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
+                                                int x,
+                                                int y,
+                                                OrthancStone::KeyboardModifiers modifiers,
+                                                const std::vector<Touch>& touches);
+
+      virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                              int x,
+                              int y,
+                              OrthancStone::KeyboardModifiers modifiers);
+    
+      virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                              char keyChar,
+                              OrthancStone::KeyboardModifiers modifiers);
+
+      virtual bool HasAnimation() const
+      {
+        return animate_;
+      }
+      
+      virtual void DoAnimation();
+
+      virtual bool HasRenderMouseOver()
+      {
+        return true;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/TestWorldSceneWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,149 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "TestWorldSceneWidget.h"
+
+#include <Core/OrthancException.h>
+
+#include <math.h>
+#include <stdio.h>
+
+namespace Deprecated
+{
+  namespace Samples
+  {
+    class TestWorldSceneWidget::Interactor : public IWorldSceneInteractor
+    {
+    public:
+      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                          const ViewportGeometry& view,
+                                                          OrthancStone::MouseButton button,
+                                                          OrthancStone::KeyboardModifiers modifiers,
+                                                          int viewportX,
+                                                          int viewportY,
+                                                          double x,
+                                                          double y,
+                                                          IStatusBar* statusBar,
+                                                          const std::vector<Touch>& touches)
+      {
+        if (statusBar)
+        {
+          char buf[64];
+          sprintf(buf, "X = %0.2f, Y = %0.2f", x, y);
+          statusBar->SetMessage(buf);
+        }
+
+        return NULL;
+      }
+
+      virtual void MouseOver(OrthancStone::CairoContext& context,
+                             WorldSceneWidget& widget,
+                             const ViewportGeometry& view,
+                             double x,
+                             double y,
+                             IStatusBar* statusBar)
+      {
+        double S = 0.5;
+
+        if (fabs(x) <= S &&
+            fabs(y) <= S)
+        {
+          cairo_t* cr = context.GetObject();
+          cairo_set_source_rgb(cr, 1, 0, 0);
+          cairo_rectangle(cr, -S, -S , 2.0 * S, 2.0 * S);
+          cairo_set_line_width(cr, 1.0 / view.GetZoom());
+          cairo_stroke(cr);
+        }
+      }
+
+      virtual void MouseWheel(WorldSceneWidget& widget,
+                              OrthancStone::MouseWheelDirection direction,
+                              OrthancStone::KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+        if (statusBar)
+        {
+          statusBar->SetMessage(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up");
+        }
+      }
+
+      virtual void KeyPressed(WorldSceneWidget& widget,
+                              OrthancStone::KeyboardKeys key,
+                              char keyChar,
+                              OrthancStone::KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+        if (statusBar)
+        {
+          statusBar->SetMessage("Key pressed: \"" + std::string(1, keyChar) + "\"");
+        }
+      }
+    };
+
+
+    bool TestWorldSceneWidget::RenderScene(OrthancStone::CairoContext& context,
+                                           const ViewportGeometry& view)
+    {
+      cairo_t* cr = context.GetObject();
+
+      // Clear background
+      cairo_set_source_rgb(cr, 0, 0, 0);
+      cairo_paint(cr);
+
+      float color = static_cast<float>(count_ % 16) / 15.0f;
+      cairo_set_source_rgb(cr, 0, 1.0f - color, color);
+      cairo_rectangle(cr, -10, -.5, 20, 1);
+      cairo_fill(cr);
+
+      return true;
+    }
+
+
+    TestWorldSceneWidget::TestWorldSceneWidget(const std::string& name, bool animate) :
+      WorldSceneWidget(name),
+      interactor_(new Interactor),
+      animate_(animate),
+      count_(0)
+    {
+      SetInteractor(*interactor_);
+    }
+
+
+    OrthancStone::Extent2D TestWorldSceneWidget::GetSceneExtent()
+    {
+      return OrthancStone::Extent2D(-10, -.5, 10, .5);
+    }
+
+
+    void TestWorldSceneWidget::DoAnimation()
+    {
+      if (animate_)
+      {
+        count_++;
+        NotifyContentChanged();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/TestWorldSceneWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,63 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+
+#include <memory>
+
+namespace Deprecated
+{
+  namespace Samples
+  {
+    class TestWorldSceneWidget : public WorldSceneWidget
+    {
+    private:
+      class Interactor;
+
+      std::auto_ptr<Interactor>   interactor_;
+      bool                        animate_;
+      unsigned int                count_;
+
+    protected:
+      virtual bool RenderScene(OrthancStone::CairoContext& context,
+                               const ViewportGeometry& view);
+
+    public:
+      TestWorldSceneWidget(const std::string& name, bool animate);
+
+      virtual OrthancStone::Extent2D GetSceneExtent();
+
+      virtual bool HasAnimation() const
+      {
+        return animate_;
+      }
+
+      virtual void DoAnimation();
+
+      virtual bool HasRenderMouseOver()
+      {
+        return true;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/WidgetBase.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,166 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WidgetBase.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
+
+namespace Deprecated
+{
+  void WidgetBase::NotifyContentChanged()
+  {
+    if (parent_ != NULL)
+    {
+      parent_->NotifyContentChanged();
+    }
+
+    if (viewport_ != NULL)
+    {
+      viewport_->NotifyBackgroundChanged();
+    }
+  }
+
+
+  void WidgetBase::SetParent(IWidget& parent)
+  {
+    if (parent_ != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      parent_ = &parent;
+    }
+  }    
+
+  
+  void WidgetBase::ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const 
+  {
+    // Clear the background using Orthanc
+
+    if (backgroundCleared_)
+    {
+      Orthanc::ImageProcessing::Set(target, 
+                                    backgroundColor_[0],
+                                    backgroundColor_[1],
+                                    backgroundColor_[2],
+                                    255 /* alpha */);
+    }
+  }
+
+
+  void WidgetBase::ClearBackgroundCairo(OrthancStone::CairoContext& context) const
+  {
+    // Clear the background using Cairo
+
+    if (IsBackgroundCleared())
+    {
+      uint8_t red, green, blue;
+      GetBackgroundColor(red, green, blue);
+
+      context.SetSourceColor(red, green, blue);
+      cairo_paint(context.GetObject());
+    }
+  }
+
+
+  void WidgetBase::ClearBackgroundCairo(Orthanc::ImageAccessor& target) const
+  {
+    OrthancStone::CairoSurface surface(target, false /* no alpha */);
+    OrthancStone::CairoContext context(surface);
+    ClearBackgroundCairo(context);
+  }
+
+
+  void WidgetBase::UpdateStatusBar(const std::string& message)
+  {
+    if (statusBar_ != NULL)
+    {
+      statusBar_->SetMessage(message);
+    }
+  }
+
+
+  WidgetBase::WidgetBase(const std::string& name) :
+    parent_(NULL),
+    viewport_(NULL),
+    statusBar_(NULL),
+    backgroundCleared_(false),
+    transmitMouseOver_(false),
+    name_(name)
+  {
+    backgroundColor_[0] = 0;
+    backgroundColor_[1] = 0;
+    backgroundColor_[2] = 0;
+  }
+
+
+  void WidgetBase::SetViewport(WidgetViewport& viewport)
+  {
+    if (viewport_ != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      viewport_ = &viewport;
+    }
+  }
+
+  
+  void WidgetBase::SetBackgroundColor(uint8_t red,
+                                      uint8_t green,
+                                      uint8_t blue)
+  {
+    backgroundColor_[0] = red;
+    backgroundColor_[1] = green;
+    backgroundColor_[2] = blue;
+  }
+
+  void WidgetBase::GetBackgroundColor(uint8_t& red,
+                                      uint8_t& green,
+                                      uint8_t& blue) const
+  {
+    red = backgroundColor_[0];
+    green = backgroundColor_[1];
+    blue = backgroundColor_[2];
+  }
+
+
+  bool WidgetBase::Render(Orthanc::ImageAccessor& surface)
+  {
+#if 0
+    ClearBackgroundOrthanc(surface);
+#else
+    ClearBackgroundCairo(surface);  // Faster than Orthanc
+#endif
+
+    return true;
+  }
+
+  
+  void WidgetBase::DoAnimation()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/WidgetBase.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,117 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWidget.h"
+
+#include "../../Wrappers/CairoContext.h"
+#include "../Viewport/WidgetViewport.h"
+
+namespace Deprecated
+{
+  class WidgetBase : public IWidget
+  {
+  private:
+    IWidget*         parent_;
+    WidgetViewport*  viewport_;
+    IStatusBar*      statusBar_;
+    bool             backgroundCleared_;
+    uint8_t          backgroundColor_[3];
+    bool             transmitMouseOver_;
+    std::string      name_;
+
+  protected:
+    void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const;
+
+    void ClearBackgroundCairo(OrthancStone::CairoContext& context) const;
+
+    void ClearBackgroundCairo(Orthanc::ImageAccessor& target) const;
+
+    void UpdateStatusBar(const std::string& message);
+
+    IStatusBar* GetStatusBar() const
+    {
+      return statusBar_;
+    }
+
+  public:
+    WidgetBase(const std::string& name);
+
+    virtual void FitContent()
+    {
+    }
+  
+    virtual void SetParent(IWidget& parent);
+    
+    virtual void SetViewport(WidgetViewport& viewport);
+
+    void SetBackgroundCleared(bool clear)
+    {
+      backgroundCleared_ = clear;
+    }
+
+    bool IsBackgroundCleared() const
+    {
+      return backgroundCleared_;
+    }
+
+    void SetTransmitMouseOver(bool transmit)
+    {
+      transmitMouseOver_ = transmit;
+    }
+
+    void SetBackgroundColor(uint8_t red,
+                            uint8_t green,
+                            uint8_t blue);
+
+    void GetBackgroundColor(uint8_t& red,
+                            uint8_t& green,
+                            uint8_t& blue) const;
+
+    virtual void SetStatusBar(IStatusBar& statusBar)
+    {
+      statusBar_ = &statusBar;
+    }
+
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+
+    virtual bool HasAnimation() const
+    {
+      return false;
+    }
+
+    virtual void DoAnimation();
+
+    virtual bool HasRenderMouseOver()
+    {
+      return transmitMouseOver_;
+    }
+
+    virtual void NotifyContentChanged();
+
+    const std::string& GetName() const
+    {
+      return name_;
+    }
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/WorldSceneWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,231 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WorldSceneWidget.h"
+
+#include "PanMouseTracker.h"
+#include "ZoomMouseTracker.h"
+#include "PanZoomMouseTracker.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <math.h>
+#include <memory>
+#include <cassert>
+
+namespace Deprecated
+{
+  // this is an adapter between a IWorldSceneMouseTracker
+  // that is tracking a mouse in scene coordinates/mm and
+  // an IMouseTracker that is tracking a mouse
+  // in screen coordinates/pixels.
+  class WorldSceneWidget::SceneMouseTracker : public IMouseTracker
+  {
+  private:
+    ViewportGeometry                        view_;
+    std::auto_ptr<IWorldSceneMouseTracker>  tracker_;
+
+  public:
+    SceneMouseTracker(const ViewportGeometry& view,
+                      IWorldSceneMouseTracker* tracker) :
+      view_(view),
+      tracker_(tracker)
+    {
+      if (tracker == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    virtual void Render(Orthanc::ImageAccessor& target)
+    {
+      if (tracker_->HasRender())
+      {
+        OrthancStone::CairoSurface surface(target, false /* no alpha */);
+        OrthancStone::CairoContext context(surface);
+        view_.ApplyTransform(context);
+        tracker_->Render(context, view_.GetZoom());
+      }
+    }
+
+    virtual void MouseUp()
+    {
+      tracker_->MouseUp();
+    }
+
+    virtual void MouseMove(int x,
+                           int y,
+                           const std::vector<Touch>& displayTouches)
+    {
+      double sceneX, sceneY;
+      view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
+
+      std::vector<Touch> sceneTouches;
+      for (size_t t = 0; t < displayTouches.size(); t++)
+      {
+        double sx, sy;
+        
+        view_.MapPixelCenterToScene(
+          sx, sy, (int)displayTouches[t].x, (int)displayTouches[t].y);
+        
+        sceneTouches.push_back(
+          Touch(static_cast<float>(sx), static_cast<float>(sy)));
+      }
+      tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches);
+    }
+  };
+
+
+  bool WorldSceneWidget::RenderCairo(OrthancStone::CairoContext& context)
+  {
+    view_.ApplyTransform(context);
+    return RenderScene(context, view_);
+  }
+
+
+  void WorldSceneWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context,
+                                              int x,
+                                              int y)
+  {
+    ViewportGeometry view = GetView();
+    view.ApplyTransform(context);
+
+    double sceneX, sceneY;
+    view.MapPixelCenterToScene(sceneX, sceneY, x, y);
+
+    if (interactor_)
+    {
+      interactor_->MouseOver(context, *this, view, sceneX, sceneY, GetStatusBar());
+    }
+  }
+
+
+  void WorldSceneWidget::SetSceneExtent(ViewportGeometry& view)
+  {
+    view.SetSceneExtent(GetSceneExtent());
+  }
+
+
+  void WorldSceneWidget::SetSize(unsigned int width,
+                                 unsigned int height)
+  {
+    CairoWidget::SetSize(width, height);
+    view_.SetDisplaySize(width, height);
+  }
+
+
+  void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor)
+  {
+    interactor_ = &interactor;
+  }
+
+
+  void WorldSceneWidget::FitContent()
+  {
+    SetSceneExtent(view_);
+    view_.FitContent();
+
+    NotifyContentChanged();
+  }
+
+
+  void WorldSceneWidget::SetView(const ViewportGeometry& view)
+  {
+    view_ = view;
+
+    NotifyContentChanged();
+  }
+
+
+  IMouseTracker* WorldSceneWidget::CreateMouseTracker(OrthancStone::MouseButton button,
+                                                      int x,
+                                                      int y,
+                                                      OrthancStone::KeyboardModifiers modifiers,
+                                                      const std::vector<Touch>& touches)
+  {
+    double sceneX, sceneY;
+    view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
+
+    // asks the Widget Interactor to provide a mouse tracker
+    std::auto_ptr<IWorldSceneMouseTracker> tracker;
+
+    if (interactor_)
+    {
+      tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches));
+    }
+    
+    if (tracker.get() != NULL)
+    {
+      return new SceneMouseTracker(view_, tracker.release());
+    }
+    else if (hasDefaultMouseEvents_)
+    {
+      printf("has default mouse events\n");
+      if (touches.size() == 2)
+      {
+        printf("2 touches !\n");
+        return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches));
+      }
+      else
+      {
+        switch (button)
+        {
+          case OrthancStone::MouseButton_Middle:
+            return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y));
+
+          case OrthancStone::MouseButton_Right:
+            return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y));
+
+          default:
+            return NULL;
+        }
+      }
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void WorldSceneWidget::MouseWheel(OrthancStone::MouseWheelDirection direction,
+                                    int x,
+                                    int y,
+                                    OrthancStone::KeyboardModifiers modifiers)
+  {
+    if (interactor_)
+    {
+      interactor_->MouseWheel(*this, direction, modifiers, GetStatusBar());
+    }
+  }
+
+
+  void WorldSceneWidget::KeyPressed(OrthancStone::KeyboardKeys key,
+                                    char keyChar,
+                                    OrthancStone::KeyboardModifiers modifiers)
+  {
+    if (interactor_)
+    {
+      interactor_->KeyPressed(*this, key, keyChar, modifiers, GetStatusBar());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/WorldSceneWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,103 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "CairoWidget.h"
+#include "IWorldSceneInteractor.h"
+
+#include "../Toolbox/ViewportGeometry.h"
+
+namespace Deprecated
+{
+  class WorldSceneWidget : public CairoWidget
+  {
+  private:
+    class SceneMouseTracker;
+
+    ViewportGeometry       view_;
+    IWorldSceneInteractor* interactor_;
+    bool                   hasDefaultMouseEvents_;
+
+  protected:
+    virtual OrthancStone::Extent2D GetSceneExtent() = 0;
+
+    virtual bool RenderScene(OrthancStone::CairoContext& context,
+                             const ViewportGeometry& view) = 0;
+
+    // From CairoWidget
+    virtual bool RenderCairo(OrthancStone::CairoContext& context);
+
+    // From CairoWidget
+    virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context,
+                                      int x,
+                                      int y);
+
+    void SetSceneExtent(ViewportGeometry& geometry);
+
+  public:
+    WorldSceneWidget(const std::string& name) :
+      CairoWidget(name),
+      interactor_(NULL),
+      hasDefaultMouseEvents_(true)
+    {
+    }
+
+    void SetDefaultMouseEvents(bool value)
+    {
+      hasDefaultMouseEvents_ = value;
+    }
+
+    bool HasDefaultMouseEvents() const
+    {
+      return hasDefaultMouseEvents_;
+    }
+
+    void SetInteractor(IWorldSceneInteractor& interactor);
+
+    void SetView(const ViewportGeometry& view);
+
+    const ViewportGeometry& GetView() const
+    {
+      return view_;
+    }
+
+    virtual void SetSize(unsigned int width,
+                         unsigned int height);
+
+    virtual void FitContent();
+
+    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
+                                              int x,
+                                              int y,
+                                              OrthancStone::KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches);
+
+    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
+                            int x,
+                            int y,
+                            OrthancStone::KeyboardModifiers modifiers);
+
+    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/ZoomMouseTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,110 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ZoomMouseTracker.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  ZoomMouseTracker::ZoomMouseTracker(WorldSceneWidget& that,
+                                     int x,
+                                     int y) :
+    that_(that),
+    originalZoom_(that.GetView().GetZoom()),
+    downX_(x),
+    downY_(y)
+  {
+    that.GetView().MapPixelCenterToScene(centerX_, centerY_, x, y);
+
+    unsigned int height = that.GetView().GetDisplayHeight();
+      
+    if (height <= 3)
+    {
+      idle_ = true;
+      LOG(WARNING) << "image is too small to zoom (current height = " << height << ")";
+    }
+    else
+    {
+      idle_ = false;
+      normalization_ = 1.0 / static_cast<double>(height - 1);
+    }
+  }
+    
+
+  void ZoomMouseTracker::Render(OrthancStone::CairoContext& context,
+                                double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void ZoomMouseTracker::MouseMove(int displayX,
+                                   int displayY,
+                                   double x,
+                                   double y,
+                                   const std::vector<Touch>& displayTouches,
+                                   const std::vector<Touch>& sceneTouches)
+  {
+    static const double MIN_ZOOM = -4;
+    static const double MAX_ZOOM = 4;
+
+      
+    if (!idle_)
+    {
+      double dy = static_cast<double>(displayY - downY_) * normalization_;  // In the range [-1,1]
+      double z;
+
+      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+      if (dy < -1.0)
+      {
+        z = MIN_ZOOM;
+      }
+      else if (dy > 1.0)
+      {
+        z = MAX_ZOOM;
+      }
+      else
+      {
+        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
+      }
+
+      z = pow(2.0, z);
+
+      ViewportGeometry view = that_.GetView();
+        
+      view.SetZoom(z * originalZoom_);
+        
+      // Correct the pan so that the original click point is kept at
+      // the same location on the display
+      double panX, panY;
+      view.GetPan(panX, panY);
+
+      int tx, ty;
+      view.MapSceneToDisplay(tx, ty, centerX_, centerY_);
+      view.SetPan(panX + static_cast<double>(downX_ - tx),
+                  panY + static_cast<double>(downY_ - ty));
+        
+      that_.SetView(view);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Widgets/ZoomMouseTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,64 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+
+namespace Deprecated
+{
+  class ZoomMouseTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;
+    double             originalZoom_;
+    int                downX_;
+    int                downY_;
+    double             centerX_;
+    double             centerY_;
+    bool               idle_;
+    double             normalization_;
+    
+  public:
+    ZoomMouseTracker(WorldSceneWidget& that,
+                     int x,
+                     int y);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void MouseUp()
+    {
+    }
+
+    virtual void Render(OrthancStone::CairoContext& context,
+                        double zoom);
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/dev.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,958 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Layers/FrameRenderer.h"
+#include "Layers/LineLayerRenderer.h"
+#include "Layers/SliceOutlineRenderer.h"
+#include "Toolbox/DownloadStack.h"
+#include "Toolbox/GeometryToolbox.h"
+#include "Toolbox/OrthancSlicesLoader.h"
+#include "Volumes/ISlicedVolume.h"
+#include "Volumes/ImageBuffer3D.h"
+#include "Widgets/SliceViewerWidget.h"
+
+#include <Core/Logging.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+
+namespace Deprecated
+{
+  // TODO: Handle errors while loading
+  class OrthancVolumeImage :
+    public ISlicedVolume,
+    public OrthancStone::IObserver
+  {
+  private:
+    OrthancSlicesLoader           loader_;
+    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;
+    std::auto_ptr<DownloadStack>  downloadStack_;
+    bool                          computeRange_;
+    size_t                        pendingSlices_;
+
+    void ScheduleSliceDownload()
+    {
+      assert(downloadStack_.get() != NULL);
+
+      unsigned int slice;
+      if (downloadStack_->Pop(slice))
+      {
+        loader_.ScheduleLoadSliceImage(slice, OrthancStone::SliceImageQuality_Jpeg90);
+      }
+    }
+
+
+    static bool IsCompatible(const Slice& a,
+                             const Slice& b)
+    {
+      if (!OrthancStone::GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(),
+                                                     b.GetGeometry().GetNormal()))
+      {
+        LOG(ERROR) << "A slice in the volume image is not parallel to the others.";
+        return false;
+      }
+
+      if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat())
+      {
+        LOG(ERROR) << "The pixel format changes across the slices of the volume image.";
+        return false;
+      }
+
+      if (a.GetWidth() != b.GetWidth() ||
+          a.GetHeight() != b.GetHeight())
+      {
+        LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image";
+        return false;
+      }
+
+      if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
+          !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY()))
+      {
+        LOG(ERROR) << "The pixel spacing of the slices change across the volume image";
+        return false;
+      }
+
+      return true;
+    }
+
+
+    static double GetDistance(const Slice& a,
+                              const Slice& b)
+    {
+      return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) -
+                  a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin()));
+    }
+
+
+    void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message)
+    {
+      assert(&message.GetOrigin() == &loader_);
+
+      if (loader_.GetSlicesCount() == 0)
+      {
+        LOG(ERROR) << "Empty volume image";
+        BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
+        return;
+      }
+
+      for (size_t i = 1; i < loader_.GetSlicesCount(); i++)
+      {
+        if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i)))
+        {
+          BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
+          return;
+        }
+      }
+
+      double spacingZ;
+
+      if (loader_.GetSlicesCount() > 1)
+      {
+        spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1));
+      }
+      else
+      {
+        // This is a volume with one single slice: Choose a dummy
+        // z-dimension for voxels
+        spacingZ = 1;
+      }
+
+      for (size_t i = 1; i < loader_.GetSlicesCount(); i++)
+      {
+        if (!OrthancStone::LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)),
+                                                 0.001 /* this is expressed in mm */))
+        {
+          LOG(ERROR) << "The distance between successive slices is not constant in a volume image";
+          BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
+          return;
+        }
+      }
+
+      unsigned int width = loader_.GetSlice(0).GetWidth();
+      unsigned int height = loader_.GetSlice(0).GetHeight();
+      Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat();
+      LOG(INFO) << "Creating a volume image of size " << width << "x" << height
+                << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format);
+
+      image_.reset(new OrthancStone::ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSlicesCount()), computeRange_));
+      image_->GetGeometry().SetAxialGeometry(loader_.GetSlice(0).GetGeometry());
+      image_->GetGeometry().SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(),
+                                               loader_.GetSlice(0).GetPixelSpacingY(), spacingZ);
+      image_->Clear();
+
+      downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSlicesCount())));
+      pendingSlices_ = loader_.GetSlicesCount();
+
+      for (unsigned int i = 0; i < 4; i++)  // Limit to 4 simultaneous downloads
+      {
+        ScheduleSliceDownload();
+      }
+
+      // TODO Check the DicomFrameConverter are constant
+
+      BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this));
+    }
+
+
+    void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message)
+    {
+      assert(&message.GetOrigin() == &loader_);
+
+      LOG(ERROR) << "Unable to download a volume image";
+      BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
+    }
+
+
+    void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message)
+    {
+      assert(&message.GetOrigin() == &loader_);
+
+      {
+        OrthancStone::ImageBuffer3D::SliceWriter writer(*image_, OrthancStone::VolumeProjection_Axial, message.GetSliceIndex());
+        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage());
+      }
+
+      BroadcastMessage(ISlicedVolume::SliceContentChangedMessage
+                       (*this, message.GetSliceIndex(), message.GetSlice()));
+
+      if (pendingSlices_ == 1)
+      {
+        BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this));
+        pendingSlices_ = 0;
+      }
+      else if (pendingSlices_ > 1)
+      {
+        pendingSlices_ -= 1;
+      }
+
+      ScheduleSliceDownload();
+    }
+
+
+    void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message)
+    {
+      assert(&message.GetOrigin() == &loader_);
+
+      LOG(ERROR) << "Cannot download slice " << message.GetSliceIndex() << " in a volume image";
+      ScheduleSliceDownload();
+    }
+
+
+  public:
+    OrthancVolumeImage(OrthancStone::MessageBroker& broker,
+                       OrthancApiClient& orthanc,
+                       bool computeRange) :
+      ISlicedVolume(broker),
+      IObserver(broker),
+      loader_(broker, orthanc),
+      computeRange_(computeRange),
+      pendingSlices_(0)
+    {
+      loader_.RegisterObserverCallback(
+        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryReadyMessage>
+        (*this, &OrthancVolumeImage::OnSliceGeometryReady));
+
+      loader_.RegisterObserverCallback(
+        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryErrorMessage>
+        (*this, &OrthancVolumeImage::OnSliceGeometryError));
+
+      loader_.RegisterObserverCallback(
+        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageReadyMessage>
+        (*this, &OrthancVolumeImage::OnSliceImageReady));
+
+      loader_.RegisterObserverCallback(
+        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageErrorMessage>
+        (*this, &OrthancVolumeImage::OnSliceImageError));
+    }
+
+    void ScheduleLoadSeries(const std::string& seriesId)
+    {
+      loader_.ScheduleLoadSeries(seriesId);
+    }
+
+    void ScheduleLoadInstance(const std::string& instanceId)
+    {
+      loader_.ScheduleLoadInstance(instanceId);
+    }
+
+    void ScheduleLoadFrame(const std::string& instanceId,
+                           unsigned int frame)
+    {
+      loader_.ScheduleLoadFrame(instanceId, frame);
+    }
+
+    virtual size_t GetSlicesCount() const
+    {
+      return loader_.GetSlicesCount();
+    }
+
+    virtual const Slice& GetSlice(size_t index) const
+    {
+      return loader_.GetSlice(index);
+    }
+
+    OrthancStone::ImageBuffer3D& GetImage() const
+    {
+      if (image_.get() == NULL)
+      {
+        // The geometry is not ready yet
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return *image_;
+      }
+    }
+
+    bool FitWindowingToRange(RenderStyle& style,
+                             const DicomFrameConverter& converter) const
+    {
+      if (image_.get() == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        return image_->FitWindowingToRange(style, converter);
+      }
+    }
+  };
+
+
+  class VolumeImageGeometry
+  {
+  private:
+    unsigned int         width_;
+    unsigned int         height_;
+    size_t               depth_;
+    double               pixelSpacingX_;
+    double               pixelSpacingY_;
+    double               sliceThickness_;
+    OrthancStone::CoordinateSystem3D   reference_;
+    DicomFrameConverter  converter_;
+
+    double ComputeAxialThickness(const OrthancVolumeImage& volume) const
+    {
+      double thickness;
+
+      size_t n = volume.GetSlicesCount();
+      if (n > 1)
+      {
+        const Slice& a = volume.GetSlice(0);
+        const Slice& b = volume.GetSlice(n - 1);
+        thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) -
+                      reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) /
+                     (static_cast<double>(n) - 1.0));
+      }
+      else
+      {
+        thickness = volume.GetSlice(0).GetThickness();
+      }
+
+      if (thickness <= 0)
+      {
+        // The slices should have been sorted with increasing Z
+        // (along the normal) by the OrthancSlicesLoader
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+      else
+      {
+        return thickness;
+      }
+    }
+
+    void SetupAxial(const OrthancVolumeImage& volume)
+    {
+      const Slice& axial = volume.GetSlice(0);
+
+      width_ = axial.GetWidth();
+      height_ = axial.GetHeight();
+      depth_ = volume.GetSlicesCount();
+
+      pixelSpacingX_ = axial.GetPixelSpacingX();
+      pixelSpacingY_ = axial.GetPixelSpacingY();
+      sliceThickness_ = ComputeAxialThickness(volume);
+
+      reference_ = axial.GetGeometry();
+    }
+
+    void SetupCoronal(const OrthancVolumeImage& volume)
+    {
+      const Slice& axial = volume.GetSlice(0);
+      double axialThickness = ComputeAxialThickness(volume);
+
+      width_ = axial.GetWidth();
+      height_ = static_cast<unsigned int>(volume.GetSlicesCount());
+      depth_ = axial.GetHeight();
+
+      pixelSpacingX_ = axial.GetPixelSpacingX();
+      pixelSpacingY_ = axialThickness;
+      sliceThickness_ = axial.GetPixelSpacingY();
+
+      OrthancStone::Vector origin = axial.GetGeometry().GetOrigin();
+      origin += (static_cast<double>(volume.GetSlicesCount() - 1) *
+                 axialThickness * axial.GetGeometry().GetNormal());
+
+      reference_ = OrthancStone::CoordinateSystem3D(origin,
+                                                    axial.GetGeometry().GetAxisX(),
+                                                    - axial.GetGeometry().GetNormal());
+    }
+
+    void SetupSagittal(const OrthancVolumeImage& volume)
+    {
+      const Slice& axial = volume.GetSlice(0);
+      double axialThickness = ComputeAxialThickness(volume);
+
+      width_ = axial.GetHeight();
+      height_ = static_cast<unsigned int>(volume.GetSlicesCount());
+      depth_ = axial.GetWidth();
+
+      pixelSpacingX_ = axial.GetPixelSpacingY();
+      pixelSpacingY_ = axialThickness;
+      sliceThickness_ = axial.GetPixelSpacingX();
+
+      OrthancStone::Vector origin = axial.GetGeometry().GetOrigin();
+      origin += (static_cast<double>(volume.GetSlicesCount() - 1) *
+                 axialThickness * axial.GetGeometry().GetNormal());
+
+      reference_ = OrthancStone::CoordinateSystem3D(origin,
+                                                    axial.GetGeometry().GetAxisY(),
+                                                    axial.GetGeometry().GetNormal());
+    }
+
+  public:
+    VolumeImageGeometry(const OrthancVolumeImage& volume,
+                        OrthancStone::VolumeProjection projection)
+    {
+      if (volume.GetSlicesCount() == 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      converter_ = volume.GetSlice(0).GetConverter();
+
+      switch (projection)
+      {
+        case OrthancStone::VolumeProjection_Axial:
+          SetupAxial(volume);
+          break;
+
+        case OrthancStone::VolumeProjection_Coronal:
+          SetupCoronal(volume);
+          break;
+
+        case OrthancStone::VolumeProjection_Sagittal:
+          SetupSagittal(volume);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    size_t GetSlicesCount() const
+    {
+      return depth_;
+    }
+
+    const OrthancStone::Vector& GetNormal() const
+    {
+      return reference_.GetNormal();
+    }
+
+    bool LookupSlice(size_t& index,
+                     const OrthancStone::CoordinateSystem3D& slice) const
+    {
+      bool opposite;
+      if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite,
+                                                               reference_.GetNormal(),
+                                                               slice.GetNormal()))
+      {
+        return false;
+      }
+
+      double z = (reference_.ProjectAlongNormal(slice.GetOrigin()) -
+                  reference_.ProjectAlongNormal(reference_.GetOrigin())) / sliceThickness_;
+
+      int s = static_cast<int>(boost::math::iround(z));
+
+      if (s < 0 ||
+          s >= static_cast<int>(depth_))
+      {
+        return false;
+      }
+      else
+      {
+        index = static_cast<size_t>(s);
+        return true;
+      }
+    }
+
+    Slice* GetSlice(size_t slice) const
+    {
+      if (slice >= depth_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        OrthancStone::CoordinateSystem3D origin(reference_.GetOrigin() +
+                                                static_cast<double>(slice) * sliceThickness_ * reference_.GetNormal(),
+                                                reference_.GetAxisX(),
+                                                reference_.GetAxisY());
+
+        return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_,
+                         width_, height_, converter_);
+      }
+    }
+  };
+
+
+
+  class VolumeImageMPRSlicer :
+    public IVolumeSlicer,
+    public OrthancStone::IObserver
+  {
+  private:
+    class RendererFactory : public LayerReadyMessage::IRendererFactory
+    {
+    private:
+      const Orthanc::ImageAccessor&  frame_;
+      const Slice&                   slice_;
+      bool                           isFullQuality_;
+
+    public:
+      RendererFactory(const Orthanc::ImageAccessor& frame,
+                      const Slice& slice,
+                      bool isFullQuality) :
+        frame_(frame),
+        slice_(slice),
+        isFullQuality_(isFullQuality)
+      {
+      }
+
+      virtual ILayerRenderer* CreateRenderer() const
+      {
+        return FrameRenderer::CreateRenderer(frame_, slice_, isFullQuality_);
+      }
+    };
+
+
+    OrthancVolumeImage&                 volume_;
+    std::auto_ptr<VolumeImageGeometry>  axialGeometry_;
+    std::auto_ptr<VolumeImageGeometry>  coronalGeometry_;
+    std::auto_ptr<VolumeImageGeometry>  sagittalGeometry_;
+
+
+    bool IsGeometryReady() const
+    {
+      return axialGeometry_.get() != NULL;
+    }
+
+    void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message)
+    {
+      assert(&message.GetOrigin() == &volume_);
+
+      // These 3 values are only used to speed up the IVolumeSlicer
+      axialGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Axial));
+      coronalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Coronal));
+      sagittalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Sagittal));
+
+      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this));
+    }
+
+    void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message)
+    {
+      assert(&message.GetOrigin() == &volume_);
+
+      BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this));
+    }
+
+    void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message)
+    {
+      assert(&message.GetOrigin() == &volume_);
+
+      BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this));
+    }
+
+    void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message)
+    {
+      assert(&message.GetOrigin() == &volume_);
+
+      //IVolumeSlicer::OnSliceContentChange(slice);
+
+      // TODO Improve this?
+      BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this));
+    }
+
+    const VolumeImageGeometry& GetProjectionGeometry(OrthancStone::VolumeProjection projection)
+    {
+      if (!IsGeometryReady())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      switch (projection)
+      {
+        case OrthancStone::VolumeProjection_Axial:
+          return *axialGeometry_;
+
+        case OrthancStone::VolumeProjection_Sagittal:
+          return *sagittalGeometry_;
+
+        case OrthancStone::VolumeProjection_Coronal:
+          return *coronalGeometry_;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+
+    bool DetectProjection(OrthancStone::VolumeProjection& projection,
+                          const OrthancStone::CoordinateSystem3D& viewportSlice)
+    {
+      bool isOpposite;  // Ignored
+
+      if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                              viewportSlice.GetNormal(),
+                                                              axialGeometry_->GetNormal()))
+      {
+        projection = OrthancStone::VolumeProjection_Axial;
+        return true;
+      }
+      else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                                   viewportSlice.GetNormal(),
+                                                                   sagittalGeometry_->GetNormal()))
+      {
+        projection = OrthancStone::VolumeProjection_Sagittal;
+        return true;
+      }
+      else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                                   viewportSlice.GetNormal(),
+                                                                   coronalGeometry_->GetNormal()))
+      {
+        projection = OrthancStone::VolumeProjection_Coronal;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+  public:
+    VolumeImageMPRSlicer(OrthancStone::MessageBroker& broker,
+                         OrthancVolumeImage&  volume) :
+      IVolumeSlicer(broker),
+      IObserver(broker),
+      volume_(volume)
+    {
+      volume_.RegisterObserverCallback(
+        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage>
+        (*this, &VolumeImageMPRSlicer::OnGeometryReady));
+
+      volume_.RegisterObserverCallback(
+        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryErrorMessage>
+        (*this, &VolumeImageMPRSlicer::OnGeometryError));
+
+      volume_.RegisterObserverCallback(
+        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::ContentChangedMessage>
+        (*this, &VolumeImageMPRSlicer::OnContentChanged));
+
+      volume_.RegisterObserverCallback(
+        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::SliceContentChangedMessage>
+        (*this, &VolumeImageMPRSlicer::OnSliceContentChanged));
+    }
+
+    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
+                           const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE
+    {
+      OrthancStone::VolumeProjection projection;
+
+      if (!IsGeometryReady() ||
+          !DetectProjection(projection, viewportSlice))
+      {
+        return false;
+      }
+      else
+      {
+        // As the slices of the volumic image are arranged in a box,
+        // we only consider one single reference slice (the one with index 0).
+        std::auto_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0));
+        slice->GetExtent(points);
+
+        return true;
+      }
+    }
+
+    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE
+    {
+      OrthancStone::VolumeProjection projection;
+
+      if (IsGeometryReady() &&
+          DetectProjection(projection, viewportSlice))
+      {
+        const VolumeImageGeometry& geometry = GetProjectionGeometry(projection);
+
+        size_t closest;
+
+        if (geometry.LookupSlice(closest, viewportSlice))
+        {
+          bool isFullQuality = true;  // TODO
+
+          std::auto_ptr<Orthanc::Image> frame;
+
+          {
+            OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, static_cast<unsigned int>(closest));
+
+            // TODO Transfer ownership if non-axial, to avoid memcpy
+            frame.reset(Orthanc::Image::Clone(reader.GetAccessor()));
+          }
+
+          std::auto_ptr<Slice> slice(geometry.GetSlice(closest));
+
+          RendererFactory factory(*frame, *slice, isFullQuality);
+
+          BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry()));
+          return;
+        }
+      }
+
+      // Error
+      OrthancStone::CoordinateSystem3D slice;
+      BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice));
+    }
+  };
+
+
+  class VolumeImageInteractor :
+    public IWorldSceneInteractor,
+    public OrthancStone::IObserver
+  {
+  private:
+    SliceViewerWidget&                  widget_;
+    OrthancStone::VolumeProjection      projection_;
+    std::auto_ptr<VolumeImageGeometry>  slices_;
+    size_t                              slice_;
+
+  protected:
+    void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message)
+    {
+      if (slices_.get() == NULL)
+      {
+        const OrthancVolumeImage& image =
+          dynamic_cast<const OrthancVolumeImage&>(message.GetOrigin());
+
+        slices_.reset(new VolumeImageGeometry(image, projection_));
+        SetSlice(slices_->GetSlicesCount() / 2);
+
+        widget_.FitContent();
+      }
+    }
+
+    virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                        const ViewportGeometry& view,
+                                                        OrthancStone::MouseButton button,
+                                                        OrthancStone::KeyboardModifiers modifiers,
+                                                        int viewportX,
+                                                        int viewportY,
+                                                        double x,
+                                                        double y,
+                                                        IStatusBar* statusBar,
+                                                        const std::vector<Touch>& touches) ORTHANC_OVERRIDE
+    {
+      return  NULL;
+    }
+
+    virtual void MouseOver(OrthancStone::CairoContext& context,
+                           WorldSceneWidget& widget,
+                           const ViewportGeometry& view,
+                           double x,
+                           double y,
+                           IStatusBar* statusBar) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void MouseWheel(WorldSceneWidget& widget,
+                            OrthancStone::MouseWheelDirection direction,
+                            OrthancStone::KeyboardModifiers modifiers,
+                            IStatusBar* statusBar) ORTHANC_OVERRIDE
+    {
+      int scale = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1);
+
+      switch (direction)
+      {
+        case OrthancStone::MouseWheelDirection_Up:
+          OffsetSlice(-scale);
+          break;
+
+        case OrthancStone::MouseWheelDirection_Down:
+          OffsetSlice(scale);
+          break;
+
+        default:
+          break;
+      }
+    }
+
+    virtual void KeyPressed(WorldSceneWidget& widget,
+                            OrthancStone::KeyboardKeys key,
+                            char keyChar,
+                            OrthancStone::KeyboardModifiers modifiers,
+                            IStatusBar* statusBar) ORTHANC_OVERRIDE
+    {
+      switch (keyChar)
+      {
+        case 's':
+          widget.FitContent();
+          break;
+
+        default:
+          break;
+      }
+    }
+
+  public:
+    VolumeImageInteractor(OrthancStone::MessageBroker& broker,
+                          OrthancVolumeImage& volume,
+                          SliceViewerWidget& widget,
+                          OrthancStone::VolumeProjection projection) :
+      IObserver(broker),
+      widget_(widget),
+      projection_(projection)
+    {
+      widget.SetInteractor(*this);
+
+      volume.RegisterObserverCallback(
+        new OrthancStone::Callable<VolumeImageInteractor, ISlicedVolume::GeometryReadyMessage>
+        (*this, &VolumeImageInteractor::OnGeometryReady));
+    }
+
+    bool IsGeometryReady() const
+    {
+      return slices_.get() != NULL;
+    }
+
+    size_t GetSlicesCount() const
+    {
+      if (slices_.get() == NULL)
+      {
+        return 0;
+      }
+      else
+      {
+        return slices_->GetSlicesCount();
+      }
+    }
+
+    void OffsetSlice(int offset)
+    {
+      if (slices_.get() != NULL)
+      {
+        int slice = static_cast<int>(slice_) + offset;
+
+        if (slice < 0)
+        {
+          slice = 0;
+        }
+
+        if (slice >= static_cast<int>(slices_->GetSlicesCount()))
+        {
+          slice = static_cast<unsigned int>(slices_->GetSlicesCount()) - 1;
+        }
+
+        if (slice != static_cast<int>(slice_))
+        {
+          SetSlice(slice);
+        }
+      }
+    }
+
+    void SetSlice(size_t slice)
+    {
+      if (slices_.get() != NULL)
+      {
+        slice_ = slice;
+
+        std::auto_ptr<Slice> tmp(slices_->GetSlice(slice_));
+        widget_.SetSlice(tmp->GetGeometry());
+      }
+    }
+  };
+
+
+
+  class ReferenceLineSource : public IVolumeSlicer
+  {
+  private:
+    class RendererFactory : public LayerReadyMessage::IRendererFactory
+    {
+    private:
+      double                     x1_;
+      double                     y1_;
+      double                     x2_;
+      double                     y2_;
+      const OrthancStone::CoordinateSystem3D&  slice_;
+
+    public:
+      RendererFactory(double x1,
+                      double y1,
+                      double x2,
+                      double y2,
+                      const OrthancStone::CoordinateSystem3D& slice) :
+        x1_(x1),
+        y1_(y1),
+        x2_(x2),
+        y2_(y2),
+        slice_(slice)
+      {
+      }
+
+      virtual ILayerRenderer* CreateRenderer() const
+      {
+        return new LineLayerRenderer(x1_, y1_, x2_, y2_, slice_);
+      }
+    };
+
+    SliceViewerWidget&  otherPlane_;
+
+  public:
+    ReferenceLineSource(OrthancStone::MessageBroker& broker,
+                        SliceViewerWidget&  otherPlane) :
+      IVolumeSlicer(broker),
+      otherPlane_(otherPlane)
+    {
+      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this));
+    }
+
+    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
+                           const OrthancStone::CoordinateSystem3D& viewportSlice)
+    {
+      return false;
+    }
+
+    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
+    {
+      Slice reference(viewportSlice, 0.001);
+
+      OrthancStone::Vector p, d;
+
+      const OrthancStone::CoordinateSystem3D& slice = otherPlane_.GetSlice();
+
+      // Compute the line of intersection between the two slices
+      if (!OrthancStone::GeometryToolbox::IntersectTwoPlanes(p, d,
+                                                             slice.GetOrigin(), slice.GetNormal(),
+                                                             viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
+      {
+        // The two slice are parallel, don't try and display the intersection
+        BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry()));
+      }
+      else
+      {
+        double x1, y1, x2, y2;
+        viewportSlice.ProjectPoint(x1, y1, p);
+        viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
+
+        const OrthancStone::Extent2D extent = otherPlane_.GetSceneExtent();
+
+        if (OrthancStone::GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2,
+                                                               x1, y1, x2, y2,
+                                                               extent.GetX1(), extent.GetY1(),
+                                                               extent.GetX2(), extent.GetY2()))
+        {
+          RendererFactory factory(x1, y1, x2, y2, slice);
+          BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry()));
+        }
+        else
+        {
+          // Error: Parallel slices
+          BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry()));
+        }
+      }
+    }
+  };
+}
--- a/Framework/Layers/CircleMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "CircleMeasureTracker.h"
-
-#include <stdio.h>
-#include <boost/math/constants/constants.hpp>
-
-namespace Deprecated
-{
-  CircleMeasureTracker::CircleMeasureTracker(IStatusBar* statusBar,
-                                             const OrthancStone::CoordinateSystem3D& slice,
-                                             double x, 
-                                             double y,
-                                             uint8_t red,
-                                             uint8_t green,
-                                             uint8_t blue,
-                                             const Orthanc::Font& font) :
-    statusBar_(statusBar),
-    slice_(slice),
-    x1_(x),
-    y1_(y),
-    x2_(x),
-    y2_(y),
-    font_(font)
-  {
-    color_[0] = red;
-    color_[1] = green;
-    color_[2] = blue;
-  }
-    
-
-  void CircleMeasureTracker::Render(OrthancStone::CairoContext& context,
-                                    double zoom)
-  {
-    double x = (x1_ + x2_) / 2.0;
-    double y = (y1_ + y2_) / 2.0;
-
-    OrthancStone::Vector tmp;
-    OrthancStone::LinearAlgebra::AssignVector(tmp, x2_ - x1_, y2_ - y1_);
-    double r = boost::numeric::ublas::norm_2(tmp) / 2.0;
-
-    context.SetSourceColor(color_[0], color_[1], color_[2]);
-
-    cairo_t* cr = context.GetObject();
-    cairo_save(cr);
-    cairo_set_line_width(cr, 2.0 / zoom);
-    cairo_translate(cr, x, y);
-    cairo_arc(cr, 0, 0, r, 0, 2.0 * boost::math::constants::pi<double>());
-    cairo_stroke_preserve(cr);
-    cairo_stroke(cr);
-    cairo_restore(cr);
-
-    context.DrawText(font_, FormatRadius(), x, y, OrthancStone::BitmapAnchor_Center);
-  }
-    
-
-  double CircleMeasureTracker::GetRadius() const  // In millimeters
-  {
-    OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_);
-    OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_);
-    return boost::numeric::ublas::norm_2(b - a) / 2.0;
-  }
-
-
-  std::string CircleMeasureTracker::FormatRadius() const
-  {
-    char buf[64];
-    sprintf(buf, "%0.01f cm", GetRadius() / 10.0);
-    return buf;
-  }
-
-  void CircleMeasureTracker::MouseMove(int displayX,
-                                       int displayY,
-                                       double x,
-                                       double y,
-                                       const std::vector<Touch>& displayTouches,
-                                       const std::vector<Touch>& sceneTouches)
-  {
-    x2_ = x;
-    y2_ = y;
-
-    if (statusBar_ != NULL)
-    {
-      statusBar_->SetMessage("Circle radius: " + FormatRadius());
-    }
-  }
-}
--- a/Framework/Layers/CircleMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Widgets/IWorldSceneMouseTracker.h"
-
-#include "../Viewport/IStatusBar.h"
-#include "../Toolbox/CoordinateSystem3D.h"
-
-#include <Core/Images/Font.h>
-
-namespace Deprecated
-{
-  class CircleMeasureTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    IStatusBar*           statusBar_;
-    OrthancStone::CoordinateSystem3D    slice_;
-    double                x1_;
-    double                y1_;
-    double                x2_;
-    double                y2_;
-    uint8_t               color_[3];
-    const Orthanc::Font&  font_;
-
-  public:
-    CircleMeasureTracker(IStatusBar* statusBar,
-                         const OrthancStone::CoordinateSystem3D& slice,
-                         double x, 
-                         double y,
-                         uint8_t red,
-                         uint8_t green,
-                         uint8_t blue,
-                         const Orthanc::Font& font);
-    
-    virtual bool HasRender() const
-    {
-      return true;
-    }
-
-    virtual void Render(OrthancStone::CairoContext& context,
-                        double zoom);
-    
-    double GetRadius() const;  // In millimeters
-
-    std::string FormatRadius() const;
-
-    virtual void MouseUp()
-    {
-      // Possibly create a new landmark "volume" with the circle in subclasses
-    }
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double x,
-                           double y,
-                           const std::vector<Touch>& displayTouches,
-                           const std::vector<Touch>& sceneTouches);
-  };
-}
--- a/Framework/Layers/ColorFrameRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ColorFrameRenderer.h"
-
-#include <Core/OrthancException.h>
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-
-namespace Deprecated
-{
-  OrthancStone::CairoSurface* ColorFrameRenderer::GenerateDisplay(const RenderStyle& style)
-  {
-    std::auto_ptr<OrthancStone::CairoSurface> display
-      (new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */));
-
-    Orthanc::ImageAccessor target;
-    display->GetWriteableAccessor(target);
-    
-    Orthanc::ImageProcessing::Convert(target, *frame_);
-
-    return display.release();
-  }
-
-
-  ColorFrameRenderer::ColorFrameRenderer(const Orthanc::ImageAccessor& frame,
-                                         const OrthancStone::CoordinateSystem3D& framePlane,
-                                         double pixelSpacingX,
-                                         double pixelSpacingY,
-                                         bool isFullQuality) :
-    FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality),
-    frame_(Orthanc::Image::Clone(frame))
-  {
-    if (frame_.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    if (frame_->GetFormat() != Orthanc::PixelFormat_RGB24)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-  }
-}
--- a/Framework/Layers/ColorFrameRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "FrameRenderer.h"
-
-namespace Deprecated
-{
-  class ColorFrameRenderer : public FrameRenderer
-  {
-  private:
-    std::auto_ptr<Orthanc::ImageAccessor>   frame_;  // In RGB24
-
-  protected:
-    virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style);
-
-  public:
-    ColorFrameRenderer(const Orthanc::ImageAccessor& frame,
-                       const OrthancStone::CoordinateSystem3D& framePlane,
-                       double pixelSpacingX,
-                       double pixelSpacingY,
-                       bool isFullQuality);
-  };
-}
--- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomSeriesVolumeSlicer.h"
-
-#include "FrameRenderer.h"
-#include "../Toolbox/DicomFrameConverter.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-#include <boost/lexical_cast.hpp>
-
-namespace Deprecated
-{
-
-  void DicomSeriesVolumeSlicer::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message)
-  {
-    if (message.GetOrigin().GetSlicesCount() > 0)
-    {
-      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this));
-    }
-    else
-    {
-      BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this));
-    }
-  }
-
-  void DicomSeriesVolumeSlicer::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message)
-  {
-    BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this));
-  }
-
-
-  class DicomSeriesVolumeSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory
-  {
-  private:
-    const OrthancSlicesLoader::SliceImageReadyMessage&  message_;
-
-  public:
-    RendererFactory(const OrthancSlicesLoader::SliceImageReadyMessage& message) :
-      message_(message)
-    {
-    }
-
-    virtual ILayerRenderer* CreateRenderer() const
-    {
-      bool isFull = (message_.GetEffectiveQuality() == OrthancStone::SliceImageQuality_FullPng ||
-                     message_.GetEffectiveQuality() == OrthancStone::SliceImageQuality_FullPam);
-
-      return FrameRenderer::CreateRenderer(message_.GetImage(), message_.GetSlice(), isFull);
-    }
-  };
-
-  void DicomSeriesVolumeSlicer::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message)
-  {
-    // first notify that the pixel data of the frame is ready (targeted to, i.e: an image cache)
-    BroadcastMessage(FrameReadyMessage(*this, message.GetImage(), 
-                                  message.GetEffectiveQuality(), message.GetSlice()));
-
-    // then notify that the layer is ready for rendering
-    RendererFactory factory(message);
-    BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry()));
-  }
-
-  void DicomSeriesVolumeSlicer::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message)
-  {
-    BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry()));
-  }
-
-
-  DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker,
-                                                   OrthancApiClient& orthanc) :
-    IVolumeSlicer(broker),
-    IObserver(broker),
-    loader_(broker, orthanc),
-    quality_(OrthancStone::SliceImageQuality_FullPng)
-  {
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage>
-        (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady));
-
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage>
-      (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError));
-
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage>
-        (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady));
-
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage>
-      (*this, &DicomSeriesVolumeSlicer::OnSliceImageError));
-  }
-
-  
-  void DicomSeriesVolumeSlicer::LoadSeries(const std::string& seriesId)
-  {
-    loader_.ScheduleLoadSeries(seriesId);
-  }
-
-
-  void DicomSeriesVolumeSlicer::LoadInstance(const std::string& instanceId)
-  {
-    loader_.ScheduleLoadInstance(instanceId);
-  }
-
-
-  void DicomSeriesVolumeSlicer::LoadFrame(const std::string& instanceId,
-                                          unsigned int frame)
-  {
-    loader_.ScheduleLoadFrame(instanceId, frame);
-  }
-
-
-  bool DicomSeriesVolumeSlicer::GetExtent(std::vector<OrthancStone::Vector>& points,
-                                          const OrthancStone::CoordinateSystem3D& viewportSlice)
-  {
-    size_t index;
-
-    if (loader_.IsGeometryReady() &&
-        loader_.LookupSlice(index, viewportSlice))
-    {
-      loader_.GetSlice(index).GetExtent(points);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  void DicomSeriesVolumeSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
-  {
-    size_t index;
-
-    if (loader_.IsGeometryReady() &&
-        loader_.LookupSlice(index, viewportSlice))
-    {
-      loader_.ScheduleLoadSliceImage(index, quality_);
-    }
-  }
-}
--- a/Framework/Layers/DicomSeriesVolumeSlicer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IVolumeSlicer.h"
-#include "../Toolbox/IWebService.h"
-#include "../Toolbox/OrthancSlicesLoader.h"
-#include "../Toolbox/OrthancApiClient.h"
-
-namespace Deprecated
-{  
-  // this class is in charge of loading a Frame.
-  // once it's been loaded (first the geometry and then the image),
-  // messages are sent to observers so they can use it
-  class DicomSeriesVolumeSlicer :
-    public IVolumeSlicer,
-    public OrthancStone::IObserver
-    //private OrthancSlicesLoader::ISliceLoaderObserver
-  {
-  public:
-    // TODO: Add "frame" and "instanceId"
-    class FrameReadyMessage : public OrthancStone::OriginMessage<DicomSeriesVolumeSlicer>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      const Orthanc::ImageAccessor&  frame_;
-      OrthancStone::SliceImageQuality              imageQuality_;
-      const Slice&                   slice_;
-
-    public:
-      FrameReadyMessage(DicomSeriesVolumeSlicer& origin,
-                        const Orthanc::ImageAccessor& frame,
-                        OrthancStone::SliceImageQuality imageQuality,
-                        const Slice& slice) :
-        OriginMessage(origin),
-        frame_(frame),
-        imageQuality_(imageQuality),
-        slice_(slice)
-      {
-      }
-
-      const Orthanc::ImageAccessor& GetFrame() const
-      {
-        return frame_;
-      }
-
-      OrthancStone::SliceImageQuality GetImageQuality() const
-      {
-        return imageQuality_;
-      }
-
-      const Slice& GetSlice() const
-      {
-        return slice_;
-      }
-    };
-
-    
-  private:
-    class RendererFactory;
-    
-    OrthancSlicesLoader  loader_;
-    OrthancStone::SliceImageQuality    quality_;
-
-  public:
-    DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker,
-                            OrthancApiClient& orthanc);
-
-    void LoadSeries(const std::string& seriesId);
-
-    void LoadInstance(const std::string& instanceId);
-
-    void LoadFrame(const std::string& instanceId,
-                   unsigned int frame);
-
-    void SetImageQuality(OrthancStone::SliceImageQuality quality)
-    {
-      quality_ = quality;
-    }
-
-    OrthancStone::SliceImageQuality GetImageQuality() const
-    {
-      return quality_;
-    }
-
-    size_t GetSlicesCount() const
-    {
-      return loader_.GetSlicesCount();
-    }
-
-    const Slice& GetSlice(size_t slice) const 
-    {
-      return loader_.GetSlice(slice);
-    }
-
-    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
-                           const OrthancStone::CoordinateSystem3D& viewportSlice);
-
-    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice);
-
-protected:
-    void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message);
-    void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message);
-    void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message);
-    void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message);
-  };
-}
--- a/Framework/Layers/DicomStructureSetSlicer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomStructureSetSlicer.h"
-
-namespace Deprecated
-{
-  class DicomStructureSetSlicer::Renderer : public ILayerRenderer
-  {
-  private:
-    class Structure
-    {
-    private:
-      bool                                                         visible_;
-      uint8_t                                                      red_;
-      uint8_t                                                      green_;
-      uint8_t                                                      blue_;
-      std::string                                                  name_;
-      std::vector< std::vector<OrthancStone::DicomStructureSet::PolygonPoint> >  polygons_;
-
-    public:
-      Structure(OrthancStone::DicomStructureSet& structureSet,
-                const OrthancStone::CoordinateSystem3D& plane,
-                size_t index) :
-        name_(structureSet.GetStructureName(index))
-      {
-        structureSet.GetStructureColor(red_, green_, blue_, index);
-        visible_ = structureSet.ProjectStructure(polygons_, index, plane);
-      }
-
-      void Render(OrthancStone::CairoContext& context)
-      {
-        if (visible_)
-        {
-          cairo_t* cr = context.GetObject();
-        
-          context.SetSourceColor(red_, green_, blue_);
-
-          for (size_t i = 0; i < polygons_.size(); i++)
-          {
-            cairo_move_to(cr, polygons_[i][0].first, polygons_[i][0].second);
-
-            for (size_t j = 1; j < polygons_[i].size(); j++)
-            {
-              cairo_line_to(cr, polygons_[i][j].first, polygons_[i][j].second);
-            }
-
-            cairo_line_to(cr, polygons_[i][0].first, polygons_[i][0].second);
-            cairo_stroke(cr);
-          }
-        }
-      }
-    };
-
-    typedef std::list<Structure*>  Structures;
-    
-    OrthancStone::CoordinateSystem3D  plane_;
-    Structures          structures_;
-    
-  public:
-    Renderer(OrthancStone::DicomStructureSet& structureSet,
-             const OrthancStone::CoordinateSystem3D& plane) :
-      plane_(plane)
-    {
-      for (size_t k = 0; k < structureSet.GetStructureCount(); k++)
-      {
-        structures_.push_back(new Structure(structureSet, plane, k));
-      }
-    }
-
-    virtual ~Renderer()
-    {
-      for (Structures::iterator it = structures_.begin();
-           it != structures_.end(); ++it)
-      {
-        delete *it;
-      }
-    }
-
-    virtual bool RenderLayer(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view)
-    {
-      cairo_set_line_width(context.GetObject(), 2.0f / view.GetZoom());
-
-      for (Structures::const_iterator it = structures_.begin();
-           it != structures_.end(); ++it)
-      {
-        assert(*it != NULL);
-        (*it)->Render(context);
-      }
-
-      return true;
-    }
-
-    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane()
-    {
-      return plane_;
-    }
-
-    virtual void SetLayerStyle(const RenderStyle& style)
-    {
-    }
-    
-    virtual bool IsFullQuality()
-    {
-      return true;
-    }
-  };
-
-
-  class DicomStructureSetSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory
-  {
-  private:
-    OrthancStone::DicomStructureSet&         structureSet_;
-    const OrthancStone::CoordinateSystem3D&  plane_;
-
-  public:
-    RendererFactory(OrthancStone::DicomStructureSet& structureSet,
-                    const OrthancStone::CoordinateSystem3D&  plane) :
-      structureSet_(structureSet),
-      plane_(plane)
-    {
-    }
-
-    virtual ILayerRenderer* CreateRenderer() const
-    {
-      return new Renderer(structureSet_, plane_);
-    }
-  };
-  
-
-  DicomStructureSetSlicer::DicomStructureSetSlicer(OrthancStone::MessageBroker& broker,
-                                                   StructureSetLoader& loader) :
-    IVolumeSlicer(broker),
-    IObserver(broker),
-    loader_(loader)
-  {
-    loader_.RegisterObserverCallback(
-      new OrthancStone::Callable<DicomStructureSetSlicer, StructureSetLoader::ContentChangedMessage>
-      (*this, &DicomStructureSetSlicer::OnStructureSetLoaded));
-  }
-
-
-  void DicomStructureSetSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane)
-  {
-    if (loader_.HasStructureSet())
-    {
-      RendererFactory factory(loader_.GetStructureSet(), viewportPlane);
-      BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane));
-    }
-  }
-}
--- a/Framework/Layers/DicomStructureSetSlicer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IVolumeSlicer.h"
-#include "../Volumes/StructureSetLoader.h"
-
-namespace Deprecated
-{
-  class DicomStructureSetSlicer :
-    public IVolumeSlicer,
-    public OrthancStone::IObserver
-  {
-  private:
-    class Renderer;
-    class RendererFactory;
-
-    StructureSetLoader& loader_;
-
-    void OnStructureSetLoaded(const IVolumeLoader::ContentChangedMessage& message)
-    {
-      BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this));
-    }
-
-  public:
-    DicomStructureSetSlicer(OrthancStone::MessageBroker& broker,
-                            StructureSetLoader& loader);
-
-    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
-                           const OrthancStone::CoordinateSystem3D& viewportPlane)
-    {
-      return false;
-    }
-
-    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane);
-  };
-}
--- a/Framework/Layers/FrameRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "FrameRenderer.h"
-
-#include "GrayscaleFrameRenderer.h"
-#include "ColorFrameRenderer.h"
-
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  FrameRenderer::FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane,
-                               double pixelSpacingX,
-                               double pixelSpacingY,
-                               bool isFullQuality) :
-    framePlane_(framePlane),
-    pixelSpacingX_(pixelSpacingX),
-    pixelSpacingY_(pixelSpacingY),
-    isFullQuality_(isFullQuality)
-  {
-  }
-
-
-  bool FrameRenderer::RenderLayer(OrthancStone::CairoContext& context,
-                                  const ViewportGeometry& view)
-  {    
-    if (!style_.visible_)
-    {
-      return true;
-    }
-
-    if (display_.get() == NULL)
-    {
-      display_.reset(GenerateDisplay(style_));
-    }
-
-    assert(display_.get() != NULL);
-
-    cairo_t *cr = context.GetObject();
-
-    cairo_save(cr);
-
-    cairo_matrix_t transform;
-    cairo_matrix_init_identity(&transform);
-    cairo_matrix_scale(&transform, pixelSpacingX_, pixelSpacingY_);
-    cairo_matrix_translate(&transform, -0.5, -0.5);
-    cairo_transform(cr, &transform);
-
-    //cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
-    cairo_set_source_surface(cr, display_->GetObject(), 0, 0);
-
-    switch (style_.interpolation_)
-    {
-      case OrthancStone::ImageInterpolation_Nearest:
-        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
-        break;
-
-      case OrthancStone::ImageInterpolation_Bilinear:
-        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    cairo_paint_with_alpha(cr, style_.alpha_);
-
-    if (style_.drawGrid_)
-    {
-      context.SetSourceColor(style_.drawColor_);
-      cairo_set_line_width(cr, 0.5 / view.GetZoom());
-
-      for (unsigned int x = 0; x <= display_->GetWidth(); x++)
-      {
-        cairo_move_to(cr, x, 0);
-        cairo_line_to(cr, x, display_->GetHeight());
-      }
-
-      for (unsigned int y = 0; y <= display_->GetHeight(); y++)
-      {
-        cairo_move_to(cr, 0, y);
-        cairo_line_to(cr, display_->GetWidth(), y);
-      }
-
-      cairo_stroke(cr);
-    }
-
-    cairo_restore(cr);
-
-    return true;
-  }
-
-
-  void FrameRenderer::SetLayerStyle(const RenderStyle& style)
-  {
-    style_ = style;
-    display_.reset(NULL);
-  }
-
-
-  ILayerRenderer* FrameRenderer::CreateRenderer(const Orthanc::ImageAccessor& frame,
-                                                const Deprecated::Slice& framePlane,
-                                                bool isFullQuality)
-  {
-    if (frame.GetFormat() == Orthanc::PixelFormat_RGB24)
-    {
-      return new ColorFrameRenderer(frame,
-                                    framePlane.GetGeometry(), 
-                                    framePlane.GetPixelSpacingX(),
-                                    framePlane.GetPixelSpacingY(), isFullQuality);
-    }
-    else
-    {
-      return new GrayscaleFrameRenderer(frame,
-                                        framePlane.GetConverter(),
-                                        framePlane.GetGeometry(), 
-                                        framePlane.GetPixelSpacingX(),
-                                        framePlane.GetPixelSpacingY(), isFullQuality);
-    }
-  }
-}
--- a/Framework/Layers/FrameRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILayerRenderer.h"
-
-#include "../Toolbox/Slice.h"
-
-namespace Deprecated
-{
-  class FrameRenderer : public ILayerRenderer
-  {
-  private:
-    OrthancStone::CoordinateSystem3D            framePlane_;
-    double                        pixelSpacingX_;
-    double                        pixelSpacingY_;
-    RenderStyle                   style_;
-    bool                          isFullQuality_;
-    std::auto_ptr<OrthancStone::CairoSurface>   display_;
-
-  protected:
-    virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style) = 0;
-
-  public:
-    FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane,
-                  double pixelSpacingX,
-                  double pixelSpacingY,
-                  bool isFullQuality);
-
-    virtual bool RenderLayer(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view);
-
-    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane()
-    {
-      return framePlane_;
-    }
-
-    virtual void SetLayerStyle(const RenderStyle& style);
-
-    virtual bool IsFullQuality() 
-    {
-      return isFullQuality_;
-    }
-
-    // TODO: Avoid cloning the "frame"
-    static ILayerRenderer* CreateRenderer(const Orthanc::ImageAccessor& frame,
-                                          const Deprecated::Slice& framePlane,
-                                          bool isFullQuality);
-  };
-}
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "GrayscaleFrameRenderer.h"
-
-#include <Core/Images/Image.h>
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  OrthancStone::CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style)
-  {
-    assert(frame_->GetFormat() == Orthanc::PixelFormat_Float32);
-
-    std::auto_ptr<OrthancStone::CairoSurface> result;
-
-    float windowCenter, windowWidth;
-    style.ComputeWindowing(windowCenter, windowWidth,
-                           defaultWindowCenter_, defaultWindowWidth_);
-
-    float x0 = windowCenter - windowWidth / 2.0f;
-    float x1 = windowCenter + windowWidth / 2.0f;
-
-    //LOG(INFO) << "Window: " << x0 << " => " << x1;
-
-    result.reset(new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */));
-
-    const uint8_t* lut = NULL;
-    if (style.applyLut_)
-    {
-      if (Orthanc::EmbeddedResources::GetFileResourceSize(style.lut_) != 3 * 256)
-      {
-        // Invalid colormap
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      lut = reinterpret_cast<const uint8_t*>(Orthanc::EmbeddedResources::GetFileResourceBuffer(style.lut_));
-    }
-
-    Orthanc::ImageAccessor target;
-    result->GetWriteableAccessor(target);
-    
-    const unsigned int width = target.GetWidth();
-    const unsigned int height = target.GetHeight();
-    
-    for (unsigned int y = 0; y < height; y++)
-    {
-      const float* p = reinterpret_cast<const float*>(frame_->GetConstRow(y));
-      uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-
-      for (unsigned int x = 0; x < width; x++, p++, q += 4)
-      {
-        uint8_t v = 0;
-        if (windowWidth >= 0.001f)  // Avoid division by zero
-        {
-          if (*p >= x1)
-          {
-            v = 255;
-          }
-          else if (*p <= x0)
-          {
-            v = 0;
-          }
-          else
-          {
-            // https://en.wikipedia.org/wiki/Linear_interpolation
-            v = static_cast<uint8_t>(255.0f * (*p - x0) / (x1 - x0));
-          }
-
-          if (style.reverse_ ^ (photometric_ == Orthanc::PhotometricInterpretation_Monochrome1))
-          {
-            v = 255 - v;
-          }
-        }
-
-        if (style.applyLut_)
-        {
-          assert(lut != NULL);
-          q[3] = 255;
-          q[2] = lut[3 * v];
-          q[1] = lut[3 * v + 1];
-          q[0] = lut[3 * v + 2];
-        }
-        else
-        {
-          q[3] = 255;
-          q[2] = v;
-          q[1] = v;
-          q[0] = v;
-        }
-      }
-    }
-
-    return result.release();
-  }
-
-
-  GrayscaleFrameRenderer::GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame,
-                                                 const Deprecated::DicomFrameConverter& converter,
-                                                 const OrthancStone::CoordinateSystem3D& framePlane,
-                                                 double pixelSpacingX,
-                                                 double pixelSpacingY,
-                                                 bool isFullQuality) :
-    FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality),
-    frame_(Orthanc::Image::Clone(frame)),
-    defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())),
-    defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())),
-    photometric_(converter.GetPhotometricInterpretation())
-  {
-    if (frame_.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    converter.ConvertFrameInplace(frame_);
-    assert(frame_.get() != NULL);
-
-    if (frame_->GetFormat() != Orthanc::PixelFormat_Float32)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-  }
-}
--- a/Framework/Layers/GrayscaleFrameRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "FrameRenderer.h"
-#include "../Toolbox/DicomFrameConverter.h"
-
-namespace Deprecated
-{
-  class GrayscaleFrameRenderer : public FrameRenderer
-  {
-  private:
-    std::auto_ptr<Orthanc::ImageAccessor>   frame_;  // In Float32
-    float                                   defaultWindowCenter_;
-    float                                   defaultWindowWidth_;
-    Orthanc::PhotometricInterpretation      photometric_;
-
-  protected:
-    virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style);
-
-  public:
-    GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame,
-                           const Deprecated::DicomFrameConverter& converter,
-                           const OrthancStone::CoordinateSystem3D& framePlane,
-                           double pixelSpacingX,
-                           double pixelSpacingY,
-                           bool isFullQuality);
-  };
-}
--- a/Framework/Layers/ILayerRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Viewport/CairoContext.h"
-#include "../Toolbox/CoordinateSystem3D.h"
-#include "../Toolbox/ViewportGeometry.h"
-#include "RenderStyle.h"
-
-namespace Deprecated
-{
-  class ILayerRenderer : public boost::noncopyable
-  {
-  public:
-    virtual ~ILayerRenderer()
-    {
-    }
-    
-    virtual bool RenderLayer(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view) = 0;
-
-    virtual void SetLayerStyle(const RenderStyle& style) = 0;
-
-    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() = 0;
-    
-    virtual bool IsFullQuality() = 0;
-  };
-}
--- a/Framework/Layers/IVolumeSlicer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILayerRenderer.h"
-#include "../Toolbox/Slice.h"
-#include "../../Framework/Messages/IObservable.h"
-#include "../../Framework/Messages/IMessage.h"
-#include "Core/Images/Image.h"
-#include <boost/shared_ptr.hpp>
-
-namespace Deprecated
-{
-  class IVolumeSlicer : public OrthancStone::IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeSlicer);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeSlicer);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeSlicer);
-
-    class SliceContentChangedMessage : public OrthancStone::OriginMessage<IVolumeSlicer>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      const Deprecated::Slice& slice_;
-
-    public:
-      SliceContentChangedMessage(IVolumeSlicer& origin,
-                                 const Deprecated::Slice& slice) :
-        OriginMessage(origin),
-        slice_(slice)
-      {
-      }
-
-      const Deprecated::Slice& GetSlice() const
-      {
-        return slice_;
-      }
-    };
-    
-
-    class LayerReadyMessage : public OrthancStone::OriginMessage<IVolumeSlicer>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    public:
-      class IRendererFactory : public boost::noncopyable
-      {
-      public:
-        virtual ~IRendererFactory()
-        {
-        }
-
-        virtual ILayerRenderer* CreateRenderer() const = 0;
-      };
-    
-    private:
-      const IRendererFactory&    factory_;
-      const OrthancStone::CoordinateSystem3D&  slice_;
-
-    public:
-      LayerReadyMessage(IVolumeSlicer& origin,
-                        const IRendererFactory& rendererFactory,
-                        const OrthancStone::CoordinateSystem3D& slice) :
-        OriginMessage(origin),
-        factory_(rendererFactory),
-        slice_(slice)
-      {
-      }
-
-      ILayerRenderer* CreateRenderer() const
-      {
-        return factory_.CreateRenderer();
-      }
-
-      const OrthancStone::CoordinateSystem3D& GetSlice() const
-      {
-        return slice_;
-      }
-    };
-
-
-    class LayerErrorMessage : public OrthancStone::OriginMessage<IVolumeSlicer>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      const OrthancStone::CoordinateSystem3D&  slice_;
-
-    public:
-      LayerErrorMessage(IVolumeSlicer& origin,
-                        const OrthancStone::CoordinateSystem3D& slice) :
-        OriginMessage(origin),
-        slice_(slice)
-      {
-      }
-
-      const OrthancStone::CoordinateSystem3D& GetSlice() const
-      {
-        return slice_;
-      }
-    };
-
-
-    IVolumeSlicer(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-    
-    virtual ~IVolumeSlicer()
-    {
-    }
-
-    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
-                           const OrthancStone::CoordinateSystem3D& viewportSlice) = 0;
-
-    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) = 0;
-  };
-}
--- a/Framework/Layers/LineLayerRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "LineLayerRenderer.h"
-
-namespace Deprecated
-{
-  LineLayerRenderer::LineLayerRenderer(double x1,
-                                       double y1,
-                                       double x2,
-                                       double y2,
-                                       const OrthancStone::CoordinateSystem3D& plane) : 
-    x1_(x1),
-    y1_(y1),
-    x2_(x2),
-    y2_(y2),
-    plane_(plane)
-  {
-    RenderStyle style;
-    SetLayerStyle(style);
-  }
-
-
-  bool LineLayerRenderer::RenderLayer(OrthancStone::CairoContext& context,
-                                      const ViewportGeometry& view)
-  {
-    if (visible_)
-    {
-      context.SetSourceColor(color_);
-
-      cairo_t *cr = context.GetObject();
-      cairo_set_line_width(cr, 1.0 / view.GetZoom());
-      cairo_move_to(cr, x1_, y1_);
-      cairo_line_to(cr, x2_, y2_);
-      cairo_stroke(cr);
-    }
-
-    return true;
-  }
-
-
-  void LineLayerRenderer::SetLayerStyle(const RenderStyle& style)
-  {
-    visible_ = style.visible_;
-    color_[0] = style.drawColor_[0];
-    color_[1] = style.drawColor_[1];
-    color_[2] = style.drawColor_[2];
-  }
-}
--- a/Framework/Layers/LineLayerRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILayerRenderer.h"
-
-namespace Deprecated
-{
-  class LineLayerRenderer : public ILayerRenderer
-  {
-  private:
-    double              x1_;
-    double              y1_;
-    double              x2_;
-    double              y2_;
-    OrthancStone::CoordinateSystem3D  plane_;
-    bool                visible_;
-    uint8_t             color_[3];
-
-  public:
-    LineLayerRenderer(double x1,
-                      double y1,
-                      double x2,
-                      double y2,
-                      const OrthancStone::CoordinateSystem3D& plane);
-
-    virtual bool RenderLayer(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view);
-
-    virtual void SetLayerStyle(const RenderStyle& style);
-
-    virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane()
-    {
-      return plane_;
-    }
-    
-    virtual bool IsFullQuality()
-    {
-      return true;
-    }
-  };
-}
--- a/Framework/Layers/LineMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "LineMeasureTracker.h"
-
-#include <stdio.h>
-
-namespace Deprecated
-{
-  LineMeasureTracker::LineMeasureTracker(IStatusBar* statusBar,
-                                         const OrthancStone::CoordinateSystem3D& slice,
-                                         double x, 
-                                         double y,
-                                         uint8_t red,
-                                         uint8_t green,
-                                         uint8_t blue,
-                                         const Orthanc::Font& font) :
-    statusBar_(statusBar),
-    slice_(slice),
-    x1_(x),
-    y1_(y),
-    x2_(x),
-    y2_(y),
-    font_(font)
-  {
-    color_[0] = red;
-    color_[1] = green;
-    color_[2] = blue;
-  }
-    
-
-  void LineMeasureTracker::Render(OrthancStone::CairoContext& context,
-                                  double zoom)
-  {
-    context.SetSourceColor(color_[0], color_[1], color_[2]);
-
-    cairo_t* cr = context.GetObject();
-    cairo_set_line_width(cr, 2.0 / zoom);
-    cairo_move_to(cr, x1_, y1_);
-    cairo_line_to(cr, x2_, y2_);
-    cairo_stroke(cr);
-
-    if (y2_ - y1_ < 0)
-    {
-      context.DrawText(font_, FormatLength(), x2_, y2_ - 5, OrthancStone::BitmapAnchor_BottomCenter);
-    }
-    else
-    {
-      context.DrawText(font_, FormatLength(), x2_, y2_ + 5, OrthancStone::BitmapAnchor_TopCenter);
-    }
-  }
-    
-
-  double LineMeasureTracker::GetLength() const  // In millimeters
-  {
-    OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_);
-    OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_);
-    return boost::numeric::ublas::norm_2(b - a);
-  }
-
-
-  std::string LineMeasureTracker::FormatLength() const
-  {
-    char buf[64];
-    sprintf(buf, "%0.01f cm", GetLength() / 10.0);
-    return buf;
-  }
-
-  void LineMeasureTracker::MouseMove(int displayX,
-                                     int displayY,
-                                     double x,
-                                     double y,
-                                     const std::vector<Touch>& displayTouches,
-                                     const std::vector<Touch>& sceneTouches)
-  {
-    x2_ = x;
-    y2_ = y;
-
-    if (statusBar_ != NULL)
-    {
-      statusBar_->SetMessage("Line length: " + FormatLength());
-    }
-  }
-}
--- a/Framework/Layers/LineMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Widgets/IWorldSceneMouseTracker.h"
-
-#include "../Viewport/IStatusBar.h"
-#include "../Toolbox/CoordinateSystem3D.h"
-
-namespace Deprecated
-{
-  class LineMeasureTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    IStatusBar*           statusBar_;
-    OrthancStone::CoordinateSystem3D    slice_;
-    double                x1_;
-    double                y1_;
-    double                x2_;
-    double                y2_;
-    uint8_t               color_[3];
-    unsigned int          fontSize_;
-    const Orthanc::Font&  font_;
-
-  public:
-    LineMeasureTracker(IStatusBar* statusBar,
-                       const OrthancStone::CoordinateSystem3D& slice,
-                       double x, 
-                       double y,
-                       uint8_t red,
-                       uint8_t green,
-                       uint8_t blue,
-                       const Orthanc::Font& font);
-
-    virtual bool HasRender() const
-    {
-      return true;
-    }
-
-    virtual void Render(OrthancStone::CairoContext& context,
-                        double zoom);
-    
-    double GetLength() const;  // In millimeters
-
-    std::string FormatLength() const;
-
-    virtual void MouseUp()
-    {
-      // Possibly create a new landmark "volume" with the line in subclasses
-    }
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double x,
-                           double y,
-                           const std::vector<Touch>& displayTouches,
-                           const std::vector<Touch>& sceneTouches);
-  };
-}
--- a/Framework/Layers/RenderStyle.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "RenderStyle.h"
-
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  RenderStyle::RenderStyle()
-  {
-    visible_ = true;
-    reverse_ = false;
-    windowing_ = OrthancStone::ImageWindowing_Custom;
-    alpha_ = 1;
-    applyLut_ = false;
-    lut_ = Orthanc::EmbeddedResources::COLORMAP_HOT;
-    drawGrid_ = false;
-    drawColor_[0] = 255;
-    drawColor_[1] = 255;
-    drawColor_[2] = 255;
-    customWindowCenter_ = 128;
-    customWindowWidth_ = 256;
-    interpolation_ = OrthancStone::ImageInterpolation_Nearest;
-    fontSize_ = 14;
-  }
-
-
-  void RenderStyle::ComputeWindowing(float& targetCenter,
-                                     float& targetWidth,
-                                     float defaultCenter,
-                                     float defaultWidth) const
-  {
-    if (windowing_ == OrthancStone::ImageWindowing_Custom)
-    {
-      targetCenter = customWindowCenter_;
-      targetWidth = customWindowWidth_;
-    }
-    else
-    {
-      return ::OrthancStone::ComputeWindowing
-        (targetCenter, targetWidth, windowing_, defaultCenter, defaultWidth);
-    }
-  }
-
-  
-  void RenderStyle::SetColor(uint8_t red,
-                             uint8_t green,
-                             uint8_t blue)
-  {
-    drawColor_[0] = red;
-    drawColor_[1] = green;
-    drawColor_[2] = blue;
-  }
-}
--- a/Framework/Layers/RenderStyle.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../StoneEnumerations.h"
-
-#include <EmbeddedResources.h>
-
-#include <stdint.h>
-
-namespace Deprecated
-{
-  struct RenderStyle
-  {
-    bool visible_;
-    bool reverse_;
-    OrthancStone::ImageWindowing windowing_;
-    float alpha_;   // In [0,1]
-    bool applyLut_;
-    Orthanc::EmbeddedResources::FileResourceId  lut_;
-    bool drawGrid_;
-    uint8_t drawColor_[3];
-    float customWindowCenter_;
-    float customWindowWidth_;
-    OrthancStone::ImageInterpolation interpolation_;
-    unsigned int fontSize_;
-    
-    RenderStyle();
-
-    void ComputeWindowing(float& targetCenter,
-                          float& targetWidth,
-                          float defaultCenter,
-                          float defaultWidth) const;
-
-    void SetColor(uint8_t red,
-                  uint8_t green,
-                  uint8_t blue);
-  };
-}
--- a/Framework/Layers/SeriesFrameRendererFactory.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,177 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SeriesFrameRendererFactory.h"
-
-#include "FrameRenderer.h"
-
-#include <OrthancException.h>
-#include <Logging.h>
-#include <Toolbox.h>
-#include <Plugins/Samples/Common/OrthancPluginException.h>
-#include <Plugins/Samples/Common/DicomDatasetReader.h>
-
-
-namespace Deprecated
-{
-  void SeriesFrameRendererFactory::ReadCurrentFrameDataset(size_t frame)
-  {
-    if (currentDataset_.get() != NULL &&
-        (fast_ || currentFrame_ == frame))
-    {
-      // The frame has not changed since the previous call, no need to
-      // update the DICOM dataset
-      return; 
-    }
-      
-    currentDataset_.reset(loader_->DownloadDicom(frame));
-    currentFrame_ = frame;
-
-    if (currentDataset_.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  void SeriesFrameRendererFactory::GetCurrentPixelSpacing(double& spacingX,
-                                                          double& spacingY) const
-  {
-    if (currentDataset_.get() == NULL)
-    {
-      // There was no previous call "ReadCurrentFrameDataset()"
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    
-    GeometryToolbox::GetPixelSpacing(spacingX, spacingY, *currentDataset_);
-  }
-
-
-  double SeriesFrameRendererFactory::GetCurrentSliceThickness() const
-  {
-    if (currentDataset_.get() == NULL)
-    {
-      // There was no previous call "ReadCurrentFrameDataset()"
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    
-    try
-    {
-      OrthancPlugins::DicomDatasetReader reader(*currentDataset_);
-
-      double thickness;
-      if (reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS))
-      {
-        return thickness;
-      }
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-    }
-
-    // Some arbitrary large slice thickness
-    return std::numeric_limits<double>::infinity();
-  }
-
-
-  SeriesFrameRendererFactory::SeriesFrameRendererFactory(ISeriesLoader* loader,   // Takes ownership
-                                                         bool fast) :
-    loader_(loader),
-    currentFrame_(0),
-    fast_(fast)
-  {
-    if (loader == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  bool SeriesFrameRendererFactory::GetExtent(double& x1,
-                                             double& y1,
-                                             double& x2,
-                                             double& y2,
-                                             const SliceGeometry& viewportSlice)
-  {
-    if (currentDataset_.get() == NULL)
-    {
-      // There has been no previous call to
-      // "CreateLayerRenderer". Read some arbitrary DICOM frame, the
-      // one at the middle of the series.
-      unsigned int depth = loader_->GetGeometry().GetSliceCount();
-      ReadCurrentFrameDataset(depth / 2);
-    }
-
-    double spacingX, spacingY;
-    GetCurrentPixelSpacing(spacingX, spacingY);
-
-    return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, 
-                                             viewportSlice, 
-                                             loader_->GetGeometry().GetSlice(0), 
-                                             loader_->GetWidth(), 
-                                             loader_->GetHeight(),
-                                             spacingX, spacingY);
-  }
-
-
-  ILayerRenderer* SeriesFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
-  {
-    size_t closest;
-    double distance;
-
-    bool isOpposite;
-    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, loader_->GetGeometry().GetNormal(), viewportSlice.GetNormal()) ||
-        !loader_->GetGeometry().ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin()))
-    {
-      // Unable to compute the slice in the series that is the
-      // closest to the slice displayed by the viewport
-      return NULL;
-    }
-
-    ReadCurrentFrameDataset(closest);
-    assert(currentDataset_.get() != NULL);
-        
-    double spacingX, spacingY;
-    GetCurrentPixelSpacing(spacingX, spacingY);
-
-    if (distance <= GetCurrentSliceThickness() / 2.0)
-    {
-      SliceGeometry frameSlice(*currentDataset_);
-      return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), 
-                                           frameSlice,
-                                           *currentDataset_, 
-                                           spacingX, spacingY,
-                                           true);
-    }
-    else
-    {
-      // The closest slice of the series is too far away from the
-      // slice displayed by the viewport
-      return NULL;
-    }
-  }
-
-
-  ISliceableVolume& SeriesFrameRendererFactory::GetSourceVolume() const
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-  }
-}
--- a/Framework/Layers/SeriesFrameRendererFactory.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILayerRendererFactory.h"
-
-#include "../Toolbox/ISeriesLoader.h"
-
-namespace Deprecated
-{
-  class SeriesFrameRendererFactory : public ILayerRendererFactory
-  {
-  private:
-    std::auto_ptr<ISeriesLoader>  loader_;
-    size_t                        currentFrame_;
-    bool                          fast_;
-
-    std::auto_ptr<OrthancPlugins::IDicomDataset>  currentDataset_;
-
-    void ReadCurrentFrameDataset(size_t frame);
-
-    void GetCurrentPixelSpacing(double& spacingX,
-                                double& spacingY) const;
-
-    double GetCurrentSliceThickness() const;
-
-  public:
-    SeriesFrameRendererFactory(ISeriesLoader* loader,   // Takes ownership
-                               bool fast);
-
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
-                           const SliceGeometry& viewportSlice);
-
-    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
-
-    virtual bool HasSourceVolume() const
-    {
-      return false;
-    }
-
-    virtual ISliceableVolume& GetSourceVolume() const;
-  };
-}
--- a/Framework/Layers/SingleFrameRendererFactory.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SingleFrameRendererFactory.h"
-
-#include "FrameRenderer.h"
-#include "../Toolbox/MessagingToolbox.h"
-#include "../Toolbox/DicomFrameConverter.h"
-
-#include <OrthancException.h>
-#include <Plugins/Samples/Common/FullOrthancDataset.h>
-#include <Plugins/Samples/Common/DicomDatasetReader.h>
-
-namespace Deprecated
-{
-  SingleFrameRendererFactory::SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc,
-                                                         const std::string& instanceId,
-                                                         unsigned int frame) :
-    orthanc_(orthanc),
-    instance_(instanceId),
-    frame_(frame)
-  {
-    dicom_.reset(new OrthancPlugins::FullOrthancDataset(orthanc, "/instances/" + instanceId + "/tags"));
-
-    DicomFrameConverter converter;
-    converter.ReadParameters(*dicom_);
-    format_ = converter.GetExpectedPixelFormat();
-  }
-
-
-  bool SingleFrameRendererFactory::GetExtent(double& x1,
-                                             double& y1,
-                                             double& x2,
-                                             double& y2,
-                                             const SliceGeometry& viewportSlice)
-  {
-    // Assume that PixelSpacingX == PixelSpacingY == 1
-
-    OrthancPlugins::DicomDatasetReader reader(*dicom_);
-
-    unsigned int width, height;
-
-    if (!reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) ||
-        !reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    x1 = 0;
-    y1 = 0;
-    x2 = static_cast<double>(width);
-    y2 = static_cast<double>(height);
-
-    return true;
-  }
-
-
-  ILayerRenderer* SingleFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
-  {
-    SliceGeometry frameSlice(*dicom_);
-    return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), 
-                                         frameSlice, *dicom_, 1, 1, true);
-  }
-
-
-  ISliceableVolume& SingleFrameRendererFactory::GetSourceVolume() const
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-}
--- a/Framework/Layers/SingleFrameRendererFactory.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILayerRendererFactory.h"
-#include <Plugins/Samples/Common/IOrthancConnection.h>
-
-namespace Deprecated
-{
-  class SingleFrameRendererFactory : public ILayerRendererFactory
-  {
-  private:
-    OrthancPlugins::IOrthancConnection&           orthanc_;
-    std::auto_ptr<OrthancPlugins::IDicomDataset>  dicom_;
-
-    std::string           instance_;
-    unsigned int          frame_;
-    Orthanc::PixelFormat  format_;
-
-  public:
-    SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc,
-                               const std::string& instanceId,
-                               unsigned int frame);
-
-    const OrthancPlugins::IDicomDataset& GetDataset() const
-    {
-      return *dicom_;
-    }
-
-    SliceGeometry GetSliceGeometry()
-    {
-      return SliceGeometry(*dicom_);
-    }
-
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
-                           const SliceGeometry& viewportSlice);
-
-    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);
-
-    virtual bool HasSourceVolume() const
-    {
-      return false;
-    }
-
-    virtual ISliceableVolume& GetSourceVolume() const;
-  };
-}
--- a/Framework/Layers/SliceOutlineRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SliceOutlineRenderer.h"
-
-namespace Deprecated
-{
-  bool SliceOutlineRenderer::RenderLayer(OrthancStone::CairoContext& context,
-                                         const ViewportGeometry& view)
-  {
-    if (style_.visible_)
-    {
-      cairo_t *cr = context.GetObject();
-      cairo_save(cr);
-
-      context.SetSourceColor(style_.drawColor_);
-
-      double x1 = -0.5 * pixelSpacingX_;
-      double y1 = -0.5 * pixelSpacingY_;
-        
-      cairo_set_line_width(cr, 1.0 / view.GetZoom());
-      cairo_rectangle(cr, x1, y1,
-                      static_cast<double>(width_) * pixelSpacingX_,
-                      static_cast<double>(height_) * pixelSpacingY_);
-
-      double handleSize = 10.0f / view.GetZoom();
-      cairo_move_to(cr, x1 + handleSize, y1);
-      cairo_line_to(cr, x1, y1 + handleSize);
-
-      cairo_stroke(cr);
-      cairo_restore(cr);
-    }
-
-    return true;
-  }
-}
--- a/Framework/Layers/SliceOutlineRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ILayerRenderer.h"
-#include "../Toolbox/Slice.h"
-
-namespace Deprecated
-{
-  class SliceOutlineRenderer : public ILayerRenderer
-  {
-  private:
-    OrthancStone::CoordinateSystem3D  geometry_;
-    double              pixelSpacingX_;
-    double              pixelSpacingY_;
-    unsigned int        width_;
-    unsigned int        height_;
-    RenderStyle         style_;
-
-  public:
-    SliceOutlineRenderer(const Slice& slice) :
-      geometry_(slice.GetGeometry()),
-      pixelSpacingX_(slice.GetPixelSpacingX()),
-      pixelSpacingY_(slice.GetPixelSpacingY()),
-      width_(slice.GetWidth()),
-      height_(slice.GetHeight())
-    {
-    }
-
-    virtual bool RenderLayer(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view);
-
-    virtual void SetLayerStyle(const RenderStyle& style)
-    {
-      style_ = style;
-    }
-
-    virtual const OrthancStone::CoordinateSystem3D& GetLayerSlice()
-    {
-      return geometry_;
-    }
-
-    virtual bool IsFullQuality()
-    {
-      return true;
-    }
-  };
-}
--- a/Framework/Loaders/BasicFetchingItemsSorter.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Loaders/BasicFetchingItemsSorter.h	Mon Jun 24 14:35:00 2019 +0200
@@ -31,6 +31,15 @@
     unsigned int  itemsCount_;
 
   public:
+    class Factory : public IFactory
+    {
+    public:
+      virtual IFetchingItemsSorter* CreateSorter(unsigned int itemsCount) const
+      {
+        return new BasicFetchingItemsSorter(itemsCount);
+      }
+    };
+
     BasicFetchingItemsSorter(unsigned int itemsCount);
 
     virtual unsigned int GetItemsCount() const
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/DicomStructureSetLoader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,264 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomStructureSetLoader.h"
+
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Toolbox/GeometryToolbox.h"
+
+namespace OrthancStone
+{
+  class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State
+  {
+  private:
+    std::string instanceId_;
+      
+  public:
+    AddReferencedInstance(DicomStructureSetLoader& that,
+                          const std::string& instanceId) :
+      State(that),
+      instanceId_(instanceId)
+    {
+    }
+
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      Json::Value tags;
+      message.ParseJsonBody(tags);
+        
+      Orthanc::DicomMap dicom;
+      dicom.FromDicomAsJson(tags);
+
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+      loader.content_->AddReferencedSlice(dicom);
+
+      loader.countProcessedInstances_ ++;
+      assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);
+
+      if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
+      {
+        // All the referenced instances have been loaded, finalize the RT-STRUCT
+        loader.content_->CheckReferencedSlices();
+        loader.revision_++;
+      }
+    }
+  };
+
+
+  // State that converts a "SOP Instance UID" to an Orthanc identifier
+  class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State
+  {
+  private:
+    std::string  sopInstanceUid_;
+      
+  public:
+    LookupInstance(DicomStructureSetLoader& that,
+                   const std::string& sopInstanceUid) :
+      State(that),
+      sopInstanceUid_(sopInstanceUid)
+    {
+    }
+
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+
+      Json::Value lookup;
+      message.ParseJsonBody(lookup);
+
+      if (lookup.type() != Json::arrayValue ||
+          lookup.size() != 1 ||
+          !lookup[0].isMember("Type") ||
+          !lookup[0].isMember("Path") ||
+          lookup[0]["Type"].type() != Json::stringValue ||
+          lookup[0]["ID"].type() != Json::stringValue ||
+          lookup[0]["Type"].asString() != "Instance")
+      {
+        std::stringstream msg;
+        msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = ";
+        for (OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin();
+             it != message.GetAnswerHeaders().end(); ++it)
+        {
+          msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n";
+        }
+        const std::string msgStr = msg.str();
+        LOG(ERROR) << msgStr;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
+      }
+
+      const std::string instanceId = lookup[0]["ID"].asString();
+
+      {
+        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+        command->SetHttpHeader("Accept-Encoding", "gzip");
+        command->SetUri("/instances/" + instanceId + "/tags");
+        command->SetPayload(new AddReferencedInstance(loader, instanceId));
+        Schedule(command.release());
+      }
+    }
+  };
+
+
+  class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State
+  {
+  public:
+    LoadStructure(DicomStructureSetLoader& that) :
+    State(that)
+    {
+    }
+
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+        
+      {
+        OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
+        loader.content_.reset(new DicomStructureSet(dicom));
+      }
+
+      std::set<std::string> instances;
+      loader.content_->GetReferencedInstances(instances);
+
+      loader.countReferencedInstances_ = static_cast<unsigned int>(instances.size());
+
+      for (std::set<std::string>::const_iterator
+             it = instances.begin(); it != instances.end(); ++it)
+      {
+        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+        command->SetUri("/tools/lookup");
+        command->SetMethod(Orthanc::HttpMethod_Post);
+        command->SetBody(*it);
+        command->SetPayload(new LookupInstance(loader, *it));
+        //LOG(TRACE) << "About to schedule a /tools/lookup POST request. URI = " << command->GetUri() << " Body size = " << (*it).size() << " Body = " << (*it) << "\n";
+        Schedule(command.release());
+      }
+    }
+  };
+    
+
+  class DicomStructureSetLoader::Slice : public IExtractedSlice
+  {
+  private:
+    const DicomStructureSet&  content_;
+    uint64_t                  revision_;
+    bool                      isValid_;
+      
+  public:
+    Slice(const DicomStructureSet& content,
+          uint64_t revision,
+          const CoordinateSystem3D& cuttingPlane) :
+      content_(content),
+      revision_(revision)
+    {
+      bool opposite;
+
+      const Vector normal = content.GetNormal();
+      isValid_ = (
+        GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
+        GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
+        GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
+    }
+      
+    virtual bool IsValid()
+    {
+      return isValid_;
+    }
+
+    virtual uint64_t GetRevision()
+    {
+      return revision_;
+    }
+
+    virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                          const CoordinateSystem3D& cuttingPlane)
+    {
+      assert(isValid_);
+
+      std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
+      layer->SetThickness(2);
+
+      for (size_t i = 0; i < content_.GetStructuresCount(); i++)
+      {
+        const Color& color = content_.GetStructureColor(i);
+
+        std::vector< std::vector<DicomStructureSet::PolygonPoint> > polygons;
+          
+        if (content_.ProjectStructure(polygons, i, cuttingPlane))
+        {
+          for (size_t j = 0; j < polygons.size(); j++)
+          {
+            PolylineSceneLayer::Chain chain;
+            chain.resize(polygons[j].size());
+            
+            for (size_t k = 0; k < polygons[j].size(); k++)
+            {
+              chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second);
+            }
+
+            layer->AddChain(chain, true /* closed */, color);
+          }
+        }
+      }
+
+      return layer.release();
+    }
+  };
+    
+
+  DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle,
+                                                   IObservable& oracleObservable) :
+    LoaderStateMachine(oracle, oracleObservable),
+    revision_(0),
+    countProcessedInstances_(0),
+    countReferencedInstances_(0)
+  {
+  }
+    
+    
+  void DicomStructureSetLoader::LoadInstance(const std::string& instanceId)
+  {
+    Start();
+      
+    instanceId_ = instanceId;
+      
+    {
+      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3006-0050");
+      command->SetPayload(new LoadStructure(*this));
+      Schedule(command.release());
+    }
+  }
+
+
+  IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
+  {
+    if (content_.get() == NULL)
+    {
+      // Geometry is not available yet
+      return new IVolumeSlicer::InvalidSlice;
+    }
+    else
+    {
+      return new Slice(*content_, revision_, cuttingPlane);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/DicomStructureSetLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,56 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/DicomStructureSet.h"
+#include "../Volumes/IVolumeSlicer.h"
+#include "LoaderStateMachine.h"
+
+namespace OrthancStone
+{
+  class DicomStructureSetLoader :
+    public LoaderStateMachine,
+    public IVolumeSlicer
+  {
+  private:
+    class Slice;
+
+    // States of LoaderStateMachine
+    class AddReferencedInstance;   // 3rd state
+    class LookupInstance;          // 2nd state
+    class LoadStructure;           // 1st state
+    
+    std::auto_ptr<DicomStructureSet>  content_;
+    uint64_t                          revision_;
+    std::string                       instanceId_;
+    unsigned int                      countProcessedInstances_;
+    unsigned int                      countReferencedInstances_;  
+    
+  public:
+    DicomStructureSetLoader(IOracle& oracle,
+                            IObservable& oracleObservable);    
+    
+    void LoadInstance(const std::string& instanceId);
+
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane);
+  };
+}
--- a/Framework/Loaders/IFetchingItemsSorter.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Loaders/IFetchingItemsSorter.h	Mon Jun 24 14:35:00 2019 +0200
@@ -29,6 +29,16 @@
   class IFetchingItemsSorter : public boost::noncopyable
   {
   public:
+    class IFactory : public boost::noncopyable
+    {
+    public:
+      virtual ~IFactory()
+      {
+      }
+
+      virtual IFetchingItemsSorter* CreateSorter(unsigned int itemsCount) const = 0;
+    };
+
     virtual ~IFetchingItemsSorter()
     {
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/LoaderStateMachine.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,177 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LoaderStateMachine.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void LoaderStateMachine::State::Handle(const OrthancRestApiCommand::SuccessMessage& message)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+      
+
+  void LoaderStateMachine::State::Handle(const GetOrthancImageCommand::SuccessMessage& message)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+      
+  void LoaderStateMachine::State::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+
+  void LoaderStateMachine::Schedule(OracleCommandWithPayload* command)
+  {
+    std::auto_ptr<OracleCommandWithPayload> protection(command);
+
+    if (command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+      
+    if (!command->HasPayload())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "The payload must contain the next state");
+    }
+
+    pendingCommands_.push_back(protection.release());
+    Step();
+  }
+
+
+  void LoaderStateMachine::Start()
+  {
+    if (active_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    active_ = true;
+
+    for (size_t i = 0; i < simultaneousDownloads_; i++)
+    {
+      Step();
+    }
+  }
+
+
+  void LoaderStateMachine::Step()
+  {
+    if (!pendingCommands_.empty() &&
+        activeCommands_ < simultaneousDownloads_)
+    {
+      oracle_.Schedule(*this, pendingCommands_.front());
+      pendingCommands_.pop_front();
+
+      activeCommands_++;
+    }
+  }
+
+
+  void LoaderStateMachine::Clear()
+  {
+    for (PendingCommands::iterator it = pendingCommands_.begin();
+         it != pendingCommands_.end(); ++it)
+    {
+      delete *it;
+    }
+
+    pendingCommands_.clear();
+  }
+
+
+  void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
+  {
+    LOG(ERROR) << "Error in the state machine, stopping all processing";
+    LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " <<
+      message.GetException().GetDetails();
+      Clear();
+  }
+
+
+  template <typename T>
+  void LoaderStateMachine::HandleSuccessMessage(const T& message)
+  {
+    assert(activeCommands_ > 0);
+    activeCommands_--;
+
+    try
+    {
+      dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message);
+      Step();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Error in the state machine, stopping all processing: " << 
+        e.What() << " Details: " << e.GetDetails();
+      Clear();
+    }
+  }
+
+
+  LoaderStateMachine::LoaderStateMachine(IOracle& oracle,
+                                         IObservable& oracleObservable) :
+    IObserver(oracleObservable.GetBroker()),
+    oracle_(oracle),
+    active_(false),
+    simultaneousDownloads_(4),
+    activeCommands_(0)
+  {
+    oracleObservable.RegisterObserverCallback(
+      new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage>
+      (*this, &LoaderStateMachine::HandleSuccessMessage));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage>
+      (*this, &LoaderStateMachine::HandleSuccessMessage));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage>
+      (*this, &LoaderStateMachine::HandleSuccessMessage));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<LoaderStateMachine, OracleCommandExceptionMessage>
+      (*this, &LoaderStateMachine::HandleExceptionMessage));
+  }
+
+
+  void LoaderStateMachine::SetSimultaneousDownloads(unsigned int count)
+  {
+    if (active_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (count == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
+    }
+    else
+    {
+      simultaneousDownloads_ = count;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/LoaderStateMachine.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,119 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IObservable.h"
+#include "../Messages/IObserver.h"
+#include "../Oracle/GetOrthancImageCommand.h"
+#include "../Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../Oracle/IOracle.h"
+#include "../Oracle/OracleCommandExceptionMessage.h"
+#include "../Oracle/OrthancRestApiCommand.h"
+
+#include <Core/IDynamicObject.h>
+
+#include <list>
+
+namespace OrthancStone
+{
+  /**
+     This class is supplied with Oracle commands and will schedule up to 
+     simultaneousDownloads_ of them at the same time, then will schedule the 
+     rest once slots become available. It is used, a.o., by the 
+     OrtancMultiframeVolumeLoader class.
+  */
+  class LoaderStateMachine : public IObserver
+  {
+  protected:
+    class State : public Orthanc::IDynamicObject
+    {
+    private:
+      LoaderStateMachine&  that_;
+
+    public:
+      State(LoaderStateMachine& that) :
+      that_(that)
+      {
+      }
+
+      State(const State& currentState) :
+      that_(currentState.that_)
+      {
+      }
+
+      void Schedule(OracleCommandWithPayload* command) const
+      {
+        that_.Schedule(command);
+      }
+
+      template <typename T>
+      T& GetLoader() const
+      {
+        return dynamic_cast<T&>(that_);
+      }
+      
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message);
+      
+      virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message);
+      
+      virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message);
+    };
+
+    void Schedule(OracleCommandWithPayload* command);
+
+    void Start();
+
+  private:
+    void Step();
+
+    void Clear();
+
+    void HandleExceptionMessage(const OracleCommandExceptionMessage& message);
+
+    template <typename T>
+    void HandleSuccessMessage(const T& message);
+
+    typedef std::list<IOracleCommand*>  PendingCommands;
+
+    IOracle&         oracle_;
+    bool             active_;
+    unsigned int     simultaneousDownloads_;
+    PendingCommands  pendingCommands_;
+    unsigned int     activeCommands_;
+
+  public:
+    LoaderStateMachine(IOracle& oracle,
+                       IObservable& oracleObservable);
+
+    virtual ~LoaderStateMachine()
+    {
+      Clear();
+    }
+
+    bool IsActive() const
+    {
+      return active_;
+    }
+
+    void SetSimultaneousDownloads(unsigned int count);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,356 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancMultiframeVolumeLoader.h"
+
+#include <Core/Endianness.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+  class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State
+  {
+  private:
+    std::auto_ptr<Orthanc::DicomMap>  dicom_;
+
+  public:
+    LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
+                       Orthanc::DicomMap* dicom) :
+      State(that),
+      dicom_(dicom)
+    {
+      if (dicom == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+    }
+
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
+      std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
+      dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
+
+      GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
+    }      
+  };
+
+
+  static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
+  {
+    std::string s;
+    if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "DICOM file without SOP class UID");
+    }
+    else
+    {
+      return s;
+    }
+  }
+    
+
+  class OrthancMultiframeVolumeLoader::LoadGeometry : public State
+  {
+  public:
+    LoadGeometry(OrthancMultiframeVolumeLoader& that) :
+    State(that)
+    {
+    }
+      
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
+        
+      Json::Value body;
+      message.ParseJsonBody(body);
+        
+      if (body.type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
+      dicom->FromDicomAsJson(body);
+
+      if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose)
+      {
+        // Download the "Grid Frame Offset Vector" DICOM tag, that is
+        // mandatory for RT-DOSE, but is too long to be returned by default
+          
+        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+        command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
+                        Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
+        command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release()));
+
+        Schedule(command.release());
+      }
+      else
+      {
+        loader.SetGeometry(*dicom);
+      }
+    }
+  };
+
+
+
+  class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State
+  {
+  public:
+    LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
+    State(that)
+    {
+    }
+      
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
+    }
+  };
+   
+    
+  class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State
+  {
+  public:
+    LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
+    State(that)
+    {
+    }
+      
+    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+    {
+      GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
+    }
+  };
+   
+    
+  const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const
+  {
+    if (IsActive())
+    {
+      return instanceId_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads()
+  {
+    if (transferSyntaxUid_.empty() ||
+        !volume_->HasGeometry())
+    {
+      return;
+    }
+    /*
+      1.2.840.10008.1.2	Implicit VR Endian: Default Transfer Syntax for DICOM
+      1.2.840.10008.1.2.1	Explicit VR Little Endian
+      1.2.840.10008.1.2.2	Explicit VR Big Endian
+
+      See https://www.dicomlibrary.com/dicom/transfer-syntax/
+    */
+    if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
+        transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
+        transferSyntaxUid_ == "1.2.840.10008.1.2.2")
+    {
+      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetUri("/instances/" + instanceId_ + "/content/" +
+                      Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
+      command->SetPayload(new LoadUncompressedPixelData(*this));
+      Schedule(command.release());
+    }
+    else
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NotImplemented,
+        "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
+    }
+  }
+      
+
+  void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax)
+  {
+    transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
+    ScheduleFrameDownloads();
+  }
+    
+
+  void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom)
+  {
+    DicomInstanceParameters parameters(dicom);
+    volume_->SetDicomParameters(parameters);
+      
+    Orthanc::PixelFormat format;
+    if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    double spacingZ;
+    switch (parameters.GetSopClassUid())
+    {
+      case SopClassUid_RTDose:
+        spacingZ = parameters.GetThickness();
+        break;
+
+      default:
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_NotImplemented,
+          "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
+    }
+
+    const unsigned int width = parameters.GetImageInformation().GetWidth();
+    const unsigned int height = parameters.GetImageInformation().GetHeight();
+    const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
+
+    {
+      VolumeImageGeometry geometry;
+      geometry.SetSize(width, height, depth);
+      geometry.SetAxialGeometry(parameters.GetGeometry());
+      geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
+                                  parameters.GetPixelSpacingY(), spacingZ);
+      volume_->Initialize(geometry, format);
+    }
+
+    volume_->GetPixelData().Clear();
+
+    ScheduleFrameDownloads();
+
+    BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
+  }
+
+
+  ORTHANC_FORCE_INLINE
+  static void CopyPixel(uint32_t& target,
+                        const void* source)
+  {
+    // TODO - check alignement?
+    target = le32toh(*reinterpret_cast<const uint32_t*>(source));
+  }
+      
+
+  template <typename T>
+  void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData)
+  {
+    ImageBuffer3D& target = volume_->GetPixelData();
+      
+    const unsigned int bpp = target.GetBytesPerPixel();
+    const unsigned int width = target.GetWidth();
+    const unsigned int height = target.GetHeight();
+    const unsigned int depth = target.GetDepth();
+
+    if (pixelData.size() != bpp * width * height * depth)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "The pixel data has not the proper size");
+    }
+
+    if (pixelData.empty())
+    {
+      return;
+    }
+
+    const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
+
+    for (unsigned int z = 0; z < depth; z++)
+    {
+      ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z);
+
+      assert (writer.GetAccessor().GetWidth() == width &&
+              writer.GetAccessor().GetHeight() == height);
+
+      for (unsigned int y = 0; y < height; y++)
+      {
+        assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
+
+        T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
+
+        for (unsigned int x = 0; x < width; x++)
+        {
+          CopyPixel(*target, source);
+            
+          target ++;
+          source += bpp;
+        }
+      }
+    }
+  }
+    
+
+  void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData)
+  {
+    switch (volume_->GetPixelData().GetFormat())
+    {
+      case Orthanc::PixelFormat_Grayscale32:
+        CopyPixelData<uint32_t>(pixelData);
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    volume_->IncrementRevision();
+
+    BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
+  }
+
+
+  OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
+                                                               IOracle& oracle,
+                                                               IObservable& oracleObservable) :
+    LoaderStateMachine(oracle, oracleObservable),
+    IObservable(oracleObservable.GetBroker()),
+    volume_(volume)
+  {
+    if (volume.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId)
+  {
+    Start();
+
+    instanceId_ = instanceId;
+
+    {
+      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetUri("/instances/" + instanceId + "/tags");
+      command->SetPayload(new LoadGeometry(*this));
+      Schedule(command.release());
+    }
+
+    {
+      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+      command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
+      command->SetPayload(new LoadTransferSyntax(*this));
+      Schedule(command.release());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "LoaderStateMachine.h"
+#include "../Volumes/DicomVolumeImage.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  class OrthancMultiframeVolumeLoader :
+    public LoaderStateMachine,
+    public IObservable
+  {
+  private:
+    class LoadRTDoseGeometry;
+    class LoadGeometry;
+    class LoadTransferSyntax;    
+    class LoadUncompressedPixelData;
+
+    boost::shared_ptr<DicomVolumeImage>  volume_;
+    std::string                          instanceId_;
+    std::string                          transferSyntaxUid_;
+
+
+    const std::string& GetInstanceId() const;
+
+    void ScheduleFrameDownloads();
+
+    void SetTransferSyntax(const std::string& transferSyntax);
+
+    void SetGeometry(const Orthanc::DicomMap& dicom);
+
+    template <typename T>
+    void CopyPixelData(const std::string& pixelData);
+
+    void SetUncompressedPixelData(const std::string& pixelData);
+
+  public:
+    OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
+                                  IOracle& oracle,
+                                  IObservable& oracleObservable);
+
+    void LoadInstance(const std::string& instanceId);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,476 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancSeriesVolumeProgressiveLoader.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+#include "../Volumes/DicomVolumeImageMPRSlicer.h"
+#include "BasicFetchingItemsSorter.h"
+#include "BasicFetchingStrategy.h"
+
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice
+  {
+  private:
+    const OrthancSeriesVolumeProgressiveLoader&  that_;
+
+  public:
+    ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
+                   const CoordinateSystem3D& plane) :
+      DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
+      that_(that)
+    {
+      if (IsValid())
+      {
+        if (GetProjection() == VolumeProjection_Axial)
+        {
+          // For coronal and sagittal projections, we take the global
+          // revision of the volume because even if a single slice changes,
+          // this means the projection will yield a different result --> 
+          // we must increase the revision as soon as any slice changes 
+          SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex()));
+        }
+      
+        if (that_.strategy_.get() != NULL &&
+            GetProjection() == VolumeProjection_Axial)
+        {
+          that_.strategy_->SetCurrent(GetSliceIndex());
+        }
+      }
+    }
+  };
+
+    
+    
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index,
+                                                                        const DicomInstanceParameters& reference) const
+  {
+    const DicomInstanceParameters& slice = *slices_[index];
+      
+    if (!GeometryToolbox::IsParallel(
+          reference.GetGeometry().GetNormal(),
+          slice.GetGeometry().GetNormal()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                      "A slice in the volume image is not parallel to the others");
+    }
+
+    if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
+                                      "The pixel format changes across the slices of the volume image");
+    }
+
+    if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
+        reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
+                                      "The width/height of slices are not constant in the volume image");
+    }
+
+    if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
+        !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                      "The pixel spacing of the slices change across the volume image");
+    }
+  }
+
+    
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckVolume() const
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(slices_[i] != NULL);
+      if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "This class does not support multi-frame images");
+      }
+    }
+
+    if (slices_.size() != 0)
+    {
+      const DicomInstanceParameters& reference = *slices_[0];
+
+      for (size_t i = 1; i < slices_.size(); i++)
+      {
+        CheckSlice(i, reference);
+      }
+    }
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(slices_[i] != NULL);
+      delete slices_[i];
+    }
+
+    slices_.clear();
+    slicesRevision_.clear();
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const
+  {
+    if (!HasGeometry())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (index >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(slices_.size() == GetImageGeometry().GetDepth() &&
+             slices_.size() == slicesRevision_.size());
+    }
+  }
+
+
+  // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
+  // (called with the slices created in LoadGeometry)
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(SlicesSorter& slices)
+  {
+    Clear();
+      
+    if (!slices.Sort())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Cannot sort the 3D slices of a DICOM series");          
+    }
+
+    if (slices.GetSlicesCount() == 0)
+    {
+      geometry_.reset(new VolumeImageGeometry);
+    }
+    else
+    {
+      slices_.reserve(slices.GetSlicesCount());
+      slicesRevision_.resize(slices.GetSlicesCount(), 0);
+
+      for (size_t i = 0; i < slices.GetSlicesCount(); i++)
+      {
+        const DicomInstanceParameters& slice =
+          dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
+        slices_.push_back(new DicomInstanceParameters(slice));
+      }
+
+      CheckVolume();
+
+      const double spacingZ = slices.ComputeSpacingBetweenSlices();
+      LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
+      
+      const DicomInstanceParameters& parameters = *slices_[0];
+
+      geometry_.reset(new VolumeImageGeometry);
+      geometry_->SetSize(parameters.GetImageInformation().GetWidth(),
+                         parameters.GetImageInformation().GetHeight(),
+                         static_cast<unsigned int>(slices.GetSlicesCount()));
+      geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
+      geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
+                                    parameters.GetPixelSpacingY(), spacingZ);
+    }
+  }
+
+
+  const VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const
+  {
+    if (!HasGeometry())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      assert(slices_.size() == geometry_->GetDepth());
+      return *geometry_;
+    }
+  }
+
+
+  const DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const
+  {
+    CheckSliceIndex(index);
+    return *slices_[index];
+  }
+
+
+  uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const
+  {
+    CheckSliceIndex(index);
+    return slicesRevision_[index];
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index)
+  {
+    CheckSliceIndex(index);
+    slicesRevision_[index] ++;
+  }
+
+
+  static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
+  {
+    return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload()
+  {
+    assert(strategy_.get() != NULL);
+      
+    unsigned int sliceIndex, quality;
+      
+    if (strategy_->GetNext(sliceIndex, quality))
+    {
+      assert(quality <= BEST_QUALITY);
+
+      const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
+          
+      const std::string& instance = slice.GetOrthancInstanceIdentifier();
+      if (instance.empty())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      std::auto_ptr<OracleCommandWithPayload> command;
+        
+      if (quality == BEST_QUALITY)
+      {
+        std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand);
+        tmp->SetHttpHeader("Accept-Encoding", "gzip");
+        tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+        tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());
+        tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
+        command.reset(tmp.release());
+      }
+      else
+      {
+        std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand);
+        tmp->SetHttpHeader("Accept-Encoding", "gzip");
+        tmp->SetInstance(instance);
+        tmp->SetQuality((quality == 0 ? 50 : 90));
+        tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
+        command.reset(tmp.release());
+      }
+
+      command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
+      oracle_.Schedule(*this, command.release());
+    }
+  }
+
+/**
+   This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
+*/
+  void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
+  {
+    Json::Value body;
+    message.ParseJsonBody(body);
+      
+    if (body.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    {
+      Json::Value::Members instances = body.getMemberNames();
+
+      SlicesSorter slices;
+        
+      for (size_t i = 0; i < instances.size(); i++)
+      {
+        Orthanc::DicomMap dicom;
+        dicom.FromDicomAsJson(body[instances[i]]);
+
+        std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
+        instance->SetOrthancInstanceIdentifier(instances[i]);
+
+        // the 3D plane corresponding to the slice
+        CoordinateSystem3D geometry = instance->GetGeometry();
+        slices.AddSlice(geometry, instance.release());
+      }
+
+      seriesGeometry_.ComputeGeometry(slices);
+    }
+
+    size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
+
+    if (slicesCount == 0)
+    {
+      volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
+    }
+    else
+    {
+      const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
+        
+      volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
+      volume_->SetDicomParameters(parameters);
+      volume_->GetPixelData().Clear();
+
+      strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY));
+        
+      assert(simultaneousDownloads_ != 0);
+      for (unsigned int i = 0; i < simultaneousDownloads_; i++)
+      {
+        ScheduleNextSliceDownload();
+      }
+    }
+
+    slicesQuality_.resize(slicesCount, 0);
+
+    BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex,
+                                                             const Orthanc::ImageAccessor& image,
+                                                             unsigned int quality)
+  {
+    assert(sliceIndex < slicesQuality_.size() &&
+           slicesQuality_.size() == volume_->GetPixelData().GetDepth());
+      
+    if (quality >= slicesQuality_[sliceIndex])
+    {
+      {
+        ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex);
+        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
+      }
+
+      volume_->IncrementRevision();
+      seriesGeometry_.IncrementSliceRevision(sliceIndex);
+      slicesQuality_[sliceIndex] = quality;
+
+      BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
+    }
+
+    ScheduleNextSliceDownload();
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message)
+  {
+    SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY);
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  {
+    unsigned int quality;
+      
+    switch (message.GetOrigin().GetQuality())
+    {
+      case 50:
+        quality = LOW_QUALITY;
+        break;
+
+      case 90:
+        quality = MIDDLE_QUALITY;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+      
+    SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
+  }
+
+
+  OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
+                                                                             IOracle& oracle,
+                                                                             IObservable& oracleObservable) :
+    IObserver(oracleObservable.GetBroker()),
+    IObservable(oracleObservable.GetBroker()),
+    oracle_(oracle),
+    active_(false),
+    simultaneousDownloads_(4),
+    volume_(volume),
+    sorter_(new BasicFetchingItemsSorter::Factory)
+  {
+    oracleObservable.RegisterObserverCallback(
+      new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage>
+      (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage>
+      (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
+      (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent));
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count)
+  {
+    if (active_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (count == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
+    }
+    else
+    {
+      simultaneousDownloads_ = count;
+    }
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId)
+  {
+    if (active_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      active_ = true;
+
+      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+      command->SetUri("/series/" + seriesId + "/instances-tags");
+
+      oracle_.Schedule(*this, command.release());
+    }
+  }
+  
+
+  IVolumeSlicer::IExtractedSlice* 
+  OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
+  {
+    if (volume_->HasGeometry())
+    {
+      return new ExtractedSlice(*this, cuttingPlane);
+    }
+    else
+    {
+      return new IVolumeSlicer::InvalidSlice;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,134 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IObservable.h"
+#include "../Messages/IObserver.h"
+#include "../Oracle/GetOrthancImageCommand.h"
+#include "../Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../Oracle/IOracle.h"
+#include "../Oracle/OrthancRestApiCommand.h"
+#include "../Toolbox/SlicesSorter.h"
+#include "../Volumes/DicomVolumeImage.h"
+#include "../Volumes/IVolumeSlicer.h"
+#include "IFetchingItemsSorter.h"
+#include "IFetchingStrategy.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  /**
+    This class is used to manage the progressive loading of a volume that
+    is stored in a Dicom series.
+  */
+  class OrthancSeriesVolumeProgressiveLoader : 
+    public IObserver,
+    public IObservable,
+    public IVolumeSlicer
+  {
+  private:
+    static const unsigned int LOW_QUALITY = 0;
+    static const unsigned int MIDDLE_QUALITY = 1;
+    static const unsigned int BEST_QUALITY = 2;
+    
+    class ExtractedSlice;
+    
+    /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */
+    class SeriesGeometry : public boost::noncopyable
+    {
+    private:
+      void CheckSlice(size_t index,
+                      const DicomInstanceParameters& reference) const;
+    
+      void CheckVolume() const;
+
+      void Clear();
+
+      void CheckSliceIndex(size_t index) const;
+
+      std::auto_ptr<VolumeImageGeometry>     geometry_;
+      std::vector<DicomInstanceParameters*>  slices_;
+      std::vector<uint64_t>                  slicesRevision_;
+
+    public:
+      ~SeriesGeometry()
+      {
+        Clear();
+      }
+
+      void ComputeGeometry(SlicesSorter& slices);
+
+      bool HasGeometry() const
+      {
+        return geometry_.get() != NULL;
+      }
+
+      const VolumeImageGeometry& GetImageGeometry() const;
+
+      const DicomInstanceParameters& GetSliceParameters(size_t index) const;
+
+      uint64_t GetSliceRevision(size_t index) const;
+
+      void IncrementSliceRevision(size_t index);
+    };
+
+
+    void ScheduleNextSliceDownload();
+
+    void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message);
+
+    void SetSliceContent(unsigned int sliceIndex,
+                         const Orthanc::ImageAccessor& image,
+                         unsigned int quality);
+
+    void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message);
+
+    void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message);
+
+    IOracle&                                       oracle_;
+    bool                                           active_;
+    unsigned int                                   simultaneousDownloads_;
+    SeriesGeometry                                 seriesGeometry_;
+    boost::shared_ptr<DicomVolumeImage>            volume_;
+    std::auto_ptr<IFetchingItemsSorter::IFactory>  sorter_;
+    std::auto_ptr<IFetchingStrategy>               strategy_;
+    std::vector<unsigned int>                      slicesQuality_;
+
+
+  public:
+    OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
+                                         IOracle& oracle,
+                                         IObservable& oracleObservable);
+
+    void SetSimultaneousDownloads(unsigned int count);
+
+    void LoadSeries(const std::string& seriesId);
+
+    /**
+    When a slice is requested, the strategy algorithm (that defines the 
+    sequence of resources to be loaded from the server) is modified to 
+    take into account this request (this is done in the ExtractedSlice ctor)
+    */
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/IMessageEmitter.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,45 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IObserver.h"
+#include "IMessage.h"
+
+namespace OrthancStone
+{
+  /**
+   This class may be used to customize the way the messages are sent between
+   a source and a destination, for instance by the ThreadedOracle.
+
+   See the concrete class LockingEmitter for an example of when it is useful.
+   */
+  class IMessageEmitter : public boost::noncopyable
+  {
+  public:
+    virtual ~IMessageEmitter()
+    {
+    }
+
+    virtual void EmitMessage(const IObserver& observer,
+                             const IMessage& message) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/LockingEmitter.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,111 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "IMessageEmitter.h"
+#include "IObservable.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancStone
+{
+  /**
+   * This class is used when using the ThreadedOracle : since messages
+   * can be sent from multiple Oracle threads, this IMessageEmitter
+   * implementation serializes the callbacks.
+   * 
+   * The internal mutex used in Oracle messaging can also be used to 
+   * protect the application data. Thus, this class can be used as a single
+   * application-wide mutex.
+   */
+  class LockingEmitter : public IMessageEmitter
+  {
+  private:
+    boost::shared_mutex                mutex_;
+    MessageBroker        broker_;
+    IObservable          oracleObservable_;
+
+  public:
+    LockingEmitter() :
+      oracleObservable_(broker_)
+    {
+    }
+
+    MessageBroker& GetBroker()
+    {
+      return broker_;
+    }
+
+    virtual void EmitMessage(const IObserver& observer,
+      const IMessage& message) ORTHANC_OVERRIDE
+    {
+      try
+      {
+        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
+        oracleObservable_.EmitMessage(observer, message);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while emitting a message: " << e.What();
+      }
+    }
+
+
+    class ReaderLock : public boost::noncopyable
+    {
+    private:
+      LockingEmitter& that_;
+      boost::shared_lock<boost::shared_mutex>  lock_;
+
+    public:
+      ReaderLock(LockingEmitter& that) :
+        that_(that),
+        lock_(that.mutex_)
+      {
+      }
+    };
+
+
+    class WriterLock : public boost::noncopyable
+    {
+    private:
+      LockingEmitter& that_;
+      boost::unique_lock<boost::shared_mutex>  lock_;
+
+    public:
+      WriterLock(LockingEmitter& that) :
+        that_(that),
+        lock_(that.mutex_)
+      {
+      }
+
+      MessageBroker& GetBroker()
+      {
+        return that_.broker_;
+      }
+
+      IObservable& GetOracleObservable()
+      {
+        return that_.oracleObservable_;
+      }
+    };
+  };
+}
--- a/Framework/Messages/MessageBroker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Messages/MessageBroker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -18,8 +18,9 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#pragma once
 
-#pragma once
+#include "../StoneException.h"
 
 #include "boost/noncopyable.hpp"
 
@@ -40,6 +41,10 @@
     std::set<const IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
 
   public:
+    MessageBroker()
+    {
+    }
+
     void Register(const IObserver& observer)
     {
       activeObservers_.insert(&observer);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancImageCommand.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,153 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GetOrthancImageCommand.h"
+
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PamReader.h>
+#include <Core/Images/PngReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+  GetOrthancImageCommand::SuccessMessage::SuccessMessage(const GetOrthancImageCommand& command,
+                                                         Orthanc::ImageAccessor* image,   // Takes ownership
+                                                         Orthanc::MimeType mime) :
+    OriginMessage(command),
+    image_(image),
+    mime_(mime)
+  {
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  GetOrthancImageCommand::GetOrthancImageCommand() :
+    uri_("/"),
+    timeout_(60),
+    hasExpectedFormat_(false)
+  {
+  }
+
+
+  void GetOrthancImageCommand::SetExpectedPixelFormat(Orthanc::PixelFormat format)
+  {
+    hasExpectedFormat_ = true;
+    expectedFormat_ = format;
+  }
+
+
+  void GetOrthancImageCommand::SetInstanceUri(const std::string& instance,
+                                              Orthanc::PixelFormat pixelFormat)
+  {
+    uri_ = "/instances/" + instance;
+          
+    switch (pixelFormat)
+    {
+      case Orthanc::PixelFormat_RGB24:
+        uri_ += "/preview";
+        break;
+      
+      case Orthanc::PixelFormat_Grayscale16:
+        uri_ += "/image-uint16";
+        break;
+      
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        uri_ += "/image-int16";
+        break;
+      
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  void GetOrthancImageCommand::ProcessHttpAnswer(IMessageEmitter& emitter,
+                                                 const IObserver& receiver,
+                                                 const std::string& answer,
+                                                 const HttpHeaders& answerHeaders) const
+  {
+    Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
+
+    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+         it != answerHeaders.end(); ++it)
+    {
+      std::string s;
+      Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+      if (s == "content-type")
+      {
+        contentType = Orthanc::StringToMimeType(it->second);
+        break;
+      }
+    }
+
+    std::auto_ptr<Orthanc::ImageAccessor> image;
+
+    switch (contentType)
+    {
+      case Orthanc::MimeType_Png:
+      {
+        image.reset(new Orthanc::PngReader);
+        dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
+        break;
+      }
+
+      case Orthanc::MimeType_Pam:
+      {
+        image.reset(new Orthanc::PamReader);
+        dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
+        break;
+      }
+
+      case Orthanc::MimeType_Jpeg:
+      {
+        image.reset(new Orthanc::JpegReader);
+        dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Unsupported HTTP Content-Type for an image: " + 
+                                        std::string(Orthanc::EnumerationToString(contentType)));
+    }
+
+    if (hasExpectedFormat_)
+    {
+      if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
+          image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+      }
+
+      if (expectedFormat_ != image->GetFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+    }
+
+    SuccessMessage message(*this, image.release(), contentType);
+    emitter.EmitMessage(receiver, message);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancImageCommand.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,119 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IMessageEmitter.h"
+#include "OracleCommandWithPayload.h"
+
+#include <Core/Images/ImageAccessor.h>
+
+#include <map>
+
+namespace OrthancStone
+{
+  class GetOrthancImageCommand : public OracleCommandWithPayload
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class SuccessMessage : public OriginMessage<GetOrthancImageCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+      Orthanc::MimeType                      mime_;
+
+    public:
+      SuccessMessage(const GetOrthancImageCommand& command,
+                     Orthanc::ImageAccessor* image,   // Takes ownership
+                     Orthanc::MimeType mime);
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+
+      Orthanc::MimeType GetMimeType() const
+      {
+        return mime_;
+      }
+    };
+
+
+  private:
+    std::string           uri_;
+    HttpHeaders           headers_;
+    unsigned int          timeout_;
+    bool                  hasExpectedFormat_;
+    Orthanc::PixelFormat  expectedFormat_;
+
+  public:
+    GetOrthancImageCommand();
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancImage;
+    }
+
+    void SetExpectedPixelFormat(Orthanc::PixelFormat format);
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetInstanceUri(const std::string& instance,
+                        Orthanc::PixelFormat pixelFormat);
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const IObserver& receiver,
+                           const std::string& answer,
+                           const HttpHeaders& answerHeaders) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,217 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GetOrthancWebViewerJpegCommand.h"
+
+#include "../Toolbox/LinearAlgebra.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  GetOrthancWebViewerJpegCommand::SuccessMessage::SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
+                                                                 Orthanc::ImageAccessor* image) :   // Takes ownership
+    OriginMessage(command),
+    image_(image)
+  {
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  GetOrthancWebViewerJpegCommand::GetOrthancWebViewerJpegCommand() :
+    frame_(0),
+    quality_(95),
+    timeout_(60),
+    expectedFormat_(Orthanc::PixelFormat_Grayscale8)
+  {
+  }
+
+
+  void GetOrthancWebViewerJpegCommand::SetQuality(unsigned int quality)
+  {
+    if (quality <= 0 ||
+        quality > 100)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      quality_ = quality;
+    }
+  }
+
+
+  std::string GetOrthancWebViewerJpegCommand::GetUri() const
+  {
+    return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
+            "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
+  }
+
+
+  void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter,
+                                                         const IObserver& receiver,
+                                                         const std::string& answer) const
+  {
+    // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
+      
+    Json::Value encoded;
+
+    {
+      Json::Reader reader;
+      if (!reader.parse(answer, encoded))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+
+    if (encoded.type() != Json::objectValue ||
+        !encoded.isMember("Orthanc") ||
+        encoded["Orthanc"].type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    const Json::Value& info = encoded["Orthanc"];
+    if (!info.isMember("PixelData") ||
+        !info.isMember("Stretched") ||
+        !info.isMember("Compression") ||
+        info["Compression"].type() != Json::stringValue ||
+        info["PixelData"].type() != Json::stringValue ||
+        info["Stretched"].type() != Json::booleanValue ||
+        info["Compression"].asString() != "Jpeg")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    bool isSigned = false;
+    bool isStretched = info["Stretched"].asBool();
+    
+    if (info.isMember("IsSigned"))
+    {
+      if (info["IsSigned"].type() != Json::booleanValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        isSigned = info["IsSigned"].asBool();
+      }
+    }
+    
+    std::auto_ptr<Orthanc::ImageAccessor> reader;
+    
+    {
+      std::string jpeg;
+      Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+      
+      reader.reset(new Orthanc::JpegReader);
+      dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
+    }
+    
+    if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+    {
+      if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      
+      if (isSigned || isStretched)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        SuccessMessage message(*this, reader.release());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
+    }
+    
+    if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    if (!isStretched)
+    {
+      if (expectedFormat_ != reader->GetFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        SuccessMessage message(*this, reader.release());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
+    }
+    
+    int32_t stretchLow = 0;
+    int32_t stretchHigh = 0;
+    
+    if (!info.isMember("StretchLow") ||
+        !info.isMember("StretchHigh") ||
+        info["StretchLow"].type() != Json::intValue ||
+        info["StretchHigh"].type() != Json::intValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    stretchLow = info["StretchLow"].asInt();
+    stretchHigh = info["StretchHigh"].asInt();
+    
+    if (stretchLow < -32768 ||
+        stretchHigh > 65535 ||
+        (stretchLow < 0 && stretchHigh > 32767))
+    {
+      // This range cannot be represented with a uint16_t or an int16_t
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    
+    // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+    std::auto_ptr<Orthanc::ImageAccessor> image
+      (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false));
+
+    Orthanc::ImageProcessing::Convert(*image, *reader);
+    reader.reset();
+    
+    float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    
+    if (!LinearAlgebra::IsCloseToZero(scaling))
+    {
+      float offset = static_cast<float>(stretchLow) / scaling;
+      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+    }
+    
+    SuccessMessage message(*this, image.release());
+    emitter.EmitMessage(receiver, message);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,135 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IMessageEmitter.h"
+#include "OracleCommandWithPayload.h"
+
+#include <Core/Images/ImageAccessor.h>
+
+#include <map>
+
+namespace OrthancStone
+{
+  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+
+    public:
+      SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
+                     Orthanc::ImageAccessor* image);   // Takes ownership
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+    };
+
+  private:
+    std::string           instanceId_;
+    unsigned int          frame_;
+    unsigned int          quality_;
+    HttpHeaders           headers_;
+    unsigned int          timeout_;
+    Orthanc::PixelFormat  expectedFormat_;
+
+  public:
+    GetOrthancWebViewerJpegCommand();
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancWebViewerJpeg;
+    }
+
+    void SetExpectedPixelFormat(Orthanc::PixelFormat format)
+    {
+      expectedFormat_ = format;
+    }
+
+    void SetInstance(const std::string& instanceId)
+    {
+      instanceId_ = instanceId;
+    }
+
+    void SetFrame(unsigned int frame)
+    {
+      frame_ = frame;
+    }
+
+    void SetQuality(unsigned int quality);
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::PixelFormat GetExpectedPixelFormat() const
+    {
+      return expectedFormat_;
+    }
+
+    const std::string& GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      return frame_;
+    }
+
+    unsigned int GetQuality() const
+    {
+      return quality_;
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    std::string GetUri() const;
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const IObserver& receiver,
+                           const std::string& answer) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/IOracle.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,39 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IObserver.h"
+#include "IOracleCommand.h"
+
+namespace OrthancStone
+{
+  class IOracle : public boost::noncopyable
+  {
+  public:
+    virtual ~IOracle()
+    {
+    }
+
+    virtual void Schedule(const IObserver& receiver,
+                          IOracleCommand* command) = 0;  // Takes ownership
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/IOracleCommand.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,45 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class IOracleCommand : public boost::noncopyable
+  {
+  public:
+    enum Type
+    {
+      Type_Sleep,
+      Type_OrthancRestApi,
+      Type_GetOrthancImage,
+      Type_GetOrthancWebViewerJpeg
+    };
+
+    virtual ~IOracleCommand()
+    {
+    }
+
+    virtual Type GetType() const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OracleCommandExceptionMessage.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,64 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IMessage.h"
+#include "IOracleCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class OracleCommandExceptionMessage : public IMessage
+  {
+    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+  private:
+    const IOracleCommand&       command_;
+    Orthanc::OrthancException   exception_;
+
+  public:
+    OracleCommandExceptionMessage(const IOracleCommand& command,
+                                  const Orthanc::OrthancException& exception) :
+      command_(command),
+      exception_(exception)
+    {
+    }
+
+    OracleCommandExceptionMessage(const IOracleCommand& command,
+                                  const Orthanc::ErrorCode& error) :
+      command_(command),
+      exception_(error)
+    {
+    }
+
+    const IOracleCommand& GetCommand() const
+    {
+      return command_;
+    }
+    
+    const Orthanc::OrthancException& GetException() const
+    {
+      return exception_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OracleCommandWithPayload.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OracleCommandWithPayload.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void OracleCommandWithPayload::SetPayload(Orthanc::IDynamicObject* payload)
+  {
+    if (payload == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      payload_.reset(payload);
+    }    
+  }
+
+
+  Orthanc::IDynamicObject& OracleCommandWithPayload::GetPayload() const
+  {
+    if (HasPayload())
+    {
+      return *payload_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  Orthanc::IDynamicObject* OracleCommandWithPayload::ReleasePayload()
+  {
+    if (HasPayload())
+    {
+      return payload_.release();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OracleCommandWithPayload.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,49 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IOracleCommand.h"
+
+#include <Core/IDynamicObject.h>
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class OracleCommandWithPayload : public IOracleCommand
+  {
+  private:
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+
+  public:
+    void SetPayload(Orthanc::IDynamicObject* payload);
+
+    bool HasPayload() const
+    {
+      return (payload_.get() != NULL);
+    }
+
+    Orthanc::IDynamicObject& GetPayload() const;
+
+    Orthanc::IDynamicObject* ReleasePayload();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OrthancRestApiCommand.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancRestApiCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <json/reader.h>
+#include <json/writer.h>
+
+namespace OrthancStone
+{
+  OrthancRestApiCommand::SuccessMessage::SuccessMessage(const OrthancRestApiCommand& command,
+                                                        const HttpHeaders& answerHeaders,
+                                                        std::string& answer) :
+    OriginMessage(command),
+    headers_(answerHeaders),
+    answer_(answer)
+  {
+  }
+
+
+  void OrthancRestApiCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const
+  {
+    Json::Reader reader;
+    if (!reader.parse(answer_, target))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  OrthancRestApiCommand::OrthancRestApiCommand() :
+    method_(Orthanc::HttpMethod_Get),
+    uri_("/"),
+    timeout_(60)
+  {
+  }
+
+
+  void OrthancRestApiCommand::SetBody(const Json::Value& json)
+  {
+    Json::FastWriter writer;
+    body_ = writer.write(json);
+  }
+
+
+  const std::string& OrthancRestApiCommand::GetBody() const
+  {
+    if (method_ == Orthanc::HttpMethod_Post ||
+        method_ == Orthanc::HttpMethod_Put)
+    {
+      return body_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OrthancRestApiCommand.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,136 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IMessage.h"
+#include "OracleCommandWithPayload.h"
+
+#include <Core/Enumerations.h>
+
+#include <map>
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  class OrthancRestApiCommand : public OracleCommandWithPayload
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class SuccessMessage : public OriginMessage<OrthancRestApiCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      HttpHeaders   headers_;
+      std::string   answer_;
+
+    public:
+      SuccessMessage(const OrthancRestApiCommand& command,
+                     const HttpHeaders& answerHeaders,
+                     std::string& answer  /* will be swapped to avoid a memcpy() */);
+
+      const std::string& GetAnswer() const
+      {
+        return answer_;
+      }
+
+      void ParseJsonBody(Json::Value& target) const;
+
+      const HttpHeaders&  GetAnswerHeaders() const
+      {
+        return headers_;
+      }
+    };
+
+
+  private:
+    Orthanc::HttpMethod  method_;
+    std::string          uri_;
+    std::string          body_;
+    HttpHeaders          headers_;
+    unsigned int         timeout_;
+
+  public:
+    OrthancRestApiCommand();
+
+    virtual Type GetType() const
+    {
+      return Type_OrthancRestApi;
+    }
+
+    void SetMethod(Orthanc::HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetBody(const std::string& body)
+    {
+      body_ = body;
+    }
+
+    void SetBody(const Json::Value& json);
+
+    void SwapBody(std::string& body)
+    {
+      body_.swap(body);
+    }
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::HttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
+    }
+
+    const std::string& GetBody() const;
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/SleepOracleCommand.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IMessage.h"
+#include "OracleCommandWithPayload.h"
+
+namespace OrthancStone
+{
+  class SleepOracleCommand : public OracleCommandWithPayload
+  {
+  private:
+    unsigned int  milliseconds_;
+
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, TimeoutMessage, SleepOracleCommand);
+
+    SleepOracleCommand(unsigned int milliseconds) : 
+      milliseconds_(milliseconds)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_Sleep;
+    }
+
+    unsigned int GetDelay() const
+    {
+      return milliseconds_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ThreadedOracle.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,533 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ThreadedOracle.h"
+
+#include "GetOrthancImageCommand.h"
+#include "GetOrthancWebViewerJpegCommand.h"
+#include "OrthancRestApiCommand.h"
+#include "SleepOracleCommand.h"
+#include "OracleCommandExceptionMessage.h"
+
+#include <Core/Compression/GzipCompressor.h>
+#include <Core/HttpClient.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+
+namespace OrthancStone
+{
+  class ThreadedOracle::Item : public Orthanc::IDynamicObject
+  {
+  private:
+    const IObserver&                receiver_;
+    std::auto_ptr<IOracleCommand>   command_;
+
+  public:
+    Item(const IObserver& receiver,
+         IOracleCommand* command) :
+      receiver_(receiver),
+      command_(command)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    const IObserver& GetReceiver() const
+    {
+      return receiver_;
+    }
+
+    IOracleCommand& GetCommand()
+    {
+      assert(command_.get() != NULL);
+      return *command_;
+    }
+  };
+
+
+  class ThreadedOracle::SleepingCommands : public boost::noncopyable
+  {
+  private:
+    class Item
+    {
+    private:
+      const IObserver&                   receiver_;
+      std::auto_ptr<SleepOracleCommand>  command_;
+      boost::posix_time::ptime           expiration_;
+
+    public:
+      Item(const IObserver& receiver,
+           SleepOracleCommand* command) :
+        receiver_(receiver),
+        command_(command)
+      {
+        if (command == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+
+        expiration_ = (boost::posix_time::microsec_clock::local_time() + 
+                       boost::posix_time::milliseconds(command_->GetDelay()));
+      }
+
+      const boost::posix_time::ptime& GetExpirationTime() const
+      {
+        return expiration_;
+      }
+
+      void Awake(IMessageEmitter& emitter)
+      {
+        assert(command_.get() != NULL);
+
+        SleepOracleCommand::TimeoutMessage message(*command_);
+        emitter.EmitMessage(receiver_, message);
+      }
+    };
+
+    typedef std::list<Item*>  Content;
+
+    boost::mutex  mutex_;
+    Content       content_;
+
+  public:
+    ~SleepingCommands()
+    {
+      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        if (*it != NULL)
+        {
+          delete *it;
+        }
+      }
+    }
+
+    void Add(const IObserver& receiver,
+             SleepOracleCommand* command)   // Takes ownership
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      content_.push_back(new Item(receiver, command));
+    }
+
+    void AwakeExpired(IMessageEmitter& emitter)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+
+      Content  stillSleeping;
+        
+      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        if (*it != NULL &&
+            (*it)->GetExpirationTime() <= now)
+        {
+          (*it)->Awake(emitter);
+          delete *it;
+          *it = NULL;
+        }
+        else
+        {
+          stillSleeping.push_back(*it);
+        }
+      }
+
+      // Compact the still-sleeping commands
+      content_ = stillSleeping;
+    }
+  };
+
+
+  static void CopyHttpHeaders(Orthanc::HttpClient& client,
+                              const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator
+           it = headers.begin(); it != headers.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+  }
+
+
+  static void DecodeAnswer(std::string& answer,
+                           const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
+
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); 
+         it != headers.end(); ++it)
+    {
+      std::string s;
+      Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+      if (s == "content-encoding")
+      {
+        if (it->second == "gzip")
+        {
+          contentEncoding = Orthanc::HttpCompression_Gzip;
+        }
+        else 
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                          "Unsupported HTTP Content-Encoding: " + it->second);
+        }
+
+        break;
+      }
+    }
+
+    if (contentEncoding == Orthanc::HttpCompression_Gzip)
+    {
+      std::string compressed;
+      answer.swap(compressed);
+          
+      Orthanc::GzipCompressor compressor;
+      compressor.Uncompress(answer, compressed.c_str(), compressed.size());
+
+      LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
+                << " to " << answer.size() << " bytes";
+    }
+  }
+
+
+  static void Execute(IMessageEmitter& emitter,
+                      const Orthanc::WebServiceParameters& orthanc,
+                      const IObserver& receiver,
+                      const OrthancRestApiCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetBody(command.GetBody());
+    }
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
+    emitter.EmitMessage(receiver, message);
+  }
+
+
+  static void Execute(IMessageEmitter& emitter,
+                      const Orthanc::WebServiceParameters& orthanc,
+                      const IObserver& receiver,
+                      const GetOrthancImageCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+    
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(emitter, receiver, answer, answerHeaders);
+  }
+
+
+  static void Execute(IMessageEmitter& emitter,
+                      const Orthanc::WebServiceParameters& orthanc,
+                      const IObserver& receiver,
+                      const GetOrthancWebViewerJpegCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(emitter, receiver, answer);
+  }
+
+
+  void ThreadedOracle::Step()
+  {
+    std::auto_ptr<Orthanc::IDynamicObject>  object(queue_.Dequeue(100));
+
+    if (object.get() != NULL)
+    {
+      Item& item = dynamic_cast<Item&>(*object);
+
+      try
+      {
+        switch (item.GetCommand().GetType())
+        {
+          case IOracleCommand::Type_Sleep:
+          {
+            SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand());
+
+            std::auto_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay()));
+
+            if (command.HasPayload())
+            {
+              copy->SetPayload(command.ReleasePayload());
+            }
+
+            sleepingCommands_->Add(item.GetReceiver(), copy.release());
+
+            break;
+          }
+
+          case IOracleCommand::Type_OrthancRestApi:
+            Execute(emitter_, orthanc_, item.GetReceiver(), 
+                    dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
+            break;
+
+          case IOracleCommand::Type_GetOrthancImage:
+            Execute(emitter_, orthanc_, item.GetReceiver(), 
+                    dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
+            break;
+
+          case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+            Execute(emitter_, orthanc_, item.GetReceiver(), 
+                    dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Exception within the oracle: " << e.What();
+        emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
+      }
+      catch (...)
+      {
+        LOG(ERROR) << "Threaded exception within the oracle";
+        emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
+                             (item.GetCommand(), Orthanc::ErrorCode_InternalError));
+      }
+    }
+  }
+
+
+  void ThreadedOracle::Worker(ThreadedOracle* that)
+  {
+    assert(that != NULL);
+      
+    for (;;)
+    {
+      {
+        boost::mutex::scoped_lock lock(that->mutex_);
+        if (that->state_ != State_Running)
+        {
+          return;
+        }
+      }
+
+      that->Step();
+    }
+  }
+
+
+  void ThreadedOracle::SleepingWorker(ThreadedOracle* that)
+  {
+    assert(that != NULL);
+      
+    for (;;)
+    {
+      {
+        boost::mutex::scoped_lock lock(that->mutex_);
+        if (that->state_ != State_Running)
+        {
+          return;
+        }
+      }
+
+      that->sleepingCommands_->AwakeExpired(that->emitter_);
+
+      boost::this_thread::sleep(boost::posix_time::milliseconds(that->sleepingTimeResolution_));
+    }
+  }
+
+
+  void ThreadedOracle::StopInternal()
+  {
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (state_ == State_Setup ||
+          state_ == State_Stopped)
+      {
+        return;
+      }
+      else
+      {
+        state_ = State_Stopped;
+      }
+    }
+
+    if (sleepingWorker_.joinable())
+    {
+      sleepingWorker_.join();
+    }
+
+    for (size_t i = 0; i < workers_.size(); i++)
+    {
+      if (workers_[i] != NULL)
+      {
+        if (workers_[i]->joinable())
+        {
+          workers_[i]->join();
+        }
+
+        delete workers_[i];
+      }
+    } 
+  }
+
+
+  ThreadedOracle::ThreadedOracle(IMessageEmitter& emitter) :
+    emitter_(emitter),
+    state_(State_Setup),
+    workers_(4),
+    sleepingCommands_(new SleepingCommands),
+    sleepingTimeResolution_(50)  // By default, time resolution of 50ms
+  {
+  }
+
+
+  ThreadedOracle::~ThreadedOracle()
+  {
+    if (state_ == State_Running)
+    {
+      LOG(ERROR) << "The threaded oracle is still running, explicit call to "
+                 << "Stop() is mandatory to avoid crashes";
+    }
+
+    try
+    {
+      StopInternal();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception while stopping the threaded oracle: " << e.What();
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Native exception while stopping the threaded oracle";
+    }           
+  }
+
+  
+  void ThreadedOracle::SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (state_ != State_Setup)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      orthanc_ = orthanc;
+    }
+  }
+
+
+  void ThreadedOracle::SetThreadsCount(unsigned int count)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (count <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else if (state_ != State_Setup)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      workers_.resize(count);
+    }
+  }
+
+
+  void ThreadedOracle::SetSleepingTimeResolution(unsigned int milliseconds)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (milliseconds <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else if (state_ != State_Setup)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      sleepingTimeResolution_ = milliseconds;
+    }
+  }
+
+
+  void ThreadedOracle::Start()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (state_ != State_Setup)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_Running;
+
+      for (unsigned int i = 0; i < workers_.size(); i++)
+      {
+        workers_[i] = new boost::thread(Worker, this);
+      }
+
+      sleepingWorker_ = boost::thread(SleepingWorker, this);
+    }      
+  }
+
+
+  void ThreadedOracle::Schedule(const IObserver& receiver,
+                                IOracleCommand* command)
+  {
+    queue_.Enqueue(new Item(receiver, command));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ThreadedOracle.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,94 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_THREADS)
+#  error The macro ORTHANC_ENABLE_THREADS must be defined
+#endif
+
+#if ORTHANC_ENABLE_THREADS != 1
+#  error This file can only compiled for native targets
+#endif
+
+#include "../Messages/IMessageEmitter.h"
+#include "IOracle.h"
+
+#include <Core/WebServiceParameters.h>
+#include <Core/MultiThreading/SharedMessageQueue.h>
+
+
+namespace OrthancStone
+{
+  class ThreadedOracle : public IOracle
+  {
+  private:
+    enum State
+    {
+      State_Setup,
+      State_Running,
+      State_Stopped
+    };
+
+    class Item;
+    class SleepingCommands;
+
+    IMessageEmitter&                     emitter_;
+    Orthanc::WebServiceParameters        orthanc_;
+    Orthanc::SharedMessageQueue          queue_;
+    State                                state_;
+    boost::mutex                         mutex_;
+    std::vector<boost::thread*>          workers_;
+    boost::shared_ptr<SleepingCommands>  sleepingCommands_;
+    boost::thread                        sleepingWorker_;
+    unsigned int                         sleepingTimeResolution_;
+
+    void Step();
+
+    static void Worker(ThreadedOracle* that);
+
+    static void SleepingWorker(ThreadedOracle* that);
+
+    void StopInternal();
+
+  public:
+    ThreadedOracle(IMessageEmitter& emitter);
+
+    virtual ~ThreadedOracle();
+
+    // The reference is not stored.
+    void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc);
+
+    void SetThreadsCount(unsigned int count);
+
+    void SetSleepingTimeResolution(unsigned int milliseconds);
+
+    void Start();
+
+    void Stop()
+    {
+      StopInternal();
+    }
+
+    virtual void Schedule(const IObserver& receiver,
+                          IOracleCommand* command);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/WebAssemblyOracle.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,479 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebAssemblyOracle.h"
+
+#include "SleepOracleCommand.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <emscripten/fetch.h>
+
+
+namespace OrthancStone
+{
+  class WebAssemblyOracle::TimeoutContext
+  {
+  private:
+    WebAssemblyOracle&                 oracle_;
+    const IObserver&                   receiver_;
+    std::auto_ptr<SleepOracleCommand>  command_;
+
+  public:
+    TimeoutContext(WebAssemblyOracle& oracle,
+                   const IObserver& receiver,
+                   IOracleCommand* command) :
+      oracle_(oracle),
+      receiver_(receiver)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        command_.reset(dynamic_cast<SleepOracleCommand*>(command));
+      }
+    }
+
+    void EmitMessage()
+    {
+      SleepOracleCommand::TimeoutMessage message(*command_);
+      oracle_.EmitMessage(receiver_, message);
+    }
+
+    static void Callback(void *userData)
+    {
+      std::auto_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData));
+      context->EmitMessage();
+    }
+  };
+    
+
+  class WebAssemblyOracle::Emitter : public IMessageEmitter
+  {
+  private:
+    WebAssemblyOracle&  oracle_;
+
+  public:
+    Emitter(WebAssemblyOracle&  oracle) :
+      oracle_(oracle)
+    {
+    }
+
+    virtual void EmitMessage(const IObserver& receiver,
+                             const IMessage& message)
+    {
+      oracle_.EmitMessage(receiver, message);
+    }
+  };
+
+
+  class WebAssemblyOracle::FetchContext : public boost::noncopyable
+  {
+  private:
+    Emitter                        emitter_;
+    const IObserver&               receiver_;
+    std::auto_ptr<IOracleCommand>  command_;
+    std::string                    expectedContentType_;
+
+  public:
+    FetchContext(WebAssemblyOracle& oracle,
+                 const IObserver& receiver,
+                 IOracleCommand* command,
+                 const std::string& expectedContentType) :
+      emitter_(oracle),
+      receiver_(receiver),
+      command_(command),
+      expectedContentType_(expectedContentType)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    const std::string& GetExpectedContentType() const
+    {
+      return expectedContentType_;
+    }
+
+    void EmitMessage(const IMessage& message)
+    {
+      emitter_.EmitMessage(receiver_, message);
+    }
+
+    IMessageEmitter& GetEmitter()
+    {
+      return emitter_;
+    }
+
+    const IObserver& GetReceiver() const
+    {
+      return receiver_;
+    }
+
+    IOracleCommand& GetCommand() const
+    {
+      return *command_;
+    }
+
+    template <typename T>
+    const T& GetTypedCommand() const
+    {
+      return dynamic_cast<T&>(*command_);
+    }
+
+    static void SuccessCallback(emscripten_fetch_t *fetch)
+    {
+      /**
+       * Firstly, make a local copy of the fetched information, and
+       * free data associated with the fetch.
+       **/
+      
+      std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
+
+      std::string answer;
+      if (fetch->numBytes > 0)
+      {
+        answer.assign(fetch->data, fetch->numBytes);
+      }
+
+      /**
+       * TODO - HACK - As of emscripten-1.38.31, the fetch API does
+       * not contain a way to retrieve the HTTP headers of the
+       * answer. We make the assumption that the "Content-Type" header
+       * of the response is the same as the "Accept" header of the
+       * query. This should be fixed in future versions of emscripten.
+       * https://github.com/emscripten-core/emscripten/pull/8486
+       **/
+
+      HttpHeaders headers;
+      if (!context->GetExpectedContentType().empty())
+      {
+        headers["Content-Type"] = context->GetExpectedContentType();
+      }
+      
+      
+      emscripten_fetch_close(fetch);
+
+
+      /**
+       * Secondly, use the retrieved data.
+       **/
+
+      try
+      {
+        if (context.get() == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+        else
+        {
+          switch (context->GetCommand().GetType())
+          {
+            case IOracleCommand::Type_OrthancRestApi:
+            {
+              OrthancRestApiCommand::SuccessMessage message
+                (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer);
+              context->EmitMessage(message);
+              break;
+            }
+            
+            case IOracleCommand::Type_GetOrthancImage:
+            {
+              context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer
+                (context->GetEmitter(), context->GetReceiver(), answer, headers);
+              break;
+            }
+          
+            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+            {
+              context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer
+                (context->GetEmitter(), context->GetReceiver(), answer);
+              break;
+            }
+          
+            default:
+              LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: "
+                         << context->GetCommand().GetType();
+          }
+        }
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What();
+      }
+    }
+
+    static void FailureCallback(emscripten_fetch_t *fetch)
+    {
+      std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
+      
+      LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status;
+
+      /**
+       * TODO - The following code leads to an infinite recursion, at
+       * least with Firefox running on incognito mode => WHY?
+       **/      
+      //emscripten_fetch_close(fetch); // Also free data on failure.
+    }
+  };
+    
+
+
+  class WebAssemblyOracle::FetchCommand : public boost::noncopyable
+  {
+  private:
+    WebAssemblyOracle&             oracle_;
+    const IObserver&               receiver_;
+    std::auto_ptr<IOracleCommand>  command_;
+    Orthanc::HttpMethod            method_;
+    std::string                    uri_;
+    std::string                    body_;
+    HttpHeaders                    headers_;
+    unsigned int                   timeout_;
+    std::string                    expectedContentType_;
+
+  public:
+    FetchCommand(WebAssemblyOracle& oracle,
+                 const IObserver& receiver,
+                 IOracleCommand* command) :
+      oracle_(oracle),
+      receiver_(receiver),
+      command_(command),
+      method_(Orthanc::HttpMethod_Get),
+      timeout_(0)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    void SetMethod(Orthanc::HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = oracle_.orthancRoot_ + uri;
+    }
+
+    void SetBody(std::string& body /* will be swapped */)
+    {
+      body_.swap(body);
+    }
+
+    void SetHttpHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
+    void SetTimeout(unsigned int timeout)
+    {
+      timeout_ = timeout;
+    }
+
+    void Execute()
+    {
+      if (command_.get() == NULL)
+      {
+        // Cannot call Execute() twice
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);          
+      }
+
+      emscripten_fetch_attr_t attr;
+      emscripten_fetch_attr_init(&attr);
+
+      const char* method;
+      
+      switch (method_)
+      {
+        case Orthanc::HttpMethod_Get:
+          method = "GET";
+          break;
+
+        case Orthanc::HttpMethod_Post:
+          method = "POST";
+          break;
+
+        case Orthanc::HttpMethod_Delete:
+          method = "DELETE";
+          break;
+
+        case Orthanc::HttpMethod_Put:
+          method = "PUT";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      strcpy(attr.requestMethod, method);
+
+      attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
+      attr.onsuccess = FetchContext::SuccessCallback;
+      attr.onerror = FetchContext::FailureCallback;
+      attr.timeoutMSecs = timeout_ * 1000;
+
+      std::vector<const char*> headers;
+      headers.reserve(2 * headers_.size() + 1);
+
+      std::string expectedContentType;
+        
+      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
+      {
+        std::string key;
+        Orthanc::Toolbox::ToLowerCase(key, it->first);
+          
+        if (key == "accept")
+        {
+          expectedContentType = it->second;
+        }
+
+        if (key != "accept-encoding")  // Web browsers forbid the modification of this HTTP header
+        {
+          headers.push_back(it->first.c_str());
+          headers.push_back(it->second.c_str());
+        }
+      }
+        
+      headers.push_back(NULL);  // Termination of the array of HTTP headers
+
+      attr.requestHeaders = &headers[0];
+
+      char* requestData = NULL;
+      if (!body_.empty())
+        requestData = reinterpret_cast<char*>(malloc(body_.size()));
+        
+      try 
+      {
+        if (!body_.empty())
+        {
+          memcpy(requestData, &(body_[0]), body_.size());
+          attr.requestDataSize = body_.size();
+          attr.requestData = requestData;
+        }
+        attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType);
+
+        // Must be the last call to prevent memory leak on error
+        emscripten_fetch(&attr, uri_.c_str());
+      }        
+      catch(...)
+      {
+        if(requestData != NULL)
+          free(requestData);
+        throw;
+      }
+    }
+  };
+    
+  void WebAssemblyOracle::Execute(const IObserver& receiver,
+                                  OrthancRestApiCommand* command)
+  {
+    FetchCommand fetch(*this, receiver, command);
+
+    fetch.SetMethod(command->GetMethod());
+    fetch.SetUri(command->GetUri());
+    fetch.SetHttpHeaders(command->GetHttpHeaders());
+    fetch.SetTimeout(command->GetTimeout());
+      
+    if (command->GetMethod() == Orthanc::HttpMethod_Post ||
+        command->GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      std::string body;
+      command->SwapBody(body);
+      fetch.SetBody(body);
+    }
+
+    fetch.Execute();
+  }
+    
+    
+  void WebAssemblyOracle::Execute(const IObserver& receiver,
+                                  GetOrthancImageCommand* command)
+  {
+    FetchCommand fetch(*this, receiver, command);
+
+    fetch.SetUri(command->GetUri());
+    fetch.SetHttpHeaders(command->GetHttpHeaders());
+    fetch.SetTimeout(command->GetTimeout());
+      
+    fetch.Execute();
+  }
+    
+    
+  void WebAssemblyOracle::Execute(const IObserver& receiver,
+                                  GetOrthancWebViewerJpegCommand* command)
+  {
+    FetchCommand fetch(*this, receiver, command);
+
+    fetch.SetUri(command->GetUri());
+    fetch.SetHttpHeaders(command->GetHttpHeaders());
+    fetch.SetTimeout(command->GetTimeout());
+      
+    fetch.Execute();
+  }
+
+
+
+  void WebAssemblyOracle::Schedule(const IObserver& receiver,
+                                   IOracleCommand* command)
+  {
+    std::auto_ptr<IOracleCommand> protection(command);
+
+    if (command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    switch (command->GetType())
+    {
+      case IOracleCommand::Type_OrthancRestApi:
+        Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release()));
+        break;
+        
+      case IOracleCommand::Type_GetOrthancImage:
+        Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release()));
+        break;
+
+      case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+        Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release()));
+        break;          
+            
+      case IOracleCommand::Type_Sleep:
+      {
+        unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
+        emscripten_set_timeout(TimeoutContext::Callback, timeoutMS,
+                               new TimeoutContext(*this, receiver, protection.release()));
+        break;
+      }
+            
+      default:
+        LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/WebAssemblyOracle.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_WASM)
+#  error The macro ORTHANC_ENABLE_WASM must be defined
+#endif
+
+#if ORTHANC_ENABLE_WASM != 1
+#  error This file can only compiled for WebAssembly
+#endif
+
+#include "../Messages/IObservable.h"
+#include "GetOrthancImageCommand.h"
+#include "GetOrthancWebViewerJpegCommand.h"
+#include "IOracle.h"
+#include "OrthancRestApiCommand.h"
+
+
+namespace OrthancStone
+{
+  class WebAssemblyOracle :
+    public IOracle,
+    public IObservable
+  {
+  private:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+    
+    class TimeoutContext;
+    class Emitter;
+    class FetchContext;
+    class FetchCommand;    
+    
+    void Execute(const IObserver& receiver,
+                 OrthancRestApiCommand* command);    
+    
+    void Execute(const IObserver& receiver,
+                 GetOrthancImageCommand* command);    
+    
+    void Execute(const IObserver& receiver,
+                 GetOrthancWebViewerJpegCommand* command);
+
+    std::string orthancRoot_;
+
+  public:
+    WebAssemblyOracle(MessageBroker& broker) :
+      IObservable(broker)
+    {
+    }
+
+    void SetOrthancRoot(const std::string& root)
+    {
+      orthancRoot_ = root;
+    }
+    
+    virtual void Schedule(const IObserver& receiver,
+                          IOracleCommand* command);
+  };
+}
--- a/Framework/Radiography/RadiographyDicomLayer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyDicomLayer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #include "RadiographyDicomLayer.h"
 
 #include "RadiographyScene.h"
-#include "../Toolbox/DicomFrameConverter.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
 
 #include <Core/OrthancException.h>
 #include <Core/Images/Image.h>
@@ -85,11 +85,11 @@
     {
       if (tmp == "MONOCHROME1")
       {
-        SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode_Monochrome1);
+        SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome1);
       }
       else if (tmp == "MONOCHROME2")
       {
-        SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode_Monochrome2);
+        SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome2);
       }
     }
   }
--- a/Framework/Radiography/RadiographyDicomLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyDicomLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../Toolbox/DicomFrameConverter.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
 #include "RadiographyLayer.h"
 
 #include <Plugins/Samples/Common/FullOrthancDataset.h>
--- a/Framework/Radiography/RadiographyLayer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -125,7 +125,7 @@
     hasSize_(false),
     width_(0),
     height_(0),
-    prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default),
+    prefferedPhotometricDisplayMode_(RadiographyPhotometricDisplayMode_Default),
     scene_(scene)
   {
     UpdateTransform();
@@ -137,7 +137,7 @@
     UpdateTransform();
   }
 
-  void RadiographyLayer::SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode  prefferedPhotometricDisplayMode)
+  void RadiographyLayer::SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode)
   {
     prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode;
 
@@ -349,21 +349,21 @@
     ControlPoint cp;
     switch (index)
     {
-    case ControlPoint_TopLeftCorner:
-      cp = ControlPoint(cropX, cropY, ControlPoint_TopLeftCorner);
-      break;
+      case RadiographyControlPointType_TopLeftCorner:
+        cp = ControlPoint(cropX, cropY, RadiographyControlPointType_TopLeftCorner);
+        break;
 
-    case ControlPoint_TopRightCorner:
-      cp = ControlPoint(cropX + cropWidth, cropY, ControlPoint_TopRightCorner);
-      break;
+      case RadiographyControlPointType_TopRightCorner:
+        cp = ControlPoint(cropX + cropWidth, cropY, RadiographyControlPointType_TopRightCorner);
+        break;
 
-    case ControlPoint_BottomLeftCorner:
-      cp = ControlPoint(cropX, cropY + cropHeight, ControlPoint_BottomLeftCorner);
-      break;
+      case RadiographyControlPointType_BottomLeftCorner:
+        cp = ControlPoint(cropX, cropY + cropHeight, RadiographyControlPointType_BottomLeftCorner);
+        break;
 
-    case ControlPoint_BottomRightCorner:
-      cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, ControlPoint_BottomRightCorner);
-      break;
+      case RadiographyControlPointType_BottomRightCorner:
+        cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, RadiographyControlPointType_BottomRightCorner);
+        break;
 
     default:
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
--- a/Framework/Radiography/RadiographyLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,7 +23,7 @@
 
 #include "../Toolbox/AffineTransform2D.h"
 #include "../Toolbox/Extent2D.h"
-#include "../Viewport/CairoContext.h"
+#include "../Wrappers/CairoContext.h"
 #include "../Messages/IMessage.h"
 #include "../Messages/IObservable.h"
 
@@ -31,6 +31,23 @@
 {
   class RadiographyScene;
 
+  enum RadiographyControlPointType
+  {
+    RadiographyControlPointType_TopLeftCorner = 0,
+    RadiographyControlPointType_TopRightCorner = 1,
+    RadiographyControlPointType_BottomRightCorner = 2,
+    RadiographyControlPointType_BottomLeftCorner = 3
+  };
+
+  enum RadiographyPhotometricDisplayMode
+  {
+    RadiographyPhotometricDisplayMode_Default,
+
+    RadiographyPhotometricDisplayMode_Monochrome1,
+    RadiographyPhotometricDisplayMode_Monochrome2
+  };
+
+  
   struct ControlPoint
   {
     double x;
@@ -196,7 +213,7 @@
     AffineTransform2D  transform_;
     AffineTransform2D  transformInverse_;
     Geometry           geometry_;
-    PhotometricDisplayMode  prefferedPhotometricDisplayMode_;
+    RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode_;
     const RadiographyScene&   scene_;
 
   protected:
@@ -210,7 +227,7 @@
       return transformInverse_;
     }
 
-    void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode  prefferedPhotometricDisplayMode);
+    void SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode  prefferedPhotometricDisplayMode);
 
   private:
     void UpdateTransform();
@@ -325,7 +342,7 @@
     virtual bool GetDefaultWindowing(float& center,
                                      float& width) const = 0;
 
-    PhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const
+    RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const
     {
       return prefferedPhotometricDisplayMode_;
     }
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -112,8 +112,8 @@
       {
         unsigned int targetX, targetWidth;
 
-        if (startControlPoint_.index == ControlPoint_TopLeftCorner ||
-            startControlPoint_.index == ControlPoint_BottomLeftCorner)
+        if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner ||
+            startControlPoint_.index == RadiographyControlPointType_BottomLeftCorner)
         {
           targetX = std::min(x, cropX_ + cropWidth_);
           targetWidth = cropX_ + cropWidth_ - targetX;
@@ -126,8 +126,8 @@
 
         unsigned int targetY, targetHeight;
 
-        if (startControlPoint_.index == ControlPoint_TopLeftCorner ||
-            startControlPoint_.index == ControlPoint_TopRightCorner)
+        if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner ||
+            startControlPoint_.index == RadiographyControlPointType_TopRightCorner)
         {
           targetY = std::min(y, cropY_ + cropHeight_);
           targetHeight = cropY_ + cropHeight_ - targetY;
--- a/Framework/Radiography/RadiographyLayerCropTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerCropTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,8 +22,8 @@
 #pragma once
 
 #include "../Toolbox/UndoRedoStack.h"
-#include "../Toolbox/ViewportGeometry.h"
-#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Deprecated/Toolbox/ViewportGeometry.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
 #include "RadiographyScene.h"
 
 namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerMaskTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerMaskTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,8 +22,8 @@
 #pragma once
 
 #include "../Toolbox/UndoRedoStack.h"
-#include "../Toolbox/ViewportGeometry.h"
-#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Deprecated/Toolbox/ViewportGeometry.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
 #include "RadiographyScene.h"
 
 namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerMoveTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerMoveTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/UndoRedoStack.h"
-#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
 #include "RadiographyScene.h"
 
 namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -102,20 +102,20 @@
       size_t oppositeControlPointType;
       switch (startControlPoint.index)
       {
-        case ControlPoint_TopLeftCorner:
-          oppositeControlPointType = ControlPoint_BottomRightCorner;
+        case RadiographyControlPointType_TopLeftCorner:
+          oppositeControlPointType = RadiographyControlPointType_BottomRightCorner;
           break;
 
-        case ControlPoint_TopRightCorner:
-          oppositeControlPointType = ControlPoint_BottomLeftCorner;
+        case RadiographyControlPointType_TopRightCorner:
+          oppositeControlPointType = RadiographyControlPointType_BottomLeftCorner;
           break;
 
-        case ControlPoint_BottomLeftCorner:
-          oppositeControlPointType = ControlPoint_TopRightCorner;
+        case RadiographyControlPointType_BottomLeftCorner:
+          oppositeControlPointType = RadiographyControlPointType_TopRightCorner;
           break;
 
-        case ControlPoint_BottomRightCorner:
-          oppositeControlPointType = ControlPoint_TopLeftCorner;
+        case RadiographyControlPointType_BottomRightCorner:
+          oppositeControlPointType = RadiographyControlPointType_TopLeftCorner;
           break;
 
         default:
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/UndoRedoStack.h"
-#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
 #include "RadiographyScene.h"
 
 namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerRotateTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyLayerRotateTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,8 +22,8 @@
 #pragma once
 
 #include "../Toolbox/UndoRedoStack.h"
-#include "../Toolbox/ViewportGeometry.h"
-#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Deprecated/Toolbox/ViewportGeometry.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
 #include "RadiographyScene.h"
 
 
--- a/Framework/Radiography/RadiographyScene.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -25,7 +25,7 @@
 #include "RadiographyDicomLayer.h"
 #include "RadiographyTextLayer.h"
 #include "RadiographyMaskLayer.h"
-#include "../Toolbox/DicomFrameConverter.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
 
 #include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
@@ -172,18 +172,18 @@
     }
   }
 
-  PhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const
+  RadiographyPhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const
   {
     // return the mode of the first layer who "cares" about its display mode (normaly, the one and only layer that is a DicomLayer)
     for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++)
     {
-      if (it->second->GetPreferredPhotomotricDisplayMode() != PhotometricDisplayMode_Default)
+      if (it->second->GetPreferredPhotomotricDisplayMode() != RadiographyPhotometricDisplayMode_Default)
       {
         return it->second->GetPreferredPhotomotricDisplayMode();
       }
     }
 
-    return PhotometricDisplayMode_Default;
+    return RadiographyPhotometricDisplayMode_Default;
   }
 
 
@@ -345,7 +345,7 @@
                                                      const std::string& instance,
                                                      unsigned int frame,
                                                      Deprecated::DicomFrameConverter* converter,  // takes ownership
-                                                     PhotometricDisplayMode preferredPhotometricDisplayMode,
+                                                     RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
                                                      RadiographyLayer::Geometry* geometry)
   {
     RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)));
@@ -642,9 +642,9 @@
 
     createDicomRequestContent["Tags"] = dicomTags;
 
-    PhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode();
-    if ((invert && photometricMode != PhotometricDisplayMode_Monochrome2) ||
-        (!invert && photometricMode == PhotometricDisplayMode_Monochrome1))
+    RadiographyPhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode();
+    if ((invert && photometricMode != RadiographyPhotometricDisplayMode_Monochrome2) ||
+        (!invert && photometricMode == RadiographyPhotometricDisplayMode_Monochrome1))
     {
       createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1";
     }
--- a/Framework/Radiography/RadiographyScene.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,9 +22,9 @@
 #pragma once
 
 #include "RadiographyLayer.h"
-#include "../Toolbox/DicomFrameConverter.h"
-#include "../Toolbox/OrthancApiClient.h"
-#include "Framework/StoneEnumerations.h"
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
+#include "../Deprecated/Toolbox/OrthancApiClient.h"
+#include "../StoneEnumerations.h"
 #include "Core/Images/Image.h"
 #include "Core/Images/ImageProcessing.h"
 
@@ -172,7 +172,7 @@
     virtual void SetWindowing(float center,
                               float width);
 
-    PhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const;
+    RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const;
 
     RadiographyLayer& LoadText(const Orthanc::Font& font,
                                const std::string& utf8,
@@ -194,7 +194,7 @@
                                              const std::string& instance,
                                              unsigned int frame,
                                              Deprecated::DicomFrameConverter* converter,  // takes ownership
-                                             PhotometricDisplayMode preferredPhotometricDisplayMode,
+                                             RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode,
                                              RadiographyLayer::Geometry* geometry);
 
     virtual RadiographyLayer& LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
--- a/Framework/Radiography/RadiographySceneReader.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographySceneReader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #include "RadiographySceneReader.h"
 
-#include <Framework/Toolbox/DicomFrameConverter.h>
+#include "../Deprecated/Toolbox/DicomFrameConverter.h"
 
 #include <Core/Images/FontRegistry.h>
 #include <Core/Images/PngReader.h>
@@ -33,7 +33,7 @@
 
   void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */,
                                      Deprecated::DicomFrameConverter* dicomFrameConverter  /* takes ownership */,
-                                     PhotometricDisplayMode preferredPhotometricDisplayMode
+                                     RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode
                                      )
   {
     dicomImage_.reset(dicomImage);
--- a/Framework/Radiography/RadiographySceneReader.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographySceneReader.h	Mon Jun 24 14:35:00 2019 +0200
@@ -26,7 +26,7 @@
 #include "RadiographyDicomLayer.h"
 #include "RadiographyMaskLayer.h"
 #include "RadiographyTextLayer.h"
-#include "../Toolbox/OrthancApiClient.h"
+#include "../Deprecated/Toolbox/OrthancApiClient.h"
 
 #include <json/value.h>
 #include <Core/Images/FontRegistry.h>
@@ -42,8 +42,8 @@
     RadiographyScene&                               scene_;
     const Orthanc::FontRegistry*                    fontRegistry_;
     std::auto_ptr<Orthanc::ImageAccessor>           dicomImage_;
-    std::auto_ptr<Deprecated::DicomFrameConverter>              dicomFrameConverter_;
-    PhotometricDisplayMode                          preferredPhotometricDisplayMode_;
+    std::auto_ptr<Deprecated::DicomFrameConverter>  dicomFrameConverter_;
+    RadiographyPhotometricDisplayMode               preferredPhotometricDisplayMode_;
 
   public:
     RadiographySceneBuilder(RadiographyScene& scene) :
@@ -56,7 +56,7 @@
     void Read(const Json::Value& input,
               Orthanc::ImageAccessor* dicomImage, // takes ownership
               Deprecated::DicomFrameConverter* dicomFrameConverter, // takes ownership
-              PhotometricDisplayMode preferredPhotometricDisplayMode
+              RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode
               );
 
     void SetFontRegistry(const Orthanc::FontRegistry& fontRegistry)
--- a/Framework/Radiography/RadiographyWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyWidget.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -35,7 +35,7 @@
     // MONOCHROME1 images must be inverted and the user can invert the 
     // image, too -> XOR the two
     return (scene_->GetPreferredPhotomotricDisplayMode() == 
-      PhotometricDisplayMode_Monochrome1) ^ invert_; 
+            RadiographyPhotometricDisplayMode_Monochrome1) ^ invert_; 
   }
 
   void RadiographyWidget::RenderBackground(
@@ -46,14 +46,14 @@
 
     switch (scene_->GetPreferredPhotomotricDisplayMode())
     {
-    case PhotometricDisplayMode_Monochrome1:
-    case PhotometricDisplayMode_Default:
+    case RadiographyPhotometricDisplayMode_Monochrome1:
+    case RadiographyPhotometricDisplayMode_Default:
       if (IsInvertedInternal())
         backgroundValue = maxValue;
       else
         backgroundValue = minValue;
       break;
-    case PhotometricDisplayMode_Monochrome2:
+    case RadiographyPhotometricDisplayMode_Monochrome2:
       if (IsInvertedInternal())
         backgroundValue = minValue;
       else
--- a/Framework/Radiography/RadiographyWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyWidget.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../Widgets/WorldSceneWidget.h"
+#include "../Deprecated/Widgets/WorldSceneWidget.h"
 #include "RadiographyScene.h"
 
 
--- a/Framework/Radiography/RadiographyWindowingTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Radiography/RadiographyWindowingTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/UndoRedoStack.h"
-#include "../Widgets/IWorldSceneMouseTracker.h"
+#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h"
 #include "RadiographyScene.h"
 
 namespace OrthancStone
--- a/Framework/Scene2D/CairoCompositor.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/CairoCompositor.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -24,6 +24,7 @@
 #include "Internals/CairoColorTextureRenderer.h"
 #include "Internals/CairoFloatTextureRenderer.h"
 #include "Internals/CairoInfoPanelRenderer.h"
+#include "Internals/CairoLookupTableTextureRenderer.h"
 #include "Internals/CairoPolylineRenderer.h"
 #include "Internals/CairoTextRenderer.h"
 
@@ -60,6 +61,9 @@
       case ISceneLayer::Type_FloatTexture:
         return new Internals::CairoFloatTextureRenderer(*this, layer);
 
+      case ISceneLayer::Type_LookupTableTexture:
+        return new Internals::CairoLookupTableTextureRenderer(*this, layer);
+
       case ISceneLayer::Type_Text:
       {
         const TextSceneLayer& l = dynamic_cast<const TextSceneLayer&>(layer);
--- a/Framework/Scene2D/CairoCompositor.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/CairoCompositor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Fonts/GlyphBitmapAlphabet.h"
-#include "../Viewport/CairoContext.h"
+#include "../Wrappers/CairoContext.h"
 #include "Internals/CompositorHelper.h"
 #include "Internals/ICairoContextProvider.h"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Color.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,82 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <stdint.h>
+
+namespace OrthancStone
+{
+  class Color
+  {
+  private:
+    uint8_t  red_;
+    uint8_t  green_;
+    uint8_t  blue_;
+
+  public:
+    Color() :
+      red_(255),
+      green_(255),
+      blue_(255)
+    {
+    }
+
+    Color(uint8_t red,
+          uint8_t green,
+          uint8_t blue) :
+      red_(red),
+      green_(green),
+      blue_(blue)
+    {
+    }
+
+    uint8_t GetRed() const
+    {
+      return red_;
+    }
+
+    uint8_t GetGreen() const
+    {
+      return green_;
+    }
+
+    uint8_t GetBlue() const
+    {
+      return blue_;
+    }
+
+    float GetRedAsFloat() const
+    {
+      return static_cast<float>(red_) / 255.0f;
+    }
+
+    float GetGreenAsFloat() const
+    {
+      return static_cast<float>(green_) / 255.0f;
+    }
+
+    float GetBlueAsFloat() const
+    {
+      return static_cast<float>(blue_) / 255.0f;
+    }
+  };
+}
--- a/Framework/Scene2D/ColorSceneLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/ColorSceneLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,18 +22,17 @@
 #pragma once
 
 #include "ISceneLayer.h"
-#include <Core/Enumerations.h>
+#include "Color.h"
 
-#include <stdint.h>
+#include <Core/Enumerations.h>  // For ORTHANC_OVERRIDE
 
 namespace OrthancStone
 {
+  // TODO - Is this needed?
   class ColorSceneLayer : public ISceneLayer
   {
   private:
-    uint8_t  red_;
-    uint8_t  green_;
-    uint8_t  blue_;
+    Color    color_;
     uint64_t revision_;
 
   protected:
@@ -45,9 +44,6 @@
 
   public:
     ColorSceneLayer() :
-      red_(255),
-      green_(255),
-      blue_(255),
       revision_(0)
     {
     }
@@ -61,40 +57,19 @@
                   uint8_t green,
                   uint8_t blue)
     {
-      red_ = red;
-      green_ = green;
-      blue_ = blue;
+      color_ = Color(red, green, blue);
       BumpRevision();
     }
 
-    uint8_t GetRed() const
+    void SetColor(const Color& color)
     {
-      return red_;
-    }
-
-    uint8_t GetGreen() const
-    {
-      return green_;
+      color_ = color;
+      BumpRevision();
     }
 
-    uint8_t GetBlue() const
-    {
-      return blue_;
-    }
-
-    float GetRedAsFloat() const
+    const Color& GetColor() const
     {
-      return static_cast<float>(red_) / 255.0f;
-    }
-
-    float GetGreenAsFloat() const
-    {
-      return static_cast<float>(green_) / 255.0f;
-    }
-
-    float GetBlueAsFloat() const
-    {
-      return static_cast<float>(blue_) / 255.0f;
+      return color_;
     }
   };
 }
--- a/Framework/Scene2D/ColorTextureSceneLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/ColorTextureSceneLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -28,6 +28,7 @@
   class ColorTextureSceneLayer : public TextureBaseSceneLayer
   {
   public:
+    // If using RGBA32, premultiplied alpha is assumed
     ColorTextureSceneLayer(const Orthanc::ImageAccessor& texture);
 
     virtual ISceneLayer* Clone() const;
--- a/Framework/Scene2D/FloatTextureSceneLayer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -27,7 +27,8 @@
 
 namespace OrthancStone
 {
-  FloatTextureSceneLayer::FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture)
+  FloatTextureSceneLayer::FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture) :
+    inverted_(false)
   {
     {
       std::auto_ptr<Orthanc::ImageAccessor> t(
@@ -86,6 +87,13 @@
   }
 
 
+  void FloatTextureSceneLayer::SetInverted(bool inverted)
+  {
+    inverted_ = inverted;
+    IncrementRevision();
+  }
+
+  
   void FloatTextureSceneLayer::FitRange()
   {
     float minValue, maxValue;
@@ -116,6 +124,7 @@
     cloned->windowing_ = windowing_;
     cloned->customCenter_ = customCenter_;
     cloned->customWidth_ = customWidth_;
+    cloned->inverted_ = inverted_;
 
     return cloned.release();
   }
--- a/Framework/Scene2D/FloatTextureSceneLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/FloatTextureSceneLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -31,9 +31,10 @@
     ImageWindowing   windowing_;
     float            customCenter_;
     float            customWidth_;
+    bool             inverted_;
 
   public:
-    // The pixel format must be "Float32"
+    // The pixel format must be convertible to "Float32"
     FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture);
 
     void SetWindowing(ImageWindowing windowing);
@@ -49,6 +50,14 @@
       return windowing_;
     }
 
+    // To achieve MONOCHROME1 photometric interpretation
+    void SetInverted(bool inverted);
+
+    bool IsInverted() const
+    {
+      return inverted_;
+    }
+
     void FitRange();
 
     virtual ISceneLayer* Clone() const;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,81 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GrayscaleStyleConfigurator.h"
+
+#include "FloatTextureSceneLayer.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing)
+  {
+    hasWindowing_ = true;
+    windowing_ = windowing;
+    revision_++;
+  }
+
+
+  void GrayscaleStyleConfigurator::SetLinearInterpolation(bool enabled)
+  {
+    linearInterpolation_ = enabled;
+    revision_++;
+  }
+
+  
+  TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromImage(
+    const Orthanc::ImageAccessor& image) const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+  
+  TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromDicom(
+    const Orthanc::ImageAccessor& frame,
+    const DicomInstanceParameters& parameters) const
+  {
+    std::auto_ptr<TextureBaseSceneLayer> layer(parameters.CreateTexture(frame));
+
+    if (layer.get() == NULL ||
+        layer->GetTexture().GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+    else
+    {
+      return layer.release();
+    }
+  }
+
+
+  void GrayscaleStyleConfigurator::ApplyStyle(ISceneLayer& layer) const
+  {
+    FloatTextureSceneLayer& l = dynamic_cast<FloatTextureSceneLayer&>(layer);
+    
+    l.SetLinearInterpolation(linearInterpolation_);
+
+    if (hasWindowing_)
+    {
+      l.SetWindowing(windowing_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,73 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerStyleConfigurator.h"
+
+namespace OrthancStone
+{
+  /**
+  Creates layers to display the supplied image in grayscale. No dynamic 
+  style is available.
+  */
+  class GrayscaleStyleConfigurator : public ILayerStyleConfigurator
+  {
+  private:
+    uint64_t        revision_;
+    bool            linearInterpolation_;
+    bool            hasWindowing_;
+    ImageWindowing  windowing_;
+
+    // TODO - Add custom windowing
+    
+  public:
+    GrayscaleStyleConfigurator() :
+      revision_(0),
+      linearInterpolation_(false),
+      hasWindowing_(false)
+    {
+    }
+
+    void SetWindowing(ImageWindowing windowing);
+
+    void SetLinearInterpolation(bool enabled);
+
+    bool IsLinearInterpolation() const
+    {
+      return linearInterpolation_;
+    }
+
+    virtual uint64_t GetRevision() const
+    {
+      return revision_;
+    }
+    
+    virtual TextureBaseSceneLayer* CreateTextureFromImage(
+      const Orthanc::ImageAccessor& image) const;
+
+    virtual TextureBaseSceneLayer* CreateTextureFromDicom(
+      const Orthanc::ImageAccessor& frame,
+      const DicomInstanceParameters& parameters) const;
+
+    virtual void ApplyStyle(ISceneLayer& layer) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/ILayerStyleConfigurator.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/DicomInstanceParameters.h"
+
+namespace OrthancStone
+{
+  /**
+  This interface is implemented by objects able to create an ISceneLayer 
+  suitable to display the Orthanc image supplied to the CreateTextureXX 
+  factory methods (taking Dicom parameters into account if relevant).
+
+  It can also refresh the style of an existing layer afterwards, to match
+  the configurator settings.
+  */
+  class ILayerStyleConfigurator
+  {
+  public:
+    virtual ~ILayerStyleConfigurator()
+    {
+    }
+    
+    virtual uint64_t GetRevision() const = 0;
+    
+    virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const = 0;
+
+    virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
+                                                          const DicomInstanceParameters& parameters) const = 0;
+
+    virtual void ApplyStyle(ISceneLayer& layer) const = 0;
+  };
+}
--- a/Framework/Scene2D/ISceneLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/ISceneLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -37,7 +37,8 @@
       Type_ColorTexture,
       Type_Polyline,
       Type_Text,
-      Type_FloatTexture
+      Type_FloatTexture,
+      Type_LookupTableTexture
     };
 
     virtual ~ISceneLayer()
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -44,13 +44,17 @@
       isLinearInterpolation_ = l.IsLinearInterpolation();
     }
 
-    
-    void CairoColorTextureRenderer::Render(const AffineTransform2D& transform)
+
+    void CairoColorTextureRenderer::RenderColorTexture(ICairoContextProvider& target,
+                                                       const AffineTransform2D& transform,
+                                                       CairoSurface& texture,
+                                                       const AffineTransform2D& textureTransform,
+                                                       bool isLinearInterpolation)
     {
-      cairo_t* cr = target_.GetCairoContext();
+      cairo_t* cr = target.GetCairoContext();
 
       AffineTransform2D t =
-        AffineTransform2D::Combine(transform, textureTransform_);
+        AffineTransform2D::Combine(transform, textureTransform);
       Matrix h = t.GetHomogeneousMatrix();
       
       cairo_save(cr);
@@ -60,9 +64,9 @@
       cairo_transform(cr, &m);
 
       cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
-      cairo_set_source_surface(cr, texture_.GetObject(), 0, 0);
+      cairo_set_source_surface(cr, texture.GetObject(), 0, 0);
 
-      if (isLinearInterpolation_)
+      if (isLinearInterpolation)
       {
         cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
       }
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
@@ -43,7 +43,17 @@
 
       virtual void Update(const ISceneLayer& layer);
     
-      virtual void Render(const AffineTransform2D& transform);
+      virtual void Render(const AffineTransform2D& transform)
+      {
+        RenderColorTexture(target_, transform, texture_,
+                           textureTransform_, isLinearInterpolation_);
+      }
+
+      static void RenderColorTexture(ICairoContextProvider& target,
+                                     const AffineTransform2D& transform,
+                                     CairoSurface& texture,
+                                     const AffineTransform2D& textureTransform,
+                                     bool isLinearInterpolation);
     };
   }
 }
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -21,6 +21,7 @@
 
 #include "CairoFloatTextureRenderer.h"
 
+#include "CairoColorTextureRenderer.h"
 #include "../FloatTextureSceneLayer.h"
 
 namespace OrthancStone
@@ -71,6 +72,11 @@
 
           uint8_t vv = static_cast<uint8_t>(v);
 
+          if (l.IsInverted())
+          {
+            vv = 255 - vv;
+          }
+
           q[0] = vv;
           q[1] = vv;
           q[2] = vv;
@@ -84,33 +90,8 @@
       
     void CairoFloatTextureRenderer::Render(const AffineTransform2D& transform)
     {
-      cairo_t* cr = target_.GetCairoContext();
-
-      AffineTransform2D t =
-        AffineTransform2D::Combine(transform, textureTransform_);
-      Matrix h = t.GetHomogeneousMatrix();
-      
-      cairo_save(cr);
-
-      cairo_matrix_t m;
-      cairo_matrix_init(&m, h(0, 0), h(1, 0), h(0, 1), h(1, 1), h(0, 2), h(1, 2));
-      cairo_transform(cr, &m);
-
-      cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
-      cairo_set_source_surface(cr, texture_.GetObject(), 0, 0);
-
-      if (isLinearInterpolation_)
-      {
-        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
-      }
-      else
-      {
-        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
-      }
-
-      cairo_paint(cr);
-
-      cairo_restore(cr);
+      CairoColorTextureRenderer::RenderColorTexture(target_, transform, texture_,
+                                                    textureTransform_, isLinearInterpolation_);
     }
   }
 }
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
--- a/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "CompositorHelper.h"
 #include "ICairoContextProvider.h"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,108 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CairoLookupTableTextureRenderer.h"
+
+#include "CairoColorTextureRenderer.h"
+#include "../LookupTableTextureSceneLayer.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    void CairoLookupTableTextureRenderer::Update(const ISceneLayer& layer)
+    {
+      const LookupTableTextureSceneLayer& l = dynamic_cast<const LookupTableTextureSceneLayer&>(layer);
+
+      textureTransform_ = l.GetTransform();
+      isLinearInterpolation_ = l.IsLinearInterpolation();
+
+      const float a = l.GetMinValue();
+      float slope;
+
+      if (l.GetMinValue() >= l.GetMaxValue())
+      {
+        slope = 0;
+      }
+      else
+      {
+        slope = 256.0f / (l.GetMaxValue() - l.GetMinValue());
+      }
+
+      const Orthanc::ImageAccessor& source = l.GetTexture();
+      const unsigned int width = source.GetWidth();
+      const unsigned int height = source.GetHeight();
+      texture_.SetSize(width, height, true /* alpha channel is enabled */);
+
+      Orthanc::ImageAccessor target;
+      texture_.GetWriteableAccessor(target);
+
+      const std::vector<uint8_t>& lut = l.GetLookupTable();
+      if (lut.size() != 4 * 256)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      assert(source.GetFormat() == Orthanc::PixelFormat_Float32 &&
+             target.GetFormat() == Orthanc::PixelFormat_BGRA32 &&
+             sizeof(float) == 4);
+
+      for (unsigned int y = 0; y < height; y++)
+      {
+        const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+
+        for (unsigned int x = 0; x < width; x++)
+        {
+          float v = (*p - a) * slope;
+          if (v <= 0)
+          {
+            v = 0;
+          }
+          else if (v >= 255)
+          {
+            v = 255;
+          }
+
+          uint8_t vv = static_cast<uint8_t>(v);
+
+          q[0] = lut[4 * vv + 2];  // B
+          q[1] = lut[4 * vv + 1];  // G
+          q[2] = lut[4 * vv + 0];  // R
+          q[3] = lut[4 * vv + 3];  // A
+
+          p++;
+          q += 4;
+        }
+      }
+
+      cairo_surface_mark_dirty(texture_.GetObject());
+    }
+
+    void CairoLookupTableTextureRenderer::Render(const AffineTransform2D& transform)
+    {
+      CairoColorTextureRenderer::RenderColorTexture(target_, transform, texture_,
+                                                    textureTransform_, isLinearInterpolation_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,53 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Wrappers/CairoSurface.h"
+#include "CompositorHelper.h"
+#include "ICairoContextProvider.h"
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    class CairoLookupTableTextureRenderer : public CompositorHelper::ILayerRenderer
+    {
+    private:
+      ICairoContextProvider&  target_;
+      CairoSurface            texture_;
+      AffineTransform2D       textureTransform_;
+      bool                    isLinearInterpolation_;
+    
+    public:
+      CairoLookupTableTextureRenderer(ICairoContextProvider& target,
+                                      const ISceneLayer& layer) :
+        target_(target)
+      {
+        Update(layer);
+      }
+
+      virtual void Update(const ISceneLayer& layer);
+    
+      virtual void Render(const AffineTransform2D& transform);
+    };
+  }
+}
--- a/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -33,11 +33,15 @@
       
       cairo_t* cr = GetCairoContext();
 
-      cairo_set_source_rgb(cr, layer.GetRedAsFloat(), layer.GetGreenAsFloat(), layer.GetBlueAsFloat());
       cairo_set_line_width(cr, layer.GetThickness());
       
       for (size_t i = 0; i < layer.GetChainsCount(); i++)
       {
+        const Color& color = layer.GetColor(i);
+        cairo_set_source_rgb(cr, color.GetRedAsFloat(),
+                             color.GetGreenAsFloat(),
+                             color.GetBlueAsFloat());
+
         const PolylineSceneLayer::Chain& chain = layer.GetChain(i);
 
         if (!chain.empty())
@@ -62,9 +66,9 @@
             cairo_line_to(cr, p.GetX(), p.GetY());
           }
         }
+
+        cairo_stroke(cr);
       }
-
-      cairo_stroke(cr);
     }
   }
 }
--- a/Framework/Scene2D/Internals/CairoTextRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoTextRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -48,9 +48,7 @@
         }
         
         const unsigned int width = source->GetWidth();
-        const unsigned int red = layer.GetRed();
-        const unsigned int green = layer.GetGreen();
-        const unsigned int blue = layer.GetBlue();
+        const Color& color = layer.GetColor();
 
         for (unsigned int y = 0; y < source->GetHeight(); y++)
         {
@@ -62,9 +60,9 @@
             unsigned int alpha = *p;
 
             // Premultiplied alpha
-            q[0] = static_cast<uint8_t>((blue * alpha) / 255);
-            q[1] = static_cast<uint8_t>((green * alpha) / 255);
-            q[2] = static_cast<uint8_t>((red * alpha) / 255);
+            q[0] = static_cast<uint8_t>((color.GetBlue() * alpha) / 255);
+            q[1] = static_cast<uint8_t>((color.GetGreen() * alpha) / 255);
+            q[2] = static_cast<uint8_t>((color.GetRed() * alpha) / 255);
             q[3] = *p;
             
             p++;
@@ -85,7 +83,9 @@
         const TextSceneLayer& layer = GetLayer<TextSceneLayer>();
       
         cairo_t* cr = GetCairoContext();
-        cairo_set_source_rgb(cr, layer.GetRedAsFloat(), layer.GetGreenAsFloat(), layer.GetBlueAsFloat());
+        cairo_set_source_rgb(cr, layer.GetColor().GetRedAsFloat(),
+                             layer.GetColor().GetGreenAsFloat(),
+                             layer.GetColor().GetBlueAsFloat());
 
         double dx, dy;  // In pixels
         ComputeAnchorTranslation(dx, dy, layer.GetAnchor(), text_.GetWidth(),
--- a/Framework/Scene2D/Internals/CairoTextRenderer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CairoTextRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../../Fonts/GlyphBitmapAlphabet.h"
-#include "../../Viewport/CairoSurface.h"
+#include "../../Wrappers/CairoSurface.h"
 #include "../TextSceneLayer.h"
 #include "CairoBaseRenderer.h"
 
--- a/Framework/Scene2D/Internals/CompositorHelper.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/CompositorHelper.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,6 +23,10 @@
 
 #include "../Scene2D.h"
 
+#include <boost/noncopyable.hpp>
+
+#include <map>
+
 namespace OrthancStone
 {
   namespace Internals
--- a/Framework/Scene2D/Internals/FixedPointAligner.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/FixedPointAligner.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -18,26 +18,26 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-#include <Framework/Scene2DViewport/ViewportController.h>
+#include "../../Scene2DViewport/ViewportController.h"
 #include "FixedPointAligner.h"
 
 namespace OrthancStone
 {
   namespace Internals
   {
-    FixedPointAligner::FixedPointAligner(ViewportControllerWPtr controllerW,
+    FixedPointAligner::FixedPointAligner(boost::weak_ptr<ViewportController> controllerW,
                                          const ScenePoint2D& p) 
       : controllerW_(controllerW)
       , canvas_(p)
     {
-      ViewportControllerPtr controller = controllerW_.lock();
+      boost::shared_ptr<ViewportController> controller = controllerW_.lock();
       pivot_ = canvas_.Apply(controller->GetCanvasToSceneTransform());
     }
 
     
     void FixedPointAligner::Apply()
     {
-      ViewportControllerPtr controller = controllerW_.lock();
+      boost::shared_ptr<ViewportController> controller = controllerW_.lock();
       ScenePoint2D p = canvas_.Apply(controller->GetCanvasToSceneTransform());
 
       controller->SetSceneToCanvasTransform(
--- a/Framework/Scene2D/Internals/FixedPointAligner.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/FixedPointAligner.h	Mon Jun 24 14:35:00 2019 +0200
@@ -20,24 +20,24 @@
 
 #pragma once
 
-#include <Framework/Scene2DViewport/PointerTypes.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
+#include "../../Scene2DViewport/PredeclaredTypes.h"
+#include "../../Scene2D/ScenePoint2D.h"
 
 namespace OrthancStone
 {
   namespace Internals
   {
     // During a mouse event that modifies the view of a scene, keeps
-    // one point (the pivot) at the same position on the canvas
+    // one point (the pivot) at a fixed position on the canvas
     class FixedPointAligner : public boost::noncopyable
     {
     private:
-      ViewportControllerWPtr controllerW_;
+      boost::weak_ptr<ViewportController> controllerW_;
       ScenePoint2D           pivot_;
       ScenePoint2D           canvas_;
 
     public:
-      FixedPointAligner(ViewportControllerWPtr controllerW,
+      FixedPointAligner(boost::weak_ptr<ViewportController> controllerW,
                         const ScenePoint2D& p);
 
       void Apply();
--- a/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -28,7 +28,7 @@
   namespace Internals
   {
     OpenGLBasicPolylineRenderer::OpenGLBasicPolylineRenderer(OpenGL::IOpenGLContext& context,
-      const PolylineSceneLayer& layer) :
+                                                             const PolylineSceneLayer& layer) :
       context_(context)
     {
       layer_.Copy(layer);
@@ -42,12 +42,14 @@
         transform);
 
       glUseProgram(0);
-      glColor3ub(layer_.GetRed(), layer_.GetGreen(), layer_.GetBlue());
 
       glBegin(GL_LINES);
 
       for (size_t i = 0; i < layer_.GetChainsCount(); i++)
       {
+        const Color& color = layer_.GetColor(i);
+        glColor3ub(color.GetRed(), color.GetGreen(), color.GetBlue());
+
         const PolylineSceneLayer::Chain& chain = layer_.GetChain(i);
 
         if (chain.size() > 1)
--- a/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -29,28 +29,28 @@
 
 static const char* FRAGMENT_SHADER = 
   ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE
-  "uniform float u_offset;                       \n"
-  "uniform float u_slope;                        \n"
-  "uniform float u_windowCenter;                 \n"
-  "uniform float u_windowWidth;                  \n"
-  "uniform sampler2D u_texture;                  \n"
-  "varying vec2 v_texcoord;                      \n"
-  "void main()                                   \n"
-  "{                                             \n"
-  "  vec4 t = texture2D(u_texture, v_texcoord);  \n"
-  "  float v = (t.r * 256.0 + t.g) * 256.0;      \n"
-  "  v = v * u_slope + u_offset;                 \n"  // (*)
-  "  float a = u_windowCenter - u_windowWidth;   \n"
-  "  float dy = 1.0 / (2.0 * u_windowWidth);     \n"
-  "  if (v <= a)                                 \n"
-  "    v = 0.0;                                  \n"
-  "  else                                        \n"
-  "  {                                           \n"
-  "    v = (v - a) * dy;                         \n"
-  "    if (v >= 1.0)                             \n"
-  "      v = 1.0;                                \n"
-  "  }                                           \n"
-  "  gl_FragColor = vec4(v, v, v, 1);            \n"
+  "uniform float u_offset;                           \n"
+  "uniform float u_slope;                            \n"
+  "uniform float u_windowCenter;                     \n"
+  "uniform float u_windowWidth;                      \n"
+  "uniform sampler2D u_texture;                      \n"
+  "varying vec2 v_texcoord;                          \n"
+  "void main()                                       \n"
+  "{                                                 \n"
+  "  vec4 t = texture2D(u_texture, v_texcoord);      \n"
+  "  float v = (t.r * 256.0 + t.g) * 256.0;          \n"
+  "  v = v * u_slope + u_offset;                     \n"  // (*)
+  "  float a = u_windowCenter - u_windowWidth / 2.0; \n"
+  "  float dy = 1.0 / u_windowWidth;                 \n"
+  "  if (v <= a)                                     \n"
+  "    v = 0.0;                                      \n"
+  "  else                                            \n"
+  "  {                                               \n"
+  "    v = (v - a) * dy;                             \n"
+  "    if (v >= 1.0)                                 \n"
+  "      v = 1.0;                                    \n"
+  "  }                                               \n"
+  "  gl_FragColor = vec4(v, v, v, 1);                \n"
   "}";
 
 
--- a/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -26,6 +26,7 @@
 
 
 static const unsigned int COMPONENTS_POSITION = 3;
+static const unsigned int COMPONENTS_COLOR = 3;
 static const unsigned int COMPONENTS_MITER = 2;
 
 
@@ -33,12 +34,15 @@
   ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE
   "attribute vec2 a_miter_direction; \n"
   "attribute vec4 a_position;        \n"
+  "attribute vec3 a_color;           \n"
   "uniform float u_thickness;        \n"
   "uniform mat4 u_matrix;            \n"
   "varying float v_distance;         \n"
+  "varying vec3 v_color;             \n"
   "void main()                       \n"
   "{                                 \n"
   "  v_distance = a_position.z;      \n"
+  "  v_color = a_color;              \n"
   "  gl_Position = u_matrix * vec4(a_position.xy + a_position.z * a_miter_direction * u_thickness, 0, 1); \n"
   "}";
 
@@ -47,20 +51,20 @@
   ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE
   "uniform bool u_antialiasing;           \n"
   "uniform float u_antialiasing_start;    \n"
-  "uniform vec3 u_color;                  \n"
   "varying float v_distance;              \n"   // Distance of the point to the segment
+  "varying vec3 v_color;                  \n"
   "void main()                            \n"
   "{                                      \n"
   "  float d = abs(v_distance);           \n"
   "  if (!u_antialiasing ||               \n"
   "      d <= u_antialiasing_start)       \n"
-  "    gl_FragColor = vec4(u_color, 1);   \n"
+  "    gl_FragColor = vec4(v_color, 1);   \n"
   "  else if (d >= 1.0)                   \n"
   "    gl_FragColor = vec4(0, 0, 0, 0);   \n"
   "  else                                 \n"
   "  {                                    \n"
   "    float alpha = 1.0 - smoothstep(u_antialiasing_start, 1.0, d); \n"
-  "    gl_FragColor = vec4(u_color * alpha, alpha); \n"
+  "    gl_FragColor = vec4(v_color * alpha, alpha); \n"
   "  }                                    \n"
   "}";
 
@@ -197,7 +201,9 @@
       }
 
       void AddTriangles(std::vector<float>& coords,
-                        std::vector<float>& miterDirections)
+                        std::vector<float>& miterDirections,
+                        std::vector<float>& colors,
+                        const Color& color)
       {
         if (isEmpty_)
         {
@@ -239,6 +245,14 @@
         miterDirections.push_back(static_cast<float>(miterY1_));
         miterDirections.push_back(static_cast<float>(miterX2_));
         miterDirections.push_back(static_cast<float>(miterY2_));
+
+        // Add the colors of the 2 triangles (leading to 2 * 3 values)
+        for (unsigned int i = 0; i < 6; i++)
+        {
+          colors.push_back(color.GetRedAsFloat());
+          colors.push_back(color.GetGreenAsFloat());
+          colors.push_back(color.GetBlueAsFloat());
+        }
       }        
     };
 
@@ -247,10 +261,7 @@
                                    const PolylineSceneLayer& layer) :
       context_(context),
       verticesCount_(0),
-      thickness_(static_cast<float>(layer.GetThickness())),
-      red_(layer.GetRedAsFloat()),
-      green_(layer.GetGreenAsFloat()),
-      blue_(layer.GetBlueAsFloat())
+      thickness_(static_cast<float>(layer.GetThickness()))
     {
       // High-level reference:
       // https://mattdesl.svbtle.com/drawing-lines-is-hard
@@ -271,8 +282,9 @@
         countVertices += countSegments * 2 * 3;
       }
 
-      std::vector<float>  coords, miterDirections;
+      std::vector<float>  coords, colors, miterDirections;
       coords.reserve(countVertices * COMPONENTS_POSITION);
+      colors.reserve(countVertices * COMPONENTS_COLOR);
       miterDirections.reserve(countVertices * COMPONENTS_MITER);
 
       for (size_t i = 0; i < layer.GetChainsCount(); i++)
@@ -307,24 +319,29 @@
           {
             if (!segments[j].IsEmpty())
             {
-              segments[j].AddTriangles(coords, miterDirections);
+              segments[j].AddTriangles(coords, miterDirections, colors, layer.GetColor(i));
             }
           }
         }
       }
 
+      assert(coords.size() == colors.size());
+
       if (!coords.empty())
       {
         verticesCount_ = coords.size() / COMPONENTS_POSITION;
 
         context_.MakeCurrent();
-        glGenBuffers(2, buffers_);
+        glGenBuffers(3, buffers_);
 
         glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]);
         glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coords.size(), &coords[0], GL_STATIC_DRAW);
 
         glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]);
         glBufferData(GL_ARRAY_BUFFER, sizeof(float) * miterDirections.size(), &miterDirections[0], GL_STATIC_DRAW);
+
+        glBindBuffer(GL_ARRAY_BUFFER, buffers_[2]);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(float) * colors.size(), &colors[0], GL_STATIC_DRAW);
       }
     }
 
@@ -334,7 +351,7 @@
       if (!IsEmpty())
       {
         context_.MakeCurrent();
-        glDeleteBuffers(2, buffers_);
+        glDeleteBuffers(3, buffers_);
       }
     }
 
@@ -365,10 +382,22 @@
     }
 
 
+    GLuint OpenGLLinesProgram::Data::GetColorsBuffer() const
+    {
+      if (IsEmpty())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return buffers_[2];
+      }
+    }
+
+
     OpenGLLinesProgram::OpenGLLinesProgram(OpenGL::IOpenGLContext&  context) :
       context_(context)
     {
-
       context_.MakeCurrent();
 
       program_.reset(new OpenGL::OpenGLProgram);
@@ -388,13 +417,12 @@
 
         GLint locationPosition = program_->GetAttributeLocation("a_position");
         GLint locationMiterDirection = program_->GetAttributeLocation("a_miter_direction");
+        GLint locationColor = program_->GetAttributeLocation("a_color");
 
         float m[16];
         transform.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight());
 
         glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m);
-        glUniform3f(program_->GetUniformLocation("u_color"), 
-                    data.GetRed(), data.GetGreen(), data.GetBlue());
 
         glBindBuffer(GL_ARRAY_BUFFER, data.GetVerticesBuffer());
         glEnableVertexAttribArray(locationPosition);
@@ -404,6 +432,10 @@
         glEnableVertexAttribArray(locationMiterDirection);
         glVertexAttribPointer(locationMiterDirection, COMPONENTS_MITER, GL_FLOAT, GL_FALSE, 0, 0);
 
+        glBindBuffer(GL_ARRAY_BUFFER, data.GetColorsBuffer());
+        glEnableVertexAttribArray(locationColor);
+        glVertexAttribPointer(locationColor, COMPONENTS_COLOR, GL_FLOAT, GL_FALSE, 0, 0);
+
         glUniform1i(program_->GetUniformLocation("u_antialiasing"), (antialiasing ? 1 : 0));
 
         const double zoom = transform.ComputeZoom();
@@ -464,6 +496,7 @@
 
         glDisableVertexAttribArray(locationPosition);
         glDisableVertexAttribArray(locationMiterDirection);
+        glDisableVertexAttribArray(locationColor);
       }
     }
   }
--- a/Framework/Scene2D/Internals/OpenGLLinesProgram.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.h	Mon Jun 24 14:35:00 2019 +0200
@@ -39,12 +39,9 @@
         class Segment;
         
         OpenGL::IOpenGLContext&  context_;
-        GLuint                   buffers_[2];
+        GLuint                   buffers_[3];
         size_t                   verticesCount_;
         float                    thickness_;
-        float                    red_;
-        float                    green_;
-        float                    blue_;
 
       public:
         Data(OpenGL::IOpenGLContext& context,
@@ -66,25 +63,12 @@
 
         GLuint GetMiterDirectionsBuffer() const;
 
+        GLuint GetColorsBuffer() const;
+
         float GetThickness() const
         {
           return thickness_;
         }
-
-        float GetRed() const
-        {
-          return red_;
-        }
-
-        float GetGreen() const
-        {
-          return green_;
-        }
-
-        float GetBlue() const
-        {
-          return blue_;
-        }
       };
       
     private:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,140 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OpenGLLookupTableTextureRenderer.h"
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    void OpenGLLookupTableTextureRenderer::LoadTexture(
+      const LookupTableTextureSceneLayer& layer)
+    {
+      const Orthanc::ImageAccessor& source = layer.GetTexture();
+      const unsigned int width = source.GetWidth();
+      const unsigned int height = source.GetHeight();
+
+      if ((texture_.get() == NULL) || 
+          (texture_->GetWidth() != width) || 
+          (texture_->GetHeight() != height))
+      {
+
+        texture_.reset(new Orthanc::Image(
+          Orthanc::PixelFormat_RGBA32,
+          width,
+          height,
+          false));
+      }
+
+      {
+
+        const float a = layer.GetMinValue();
+        float slope = 0;
+
+        if (layer.GetMinValue() >= layer.GetMaxValue())
+        {
+          slope = 0;
+        }
+        else
+        {
+          slope = 256.0f / (layer.GetMaxValue() - layer.GetMinValue());
+        }
+
+        Orthanc::ImageAccessor target;
+        texture_->GetWriteableAccessor(target);
+
+        const std::vector<uint8_t>& lut = layer.GetLookupTable();
+        if (lut.size() != 4 * 256)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        assert(source.GetFormat() == Orthanc::PixelFormat_Float32 &&
+          target.GetFormat() == Orthanc::PixelFormat_RGBA32 &&
+          sizeof(float) == 4);
+
+        for (unsigned int y = 0; y < height; y++)
+        {
+          const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
+          uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+
+          for (unsigned int x = 0; x < width; x++)
+          {
+            float v = (*p - a) * slope;
+            if (v <= 0)
+            {
+              v = 0;
+            }
+            else if (v >= 255)
+            {
+              v = 255;
+            }
+
+            uint8_t vv = static_cast<uint8_t>(v);
+
+            q[0] = lut[4 * vv + 0];  // R
+            q[1] = lut[4 * vv + 1];  // G
+            q[2] = lut[4 * vv + 2];  // B
+            q[3] = lut[4 * vv + 3];  // A
+
+            p++;
+            q += 4;
+          }
+        }
+      }
+
+      context_.MakeCurrent();
+      glTexture_.reset(new OpenGL::OpenGLTexture);
+      glTexture_->Load(*texture_, layer.IsLinearInterpolation());
+      layerTransform_ = layer.GetTransform();
+    }
+
+    
+    OpenGLLookupTableTextureRenderer::OpenGLLookupTableTextureRenderer(
+      OpenGL::IOpenGLContext&                 context,
+      OpenGLColorTextureProgram&              program,
+      const LookupTableTextureSceneLayer&     layer)
+      : context_(context)
+      , program_(program)
+    {
+      LoadTexture(layer);
+    }
+
+    
+    void OpenGLLookupTableTextureRenderer::Render(const AffineTransform2D& transform)
+    {
+      if (glTexture_.get() != NULL)
+      {
+        program_.Apply(
+          *glTexture_, 
+          AffineTransform2D::Combine(transform, layerTransform_), 
+          true);
+      }
+    }
+
+    
+    void OpenGLLookupTableTextureRenderer::Update(const ISceneLayer& layer)
+    {
+      // Should never happen (no revisions in color textures)
+      LoadTexture(dynamic_cast<const LookupTableTextureSceneLayer&>(layer));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,56 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OpenGLColorTextureProgram.h"
+#include "CompositorHelper.h"
+#include "../LookupTableTextureSceneLayer.h"
+
+#include <Core/Images/Image.h>
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    class OpenGLLookupTableTextureRenderer : public CompositorHelper::ILayerRenderer
+    {
+    private:
+      OpenGL::IOpenGLContext&                context_;
+      OpenGLColorTextureProgram&             program_;
+      std::auto_ptr<OpenGL::OpenGLTexture>   glTexture_;
+      std::auto_ptr<Orthanc::Image>          texture_;
+      AffineTransform2D                      layerTransform_;
+
+      void LoadTexture(const LookupTableTextureSceneLayer& layer);
+
+    public:
+      OpenGLLookupTableTextureRenderer(
+        OpenGL::IOpenGLContext&             context,
+        OpenGLColorTextureProgram&          program,
+        const LookupTableTextureSceneLayer& layer);
+
+      virtual void Render(const AffineTransform2D& transform);
+
+      virtual void Update(const ISceneLayer& layer);
+    };
+  }
+}
--- a/Framework/Scene2D/Internals/OpenGLTextProgram.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLTextProgram.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -75,9 +75,9 @@
                                   const GlyphTextureAlphabet& alphabet,
                                   const TextSceneLayer& layer) :
       context_(context),
-      red_(layer.GetRedAsFloat()),
-      green_(layer.GetGreenAsFloat()),
-      blue_(layer.GetBlueAsFloat()),
+      red_(layer.GetColor().GetRedAsFloat()),
+      green_(layer.GetColor().GetGreenAsFloat()),
+      blue_(layer.GetColor().GetBlueAsFloat()),
       x_(layer.GetX()),
       y_(layer.GetY()),
       border_(layer.GetBorder()),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,90 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LookupTableStyleConfigurator.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  LookupTableStyleConfigurator::LookupTableStyleConfigurator() :
+    revision_(0),
+    hasLut_(false),
+    hasRange_(false)
+  {
+  }
+
+
+  void LookupTableStyleConfigurator::SetLookupTable(Orthanc::EmbeddedResources::FileResourceId resource)
+  {
+    hasLut_ = true;
+    Orthanc::EmbeddedResources::GetFileResource(lut_, resource);
+  }
+
+
+  void LookupTableStyleConfigurator::SetLookupTable(const std::string& lut)
+  {
+    hasLut_ = true;
+    lut_ = lut;
+  }
+
+
+  void LookupTableStyleConfigurator::SetRange(float minValue,
+                                              float maxValue)
+  {
+    if (minValue > maxValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      hasRange_ = true;
+      minValue_ = minValue;
+      maxValue_ = maxValue;
+    }
+  }
+
+    
+  TextureBaseSceneLayer* LookupTableStyleConfigurator::CreateTextureFromImage(const Orthanc::ImageAccessor& image) const
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+
+  void LookupTableStyleConfigurator::ApplyStyle(ISceneLayer& layer) const
+  {
+    LookupTableTextureSceneLayer& l = dynamic_cast<LookupTableTextureSceneLayer&>(layer);
+      
+    if (hasLut_)
+    {
+      l.SetLookupTable(lut_);
+    }
+
+    if (hasRange_)
+    {
+      l.SetRange(minValue_, maxValue_);
+    }
+    else
+    {
+      l.FitRange();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/LookupTableStyleConfigurator.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ILayerStyleConfigurator.h"
+
+#include <EmbeddedResources.h>
+
+namespace OrthancStone
+{
+  /**
+  This configurator supplies an API to set a display range and a LUT.
+  */
+  class LookupTableStyleConfigurator : public ILayerStyleConfigurator
+  {
+  private:
+    uint64_t     revision_;
+    bool         hasLut_;
+    std::string  lut_;
+    bool         hasRange_;
+    float        minValue_;
+    float        maxValue_;
+    
+  public:
+    LookupTableStyleConfigurator();
+
+    void SetLookupTable(Orthanc::EmbeddedResources::FileResourceId resource);
+
+    void SetLookupTable(const std::string& lut);
+
+    void SetRange(float minValue,
+                  float maxValue);
+
+    virtual uint64_t GetRevision() const
+    {
+      return revision_;
+    }
+    
+    virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const;
+
+    virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
+                                                          const DicomInstanceParameters& parameters) const
+    {
+      return parameters.CreateLookupTableTexture(frame);
+    }
+
+    virtual void ApplyStyle(ISceneLayer& layer) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,178 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LookupTableTextureSceneLayer.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  static void StringToVector(std::vector<uint8_t>& target,
+                             const std::string& source)
+  {
+    target.resize(source.size());
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      target[i] = source[i];
+    }
+  }
+
+  
+  LookupTableTextureSceneLayer::LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture)
+  {
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> t(
+        new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                           texture.GetWidth(), 
+                           texture.GetHeight(), 
+                           false));
+
+      Orthanc::ImageProcessing::Convert(*t, texture);
+      SetTexture(t.release());
+    }
+
+    SetLookupTableGrayscale();
+    SetRange(0, 1);
+  }
+
+
+  void LookupTableTextureSceneLayer::SetLookupTableGrayscale()
+  {
+    std::vector<uint8_t> rgb(3 * 256);
+
+    for (size_t i = 0; i < 256; i++)
+    {
+      rgb[3 * i]     = static_cast<uint8_t>(i);
+      rgb[3 * i + 1] = static_cast<uint8_t>(i);
+      rgb[3 * i + 2] = static_cast<uint8_t>(i);
+    }
+
+    SetLookupTableRgb(rgb);
+  }  
+
+
+  void LookupTableTextureSceneLayer::SetLookupTableRgb(const std::vector<uint8_t>& lut)
+  {
+    if (lut.size() != 3 * 256)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    lut_.resize(4 * 256);
+
+    for (size_t i = 0; i < 256; i++)
+    {
+      // Premultiplied alpha
+      
+      if (i == 0)
+      {
+        // Make zero transparent
+        lut_[4 * i] = 0;        // R
+        lut_[4 * i + 1] = 0;    // G
+        lut_[4 * i + 2] = 0;    // B
+        lut_[4 * i + 3] = 0;    // A
+      }
+      else
+      {
+        float a = static_cast<float>(i) / 255.0f;
+        
+        float r = static_cast<float>(lut[3 * i]) * a;
+        float g = static_cast<float>(lut[3 * i + 1]) * a;
+        float b = static_cast<float>(lut[3 * i + 2]) * a;
+        
+        lut_[4 * i] = static_cast<uint8_t>(std::floor(r));
+        lut_[4 * i + 1] = static_cast<uint8_t>(std::floor(g));
+        lut_[4 * i + 2] = static_cast<uint8_t>(std::floor(b));
+        lut_[4 * i + 3] = static_cast<uint8_t>(std::floor(a * 255.0f));
+      }
+    }
+
+    IncrementRevision();
+  }
+
+
+  void LookupTableTextureSceneLayer::SetLookupTable(const std::vector<uint8_t>& lut)
+  {
+    if (lut.size() == 4 * 256)
+    {
+      lut_ = lut;
+      IncrementRevision();
+    }
+    else if (lut.size() == 3 * 256)
+    {
+      SetLookupTableRgb(lut);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void LookupTableTextureSceneLayer::SetLookupTable(const std::string& lut)
+  {
+    std::vector<uint8_t> tmp;
+    StringToVector(tmp, lut);
+    SetLookupTable(tmp);
+  }
+
+  
+  void LookupTableTextureSceneLayer::SetRange(float minValue,
+                                              float maxValue)
+  {
+    if (minValue > maxValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      minValue_ = minValue;
+      maxValue_ = maxValue;
+      IncrementRevision();
+    }
+  }
+    
+
+  void LookupTableTextureSceneLayer::FitRange()
+  {
+    Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture());
+    assert(minValue_ <= maxValue_);
+    
+    IncrementRevision();
+  }
+
+    
+  ISceneLayer* LookupTableTextureSceneLayer::Clone() const
+  {
+    std::auto_ptr<LookupTableTextureSceneLayer> cloned
+      (new LookupTableTextureSceneLayer(GetTexture()));
+
+    cloned->CopyParameters(*this);
+    cloned->minValue_ = minValue_;
+    cloned->maxValue_ = maxValue_;
+    cloned->lut_ = lut_;
+
+    return cloned.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "TextureBaseSceneLayer.h"
+
+namespace OrthancStone
+{
+  class LookupTableTextureSceneLayer : public TextureBaseSceneLayer
+  {
+  private:
+    ImageWindowing        windowing_;
+    float                 minValue_;
+    float                 maxValue_;
+    std::vector<uint8_t>  lut_;
+
+    void SetLookupTableRgb(const std::vector<uint8_t>& lut);
+
+  public:
+    // The pixel format must be convertible to Float32
+    LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture);
+
+    void SetLookupTableGrayscale();
+
+    // The vector must contain either 3 * 256 values (RGB), or 4 * 256
+    // (RGBA). In the RGB case, an alpha channel will be automatically added.
+    void SetLookupTable(const std::vector<uint8_t>& lut);
+
+    void SetLookupTable(const std::string& lut);
+
+    void SetRange(float minValue,
+                  float maxValue);
+    
+    void FitRange();
+
+    float GetMinValue() const
+    {
+      return minValue_;
+    }
+
+    float GetMaxValue() const
+    {
+      return maxValue_;
+    }
+
+    // This returns a vector of 4 * 256 values between 0 and 255, in RGBA.
+    const std::vector<uint8_t>& GetLookupTable() const
+    {
+      return lut_;
+    }
+
+    virtual ISceneLayer* Clone() const;
+
+    virtual Type GetType() const
+    {
+      return Type_LookupTableTexture;
+    }
+  };
+}
--- a/Framework/Scene2D/OpenGLCompositor.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/OpenGLCompositor.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -26,6 +26,7 @@
 #include "Internals/OpenGLColorTextureRenderer.h"
 #include "Internals/OpenGLFloatTextureRenderer.h"
 #include "Internals/OpenGLInfoPanelRenderer.h"
+#include "Internals/OpenGLLookupTableTextureRenderer.h"
 #include "Internals/OpenGLTextRenderer.h"
 
 namespace OrthancStone
@@ -92,6 +93,10 @@
         return new Internals::OpenGLFloatTextureRenderer
           (context_, floatTextureProgram_, dynamic_cast<const FloatTextureSceneLayer&>(layer));
 
+      case ISceneLayer::Type_LookupTableTexture:
+        return new Internals::OpenGLLookupTableTextureRenderer
+        (context_, colorTextureProgram_, dynamic_cast<const LookupTableTextureSceneLayer&>(layer));
+
       case ISceneLayer::Type_Polyline:
         return new Internals::OpenGLAdvancedPolylineRenderer
           (context_, linesProgram_, dynamic_cast<const PolylineSceneLayer&>(layer));
--- a/Framework/Scene2D/PanSceneTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/PanSceneTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -20,11 +20,11 @@
 
 
 #include "PanSceneTracker.h"
-#include <Framework/Scene2DViewport/ViewportController.h>
+#include "../Scene2DViewport/ViewportController.h"
 
 namespace OrthancStone
 {
-  PanSceneTracker::PanSceneTracker(ViewportControllerWPtr controllerW,
+  PanSceneTracker::PanSceneTracker(boost::weak_ptr<ViewportController> controllerW,
                                    const PointerEvent& event)
     : OneGesturePointerTracker(controllerW)
     , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform())
--- a/Framework/Scene2D/PanSceneTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/PanSceneTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -25,19 +25,17 @@
 
 namespace OrthancStone
 {
-  class ViewportController;
-
   class PanSceneTracker : public OneGesturePointerTracker
   {
   public:
-    PanSceneTracker(ViewportControllerWPtr controllerW,
+    PanSceneTracker(boost::weak_ptr<ViewportController> controllerW,
                     const PointerEvent& event);
 
     virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE;
     virtual void Cancel() ORTHANC_OVERRIDE;
 
   private:
-    ViewportControllerWPtr controllerW_;
+    boost::weak_ptr<ViewportController> controllerW_;
     ScenePoint2D           pivot_;
     AffineTransform2D      originalSceneToCanvas_;
     AffineTransform2D      originalCanvasToScene_;
--- a/Framework/Scene2D/PolylineSceneLayer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/PolylineSceneLayer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -25,6 +25,14 @@
 
 namespace OrthancStone
 {
+  void PolylineSceneLayer::Copy(const PolylineSceneLayer& other)
+  {
+    items_ = other.items_;
+    thickness_ = other.thickness_;
+    revision_ ++;
+  }
+
+  
   ISceneLayer* PolylineSceneLayer::Clone() const
   {
     std::auto_ptr<PolylineSceneLayer> cloned(new PolylineSceneLayer);
@@ -42,52 +50,40 @@
     else
     {
       thickness_ = thickness;
-      BumpRevision();
+      revision_++;
     }
   }
 
 
-  void PolylineSceneLayer::Copy(const PolylineSceneLayer& from)
-  {
-    SetColor(from.GetRed(), from.GetGreen(), from.GetBlue());
-    chains_ = from.chains_;
-    closed_ = from.closed_;
-    thickness_ = from.thickness_;
-    BumpRevision();
-  }
-
-  
-  void PolylineSceneLayer::Reserve(size_t countChains)
-  {
-    chains_.reserve(countChains);
-    closed_.reserve(countChains);
-  }
-
-  
   void PolylineSceneLayer::AddChain(const Chain& chain,
-                                    bool isClosed)
+                                    bool isClosed,
+                                    uint8_t red,
+                                    uint8_t green,
+                                    uint8_t blue)
   {
     if (!chain.empty())
     {
-      chains_.push_back(chain);
-      closed_.push_back(isClosed);
-      BumpRevision();
+      items_.push_back(Item());
+      items_.back().chain_ = chain;
+      items_.back().closed_ = isClosed;
+      items_.back().color_ = Color(red, green, blue);
+
+      revision_++;
     }
   }
 
 
   void PolylineSceneLayer::ClearAllChains()
   {
-    chains_.clear();
-    closed_.clear();
-    BumpRevision();
+    items_.clear();
+    revision_++;
   }
 
-  const PolylineSceneLayer::Chain& PolylineSceneLayer::GetChain(size_t i) const
+  const PolylineSceneLayer::Item& PolylineSceneLayer::GetItem(size_t i) const
   {
-    if (i < chains_.size())
+    if (i < items_.size())
     {
-      return chains_[i];
+      return items_[i];
     }
     else
     {
@@ -96,28 +92,15 @@
   }
 
   
-  bool PolylineSceneLayer::IsClosedChain(size_t i) const
-  {
-    if (i < closed_.size())
-    {
-      return closed_[i];
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
   bool PolylineSceneLayer::GetBoundingBox(Extent2D& target) const
   {
     target.Reset();
 
-    for (size_t i = 0; i < chains_.size(); i++)
+    for (size_t i = 0; i < items_.size(); i++)
     {
-      for (size_t j = 0; j < chains_[i].size(); j++)
+      for (size_t j = 0; j < items_[i].chain_.size(); j++)
       {
-        const ScenePoint2D& p = chains_[i][j];
+        const ScenePoint2D& p = items_[i].chain_[j];
         target.AddPoint(p.GetX(), p.GetY());
       }
     }
--- a/Framework/Scene2D/PolylineSceneLayer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/PolylineSceneLayer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,29 +21,47 @@
 
 #pragma once
 
-#include "ColorSceneLayer.h"
+#include "Color.h"
 #include "ScenePoint2D.h"
+#include "ISceneLayer.h"
 
 #include <vector>
 
 namespace OrthancStone
 {
-  class PolylineSceneLayer : public ColorSceneLayer
+  class PolylineSceneLayer : public ISceneLayer
   {
   public:
     typedef std::vector<ScenePoint2D>  Chain;
 
   private:
-    std::vector<Chain>  chains_;
-    std::vector<bool>   closed_;
-    double              thickness_;
+    struct Item
+    {
+      Chain  chain_;
+      bool   closed_;
+      Color  color_;
+    };
+    
+    std::vector<Item>  items_;
+    double             thickness_;
+    uint64_t           revision_;
+
+    const Item& GetItem(size_t i) const;
 
   public:
     PolylineSceneLayer() :
-      thickness_(1.0)
+      thickness_(1.0),
+      revision_(0)
     {
     }
 
+    void Copy(const PolylineSceneLayer& other);
+
+    virtual uint64_t GetRevision() const
+    {
+      return revision_;
+    }
+
     virtual ISceneLayer* Clone() const;
 
     void SetThickness(double thickness);
@@ -53,23 +71,45 @@
       return thickness_;
     }
 
-    void Copy(const PolylineSceneLayer& from);
-
-    void Reserve(size_t countChains);
+    void Reserve(size_t countChains)
+    {
+      items_.reserve(countChains);
+    }
 
     void AddChain(const Chain& chain,
-                  bool isClosed);
+                  bool isClosed,
+                  uint8_t red,
+                  uint8_t green,
+                  uint8_t blue);
+
+    void AddChain(const Chain& chain,
+                  bool isClosed,
+                  const Color& color)
+    {
+      AddChain(chain, isClosed, color.GetRed(), color.GetGreen(), color.GetBlue());
+    }
 
     void ClearAllChains();
 
     size_t GetChainsCount() const
     {
-      return chains_.size();
+      return items_.size();
+    }
+
+    const Chain& GetChain(size_t i) const
+    {
+      return GetItem(i).chain_;
     }
 
-    const Chain& GetChain(size_t i) const;
+    bool IsClosedChain(size_t i) const
+    {
+      return GetItem(i).closed_;
+    }
 
-    bool IsClosedChain(size_t i) const;
+    const Color& GetColor(size_t i) const
+    {
+      return GetItem(i).color_;
+    }
 
     virtual Type GetType() const
     {
--- a/Framework/Scene2D/RotateSceneTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/RotateSceneTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,11 +19,11 @@
  **/
 
 #include "RotateSceneTracker.h"
-#include <Framework/Scene2DViewport/ViewportController.h>
+#include "../Scene2DViewport/ViewportController.h"
 
 namespace OrthancStone
 {
-  RotateSceneTracker::RotateSceneTracker(ViewportControllerWPtr controllerW,
+  RotateSceneTracker::RotateSceneTracker(boost::weak_ptr<ViewportController> controllerW,
                                          const PointerEvent& event)
     : OneGesturePointerTracker(controllerW)
     , click_(event.GetMainPosition())
--- a/Framework/Scene2D/RotateSceneTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/RotateSceneTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,15 +23,14 @@
 
 #include "../Scene2DViewport/OneGesturePointerTracker.h"
 #include "Internals/FixedPointAligner.h"
+#include <memory>
 
 namespace OrthancStone
 {
-  class ViewportController;
-
   class RotateSceneTracker : public OneGesturePointerTracker
   {
   public:
-    RotateSceneTracker(ViewportControllerWPtr controllerW,
+    RotateSceneTracker(boost::weak_ptr<ViewportController> controllerW,
                        const PointerEvent& event);
 
     virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE;
--- a/Framework/Scene2D/Scene2D.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Scene2D.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -102,8 +102,7 @@
   void Scene2D::SetLayer(int depth,
                          ISceneLayer* layer)  // Takes ownership
   {
-    LOG(INFO) << "SetLayer(" << depth << ", " <<
-      reinterpret_cast<intptr_t>(layer) << ")";
+    LOG(TRACE) << "SetLayer(" << depth << ", " << reinterpret_cast<intptr_t>(layer) << ")";
     std::auto_ptr<Item> item(new Item(layer, layerCounter_++));
 
     if (layer == NULL)
--- a/Framework/Scene2D/Scene2D.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/Scene2D.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,8 +23,8 @@
 
 #include "ISceneLayer.h"
 #include "../Toolbox/AffineTransform2D.h"
-#include <Framework/Messages/IObservable.h>
-#include <Framework/Messages/IMessage.h>
+#include "../Messages/IObservable.h"
+#include "../Messages/IMessage.h"
 
 #include <map>
 
--- a/Framework/Scene2D/TextSceneLayer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/TextSceneLayer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -37,7 +37,7 @@
   ISceneLayer* TextSceneLayer::Clone() const
   {
     std::auto_ptr<TextSceneLayer> cloned(new TextSceneLayer);
-    cloned->SetColor(GetRed(), GetGreen(), GetBlue());
+    cloned->SetColor(GetColor());
     cloned->x_ = x_;
     cloned->y_ = y_;
     cloned->utf8_ = utf8_;
--- a/Framework/Scene2D/ZoomSceneTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -20,11 +20,14 @@
 
 
 #include "ZoomSceneTracker.h"
-#include <Framework/Scene2DViewport/ViewportController.h>
+#include "../Scene2DViewport/ViewportController.h"
+
+using boost::weak_ptr;
+using boost::shared_ptr;
 
 namespace OrthancStone
 {
-  ZoomSceneTracker::ZoomSceneTracker(ViewportControllerWPtr controllerW,
+  ZoomSceneTracker::ZoomSceneTracker(weak_ptr<ViewportController> controllerW,
                                      const PointerEvent& event,
                                      unsigned int canvasHeight)
     : OneGesturePointerTracker(controllerW)
--- a/Framework/Scene2D/ZoomSceneTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -27,12 +27,10 @@
 
 namespace OrthancStone
 {
-  class Scene2D;
-
   class ZoomSceneTracker : public OneGesturePointerTracker
   {
   public:
-    ZoomSceneTracker(ViewportControllerWPtr controllerW,
+    ZoomSceneTracker(boost::weak_ptr<ViewportController> controllerW,
                      const PointerEvent& event,
                      unsigned int canvasHeight);
 
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -20,15 +20,33 @@
 
 #include "AngleMeasureTool.h"
 #include "MeasureToolsToolbox.h"
+#include "LayerHolder.h"
 
 #include <Core/Logging.h>
 
 #include <boost/math/constants/constants.hpp>
+#include <boost/make_shared.hpp>
 
-extern void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value);
+//// <HACK>
+//// REMOVE THIS
+//#ifndef NDEBUG
+//extern void 
+//TrackerSample_SetInfoDisplayMessage(std::string key, std::string value);
+//#endif
+//// </HACK>
 
 namespace OrthancStone
 {
+  // the params in the LayerHolder ctor specify the number of polyline and text
+  // layers
+  AngleMeasureTool::AngleMeasureTool(
+    MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
+    : MeasureTool(broker, controllerW)
+    , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5))
+  {
+
+  }
+
   AngleMeasureTool::~AngleMeasureTool()
   {
     // this measuring tool is a RABI for the corresponding visual layers
@@ -39,12 +57,9 @@
 
   void AngleMeasureTool::RemoveFromScene()
   {
-    if (layersCreated)
+    if (layerHolder_->AreLayersCreated() && IsSceneAlive())
     {
-      assert(GetScene()->HasLayer(polylineZIndex_));
-      assert(GetScene()->HasLayer(textBaseZIndex_));
-      GetScene()->DeleteLayer(polylineZIndex_);
-      GetScene()->DeleteLayer(textBaseZIndex_);
+      layerHolder_->DeleteLayers();
     }
   }
 
@@ -60,84 +75,33 @@
     RefreshScene();
   }
 
+
+  bool AngleMeasureTool::HitTest(ScenePoint2D p) const
+  {
+    throw std::logic_error("The method or operation is not implemented.");
+  }
+
   void AngleMeasureTool::SetCenter(ScenePoint2D pt)
   {
     center_ = pt;
     RefreshScene();
   }
   
-  PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer()
-  {
-    assert(GetScene()->HasLayer(polylineZIndex_));
-    ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_));
-    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
   void AngleMeasureTool::RefreshScene()
   {
     if (IsSceneAlive())
     {
-
+      boost::shared_ptr<ViewportController> controller = GetController();
       if (IsEnabled())
       {
-        // get the scaling factor 
-        const double pixelToScene =
-          GetScene()->GetCanvasToSceneTransform().ComputeZoom();
-
-        if (!layersCreated)
-        {
-          // Create the layers if need be
-
-          assert(textBaseZIndex_ == -1);
-          {
-            polylineZIndex_ = GetScene()->GetMaxDepth() + 100;
-            //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
-            std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-            GetScene()->SetLayer(polylineZIndex_, layer.release());
+        layerHolder_->CreateLayersIfNeeded();
 
-          }
-          {
-            textBaseZIndex_ = GetScene()->GetMaxDepth() + 100;
-            // create the four text background layers
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_, layer.release());
-            }
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 1, layer.release());
-            }
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 2, layer.release());
-            }
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 3, layer.release());
-            }
+        {
+          // Fill the polyline layer with the measurement lines
+          PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0);
+          polylineLayer->ClearAllChains();
 
-            // and the text layer itself
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 4, layer.release());
-            }
-
-          }
-          layersCreated = true;
-        }
-        else
-        {
-          assert(GetScene()->HasLayer(polylineZIndex_));
-          assert(GetScene()->HasLayer(textBaseZIndex_));
-        }
-        {
-          // Fill the polyline layer with the measurement line
-
-          PolylineSceneLayer* polylineLayer = GetPolylineLayer();
-          polylineLayer->ClearAllChains();
-          polylineLayer->SetColor(0, 183, 17);
+          const Color color(0, 183, 17);
 
           // sides
           {
@@ -145,41 +109,41 @@
               PolylineSceneLayer::Chain chain;
               chain.push_back(side1End_);
               chain.push_back(center_);
-              polylineLayer->AddChain(chain, false);
+              polylineLayer->AddChain(chain, false, color);
             }
             {
               PolylineSceneLayer::Chain chain;
               chain.push_back(side2End_);
               chain.push_back(center_);
-              polylineLayer->AddChain(chain, false);
+              polylineLayer->AddChain(chain, false, color);
             }
           }
 
-          // handles
+          // Create the handles
           {
-            //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
-
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), side1End_, 10.0 * pixelToScene); //TODO: take DPI into account
-              polylineLayer->AddChain(chain, true);
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), side1End_, 
+                GetController()->GetHandleSideLengthS());
+              polylineLayer->AddChain(chain, true, color);
             }
-
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), side2End_, 10.0 * pixelToScene); //TODO: take DPI into account
-              polylineLayer->AddChain(chain, true);
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), side2End_, 
+                GetController()->GetHandleSideLengthS());
+              polylineLayer->AddChain(chain, true, color);
             }
           }
 
-          // arc
+          // Create the arc
           {
             PolylineSceneLayer::Chain chain;
 
-            const double ARC_RADIUS_CANVAS_COORD = 30.0;
-            AddShortestArc(chain, *GetScene(), side1End_, center_, side2End_,
-              ARC_RADIUS_CANVAS_COORD * pixelToScene);
-            polylineLayer->AddChain(chain, false);
+            AddShortestArc(chain, side1End_, center_, side2End_,
+                           controller->GetAngleToolArcRadiusS());
+            polylineLayer->AddChain(chain, false, color);
           }
         }
         {
@@ -189,25 +153,18 @@
             side1End_.GetY() - center_.GetY(),
             side1End_.GetX() - center_.GetX());
 
-
           double p2cAngle = atan2(
             side2End_.GetY() - center_.GetY(),
             side2End_.GetX() - center_.GetX());
 
           double delta = NormalizeAngle(p2cAngle - p1cAngle);
-
-
           double theta = p1cAngle + delta / 2;
 
-
-          const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90;
-
-          double offsetX = TEXT_CENTER_DISTANCE_CANVAS_COORD * cos(theta);
+          double ox = controller->GetAngleTopTextLabelDistanceS() * cos(theta);
+          double oy = controller->GetAngleTopTextLabelDistanceS() * sin(theta);
 
-          double offsetY = TEXT_CENTER_DISTANCE_CANVAS_COORD * sin(theta);
-
-          double pointX = center_.GetX() + offsetX * pixelToScene;
-          double pointY = center_.GetY() + offsetY * pixelToScene;
+          double pointX = center_.GetX() + ox;
+          double pointY = center_.GetY() + oy;
 
           char buf[64];
           double angleDeg = RadiansToDegrees(delta);
@@ -216,8 +173,9 @@
           sprintf(buf, "%0.02f\xc2\xb0", angleDeg);
 
           SetTextLayerOutlineProperties(
-            *GetScene(), textBaseZIndex_, buf, ScenePoint2D(pointX, pointY));
+            GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY));
 
+#if 0
           // TODO:make it togglable
           bool enableInfoDisplay = false;
           if (enableInfoDisplay)
@@ -252,11 +210,11 @@
             TrackerSample_SetInfoDisplayMessage("p2cAngle (deg)",
               boost::lexical_cast<std::string>(RadiansToDegrees(p2cAngle)));
 
-            TrackerSample_SetInfoDisplayMessage("offsetX (pix)",
-              boost::lexical_cast<std::string>(offsetX));
+            TrackerSample_SetInfoDisplayMessage("ox (scene)",
+              boost::lexical_cast<std::string>(ox));
 
-            TrackerSample_SetInfoDisplayMessage("offsetY (pix)",
-              boost::lexical_cast<std::string>(offsetY));
+            TrackerSample_SetInfoDisplayMessage("offsetY (scene)",
+              boost::lexical_cast<std::string>(oy));
 
             TrackerSample_SetInfoDisplayMessage("pointX",
               boost::lexical_cast<std::string>(pointX));
@@ -267,18 +225,12 @@
             TrackerSample_SetInfoDisplayMessage("angleDeg",
               boost::lexical_cast<std::string>(angleDeg));
           }
-
-
-
+#endif
         }
       }
       else
       {
-        if (layersCreated)
-        {
-          RemoveFromScene();
-          layersCreated = false;
-        }
+        RemoveFromScene();
       }
     }
   }
--- a/Framework/Scene2DViewport/AngleMeasureTool.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Mon Jun 24 14:35:00 2019 +0200
@@ -20,12 +20,13 @@
 
 #pragma once
 
-#include "MeasureTools.h"
+#include "MeasureTool.h"
 
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
+#include "../Scene2DViewport/LayerHolder.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/ScenePoint2D.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/TextSceneLayer.h"
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
@@ -38,14 +39,7 @@
   class AngleMeasureTool : public MeasureTool
   {
   public:
-    AngleMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
-      : MeasureTool(broker, controllerW)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textBaseZIndex_(-1)
-    {
-
-    }
+    AngleMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
 
     ~AngleMeasureTool();
 
@@ -53,24 +47,19 @@
     void SetCenter(ScenePoint2D start);
     void SetSide2End(ScenePoint2D start);
 
+
+    virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+
   private:
-    PolylineSceneLayer* GetPolylineLayer();
-    
-    // 0 --> 3 are for the text background (outline)
-    // 4 is for the actual text
-    TextSceneLayer*     GetTextLayer(int index);
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
 
   private:
-    ScenePoint2D side1End_;
-    ScenePoint2D side2End_;
-    ScenePoint2D center_;
-    bool         layersCreated;
-    int          polylineZIndex_;
-    int          textBaseZIndex_;
+    ScenePoint2D    side1End_;
+    ScenePoint2D    side2End_;
+    ScenePoint2D    center_;
+    boost::shared_ptr<LayerHolder>  layerHolder_;
   };
-
 }
 
 
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -21,13 +21,11 @@
 #include "CreateAngleMeasureTracker.h"
 #include <Core/OrthancException.h>
 
-using namespace Orthanc;
-
 namespace OrthancStone
 {
   CreateAngleMeasureTracker::CreateAngleMeasureTracker(
     MessageBroker&                  broker,
-    ViewportControllerWPtr          controllerW,
+    boost::weak_ptr<ViewportController>          controllerW,
     const PointerEvent&             e)
     : CreateMeasureTracker(controllerW)
     , state_(CreatingSide1)
@@ -49,7 +47,7 @@
 
     if (!alive_)
     {
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Internal error: wrong state in CreateAngleMeasureTracker::"
         "PointerMove: active_ == false");
     }
@@ -66,7 +64,7 @@
       GetCommand()->SetSide2End(scenePos);
       break;
     default:
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Wrong state in CreateAngleMeasureTracker::"
         "PointerMove: state_ invalid");
     }
@@ -88,12 +86,12 @@
       state_ = CreatingSide2;
       break;
     case CreatingSide2:
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Wrong state in CreateAngleMeasureTracker::"
         "PointerUp: state_ == CreatingSide2 ; this should not happen");
       break;
     default:
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Wrong state in CreateAngleMeasureTracker::"
         "PointerMove: state_ invalid");
     }
@@ -104,7 +102,7 @@
     switch (state_)
     {
     case CreatingSide1:
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Wrong state in CreateAngleMeasureTracker::"
         "PointerDown: state_ == CreatingSide1 ; this should not happen");
       break;
@@ -113,13 +111,13 @@
       alive_ = false;
       break;
     default:
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Wrong state in CreateAngleMeasureTracker::"
         "PointerMove: state_ invalid");
     }
   }
 
-  CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand()
+  boost::shared_ptr<CreateAngleMeasureCommand> CreateAngleMeasureTracker::GetCommand()
   {
     return boost::dynamic_pointer_cast<CreateAngleMeasureCommand>(command_);
   }
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -39,7 +39,7 @@
     */
     CreateAngleMeasureTracker(
       MessageBroker&                  broker,
-      ViewportControllerWPtr          controllerW,
+      boost::weak_ptr<ViewportController>          controllerW,
       const PointerEvent&             e);
 
     ~CreateAngleMeasureTracker();
@@ -49,7 +49,7 @@
     virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
 
   private:
-    CreateAngleMeasureCommandPtr GetCommand();
+    boost::shared_ptr<CreateAngleMeasureCommand> GetCommand();
 
     enum State
     {
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -21,13 +21,11 @@
 #include "CreateLineMeasureTracker.h"
 #include <Core/OrthancException.h>
 
-using namespace Orthanc;
-
 namespace OrthancStone
 {
   CreateLineMeasureTracker::CreateLineMeasureTracker(
     MessageBroker&                  broker,
-    ViewportControllerWPtr          controllerW,
+    boost::weak_ptr<ViewportController>          controllerW,
     const PointerEvent&             e)
     : CreateMeasureTracker(controllerW)
   {
@@ -49,7 +47,7 @@
     
     if (!alive_)
     {
-      throw OrthancException(ErrorCode_InternalError,
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
         "Internal error: wrong state in CreateLineMeasureTracker::"
         "PointerMove: active_ == false");
     }
@@ -82,7 +80,7 @@
       "are ignored when the line measure creation tracker is active";
   }
 
-  CreateLineMeasureCommandPtr CreateLineMeasureTracker::GetCommand()
+  boost::shared_ptr<CreateLineMeasureCommand> CreateLineMeasureTracker::GetCommand()
   {
     return boost::dynamic_pointer_cast<CreateLineMeasureCommand>(command_);
   }
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -36,7 +36,7 @@
     */
     CreateLineMeasureTracker(
       MessageBroker&                  broker,
-      ViewportControllerWPtr          controllerW,
+      boost::weak_ptr<ViewportController>          controllerW,
       const PointerEvent&             e);
 
     ~CreateLineMeasureTracker();
@@ -46,6 +46,6 @@
     virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
 
   private:
-    CreateLineMeasureCommandPtr GetCommand();
+    boost::shared_ptr<CreateLineMeasureCommand> GetCommand();
   };
 }
--- a/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,7 +19,7 @@
  **/
 
 #include "IFlexiblePointerTracker.h"
-#include <Framework/Scene2D/IPointerTracker.h>
+#include "../Scene2D/IPointerTracker.h"
 
 
 namespace OrthancStone
@@ -30,7 +30,7 @@
     class SimpleTrackerAdapter : public IFlexiblePointerTracker
     {
     public:
-      SimpleTrackerAdapter(PointerTrackerPtr wrappedTracker)
+      SimpleTrackerAdapter(boost::shared_ptr<IPointerTracker> wrappedTracker)
         : wrappedTracker_(wrappedTracker)
         , active_(true)
       {
@@ -66,14 +66,14 @@
       }
 
     private:
-      PointerTrackerPtr wrappedTracker_;
+      boost::shared_ptr<IPointerTracker> wrappedTracker_;
       bool active_;
     };
   }
 
-  FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr t)
+  boost::shared_ptr<IFlexiblePointerTracker> CreateSimpleTrackerAdapter(boost::shared_ptr<IPointerTracker> t)
   {
-    return FlexiblePointerTrackerPtr(new SimpleTrackerAdapter(t));
+    return boost::shared_ptr<IFlexiblePointerTracker>(new SimpleTrackerAdapter(t));
   }
 #endif
 }
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-namespace OrthancStone
-{
-}
--- a/Framework/Scene2DViewport/EditCircleMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Framework/Scene2DViewport/EditCircleMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-namespace OrthancStone
-{
-}
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-namespace OrthancStone
-{
-}
--- a/Framework/Scene2DViewport/IFlexiblePointerTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/IFlexiblePointerTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,9 +21,10 @@
 
 #pragma once
 
-#include "PointerTypes.h"
+#include "PredeclaredTypes.h"
 
-#include <Framework/Scene2D/PointerEvent.h>
+#include "../Scene2D/PointerEvent.h"
+
 
 namespace OrthancStone
 {
@@ -82,6 +83,6 @@
   This factory adopts the supplied simple tracker and creates a flexible 
   tracker wrapper around it.
   */
-  FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr);
+  boost::shared_ptr<IFlexiblePointerTracker> CreateSimpleTrackerAdapter(boost::shared_ptr<IPointerTracker>);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/LayerHolder.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,139 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "LayerHolder.h"
+#include "../Scene2D/TextSceneLayer.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2DViewport/ViewportController.h"
+#include "../StoneException.h"
+
+namespace OrthancStone
+{
+  LayerHolder::LayerHolder(
+    boost::weak_ptr<ViewportController> controllerW,
+    int                    polylineLayerCount,
+    int                    textLayerCount)
+    : textLayerCount_(textLayerCount)
+    , polylineLayerCount_(polylineLayerCount)
+    , controllerW_(controllerW)
+    , baseLayerIndex_(-1)
+  {
+
+  }
+
+  void LayerHolder::CreateLayers()
+  {
+    assert(baseLayerIndex_ == -1);
+
+    baseLayerIndex_ = GetScene()->GetMaxDepth() + 100;
+
+    for (int i = 0; i < polylineLayerCount_; ++i)
+    {
+      std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
+      GetScene()->SetLayer(baseLayerIndex_ + i, layer.release());
+    }
+
+    for (int i = 0; i < textLayerCount_; ++i)
+    {
+      std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+      GetScene()->SetLayer(
+        baseLayerIndex_ + polylineLayerCount_ + i,
+        layer.release());
+    }
+
+  }
+
+  void LayerHolder::CreateLayersIfNeeded()
+  {
+    if (baseLayerIndex_ == -1)
+      CreateLayers();
+  }
+
+  bool LayerHolder::AreLayersCreated() const
+  {
+    return (baseLayerIndex_ != -1);
+  }
+
+  boost::shared_ptr<Scene2D> LayerHolder::GetScene()
+  {
+    boost::shared_ptr<ViewportController> controller = controllerW_.lock();
+    ORTHANC_ASSERT(controller.get() != 0, "Zombie attack!");
+    return controller->GetScene();
+  }
+
+  void LayerHolder::DeleteLayers()
+  {
+    for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i)
+    {
+      ORTHANC_ASSERT(GetScene()->HasLayer(baseLayerIndex_ + i), "No layer");
+      GetScene()->DeleteLayer(baseLayerIndex_ + i);
+    }
+    baseLayerIndex_ = -1;
+  }
+
+  PolylineSceneLayer* LayerHolder::GetPolylineLayer(int index /*= 0*/)
+  {
+    using namespace Orthanc;
+    ORTHANC_ASSERT(baseLayerIndex_ != -1);
+    ORTHANC_ASSERT(GetScene()->HasLayer(GetPolylineLayerIndex(index)));
+    ISceneLayer* layer =
+      &(GetScene()->GetLayer(GetPolylineLayerIndex(index)));
+
+    PolylineSceneLayer* concreteLayer =
+      dynamic_cast<PolylineSceneLayer*>(layer);
+
+    ORTHANC_ASSERT(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  TextSceneLayer* LayerHolder::GetTextLayer(int index /*= 0*/)
+  {
+    using namespace Orthanc;
+    ORTHANC_ASSERT(baseLayerIndex_ != -1);
+    ORTHANC_ASSERT(GetScene()->HasLayer(GetTextLayerIndex(index)));
+    ISceneLayer* layer =
+      &(GetScene()->GetLayer(GetTextLayerIndex(index)));
+
+    TextSceneLayer* concreteLayer =
+      dynamic_cast<TextSceneLayer*>(layer);
+
+    ORTHANC_ASSERT(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  int LayerHolder::GetPolylineLayerIndex(int index /*= 0*/)
+  {
+    using namespace Orthanc;
+    ORTHANC_ASSERT(index < polylineLayerCount_);
+    return baseLayerIndex_ + index;
+  }
+
+
+  int LayerHolder::GetTextLayerIndex(int index /*= 0*/)
+  {
+    using namespace Orthanc;
+    ORTHANC_ASSERT(index < textLayerCount_);
+
+    // the text layers are placed right after the polyline layers
+    // this means they are drawn ON TOP
+    return baseLayerIndex_ + polylineLayerCount_ + index;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/LayerHolder.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,100 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "PredeclaredTypes.h"
+
+#include "boost/noncopyable.hpp"
+#include "boost/weak_ptr.hpp"
+#include "boost/shared_ptr.hpp"
+
+namespace OrthancStone
+{
+  class PolylineSceneLayer;
+  class TextSceneLayer;
+
+  /**
+  This class holds the indices of a set a layer and supplies
+  getters to the concrete layer objects. Sounds very ad hoc, and it is.
+  */
+  class LayerHolder : public boost::noncopyable
+  {
+  public:
+    /**
+    This ctor merely stores the scene and layer counts. No layer creation
+    performed at this time
+    */
+    LayerHolder(
+      boost::weak_ptr<ViewportController> controllerW,
+      int polylineLayerCount, int textLayerCount);
+
+    /**
+    This actually creates the layers
+    */
+    void CreateLayers();
+
+    /**
+    This creates the layers if they are not created yet. Can be useful in 
+    some scenarios
+    */
+    void CreateLayersIfNeeded();
+
+    /**
+    Whether the various text and polylines layers have all been created or 
+    none at all
+    */
+    bool AreLayersCreated() const;
+
+    /**
+    This removes the layers from the scene
+    */
+    void DeleteLayers();
+
+    /**
+    Please note that the returned pointer belongs to the scene.Don't you dare
+    storing or deleting it, you fool!
+
+    This throws if the index is not valid or if the layers are not created or
+    have been deleted
+    */
+    PolylineSceneLayer* GetPolylineLayer(int index = 0);
+
+    /**
+    Please note that the returned pointer belongs to the scene. Don't you dare
+    storing or deleting it, you fool!
+
+    This throws if the index is not valid or if the layers are not created or
+    have been deleted
+    */
+    TextSceneLayer* GetTextLayer(int index = 0);
+
+  private:
+    int GetPolylineLayerIndex(int index = 0);
+    int GetTextLayerIndex(int index = 0);
+    boost::shared_ptr<Scene2D> GetScene();
+
+    int textLayerCount_;
+    int polylineLayerCount_;
+    boost::weak_ptr<ViewportController> controllerW_;
+    int baseLayerIndex_;
+  };
+}
+
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -20,12 +20,23 @@
 
 #include "LineMeasureTool.h"
 #include "MeasureToolsToolbox.h"
+#include "LayerHolder.h"
 
 #include <Core/Logging.h>
 
+#include <boost/make_shared.hpp>
 
 namespace OrthancStone
 {
+
+  LineMeasureTool::LineMeasureTool(
+    MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW)
+    : MeasureTool(broker, controllerW)
+    , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5))
+  {
+
+  }
+
   LineMeasureTool::~LineMeasureTool()
   {
     // this measuring tool is a RABI for the corresponding visual layers
@@ -36,16 +47,12 @@
 
   void LineMeasureTool::RemoveFromScene()
   {
-    if (layersCreated)
+    if (layerHolder_->AreLayersCreated() && IsSceneAlive())
     {
-      assert(GetScene()->HasLayer(polylineZIndex_));
-      assert(GetScene()->HasLayer(textZIndex_));
-      GetScene()->DeleteLayer(polylineZIndex_);
-      GetScene()->DeleteLayer(textZIndex_);
+      layerHolder_->DeleteLayers();
     }
   }
-
-
+  
   void LineMeasureTool::SetStart(ScenePoint2D start)
   {
     start_ = start;
@@ -65,22 +72,26 @@
     RefreshScene();
   }
 
-  PolylineSceneLayer* LineMeasureTool::GetPolylineLayer()
+  
+
+  bool LineMeasureTool::HitTest(ScenePoint2D p) const
   {
-    assert(GetScene()->HasLayer(polylineZIndex_));
-    ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_));
-    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
+    const double pixelToScene =
+      GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+
+    // the hit test will return true if the supplied point (in scene coords)
+    // is close to the handle or to the line.
 
-  TextSceneLayer* LineMeasureTool::GetTextLayer()
-  {
-    assert(GetScene()->HasLayer(textZIndex_));
-    ISceneLayer* layer = &(GetScene()->GetLayer(textZIndex_));
-    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
+    // since the handle is small, a nice approximation is to defined this
+    // as a threshold on the distance between the point and the handle center.
+
+    // this threshold is defined as a constant value in CANVAS units.
+
+
+    // line equation from two points (non-normalized)
+    // (y0-y1)*x + (x1-x0)*xy + (x0*y1 - x1*y0) = 0
+    // 
+    return false;
   }
 
   void LineMeasureTool::RefreshScene()
@@ -89,113 +100,71 @@
     {
       if (IsEnabled())
       {
-        if (!layersCreated)
-        {
-          // Create the layers if need be
+        layerHolder_->CreateLayersIfNeeded();
 
-          assert(textZIndex_ == -1);
-          {
-            polylineZIndex_ = GetScene()->GetMaxDepth() + 100;
-            //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
-            std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-            GetScene()->SetLayer(polylineZIndex_, layer.release());
-          }
-          {
-            textZIndex_ = GetScene()->GetMaxDepth() + 100;
-            //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
-            std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-            GetScene()->SetLayer(textZIndex_, layer.release());
-          }
-          layersCreated = true;
-        }
-        else
-        {
-          assert(GetScene()->HasLayer(polylineZIndex_));
-          assert(GetScene()->HasLayer(textZIndex_));
-        }
         {
           // Fill the polyline layer with the measurement line
 
-          PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+          PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0);
           polylineLayer->ClearAllChains();
-          polylineLayer->SetColor(0, 223, 21);
+
+          const Color color(TOOL_LINES_COLOR_RED, 
+                            TOOL_LINES_COLOR_GREEN, 
+                            TOOL_LINES_COLOR_BLUE);
 
           {
             PolylineSceneLayer::Chain chain;
             chain.push_back(start_);
             chain.push_back(end_);
-            polylineLayer->AddChain(chain, false);
+            polylineLayer->AddChain(chain, false, color);
           }
 
           // handles
           {
-            //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
-
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), start_, 10.0); //TODO: take DPI into account
-              polylineLayer->AddChain(chain, true);
+              
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), start_, 
+                GetController()->GetHandleSideLengthS());
+              
+              polylineLayer->AddChain(chain, true, color);
             }
 
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), end_, 10.0); //TODO: take DPI into account
-              polylineLayer->AddChain(chain, true);
+              
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), end_, 
+                GetController()->GetHandleSideLengthS());
+              
+              polylineLayer->AddChain(chain, true, color);
             }
-
-            //ScenePoint2D startC = start_.Apply(GetScene()->GetSceneToCanvasTransform());
-            //double squareSize = 10.0; 
-            //double startHandleLX = startC.GetX() - squareSize/2;
-            //double startHandleTY = startC.GetY() - squareSize / 2;
-            //double startHandleRX = startC.GetX() + squareSize / 2;
-            //double startHandleBY = startC.GetY() + squareSize / 2;
-            //ScenePoint2D startLTC(startHandleLX, startHandleTY);
-            //ScenePoint2D startRTC(startHandleRX, startHandleTY);
-            //ScenePoint2D startRBC(startHandleRX, startHandleBY);
-            //ScenePoint2D startLBC(startHandleLX, startHandleBY);
-
-            //ScenePoint2D startLT = startLTC.Apply(GetScene()->GetCanvasToSceneTransform());
-            //ScenePoint2D startRT = startRTC.Apply(GetScene()->GetCanvasToSceneTransform());
-            //ScenePoint2D startRB = startRBC.Apply(GetScene()->GetCanvasToSceneTransform());
-            //ScenePoint2D startLB = startLBC.Apply(GetScene()->GetCanvasToSceneTransform());
-
-            //PolylineSceneLayer::Chain chain;
-            //chain.push_back(startLT);
-            //chain.push_back(startRT);
-            //chain.push_back(startRB);
-            //chain.push_back(startLB);
-            //polylineLayer->AddChain(chain, true);
           }
 
         }
         {
-          // Set the text layer proporeties
-
-          TextSceneLayer* textLayer = GetTextLayer();
+          // Set the text layer propreties
           double deltaX = end_.GetX() - start_.GetX();
           double deltaY = end_.GetY() - start_.GetY();
           double squareDist = deltaX * deltaX + deltaY * deltaY;
           double dist = sqrt(squareDist);
           char buf[64];
           sprintf(buf, "%0.02f units", dist);
-          textLayer->SetText(buf);
-          textLayer->SetColor(0, 223, 21);
 
           // TODO: for now we simply position the text overlay at the middle
           // of the measuring segment
           double midX = 0.5 * (end_.GetX() + start_.GetX());
           double midY = 0.5 * (end_.GetY() + start_.GetY());
-          textLayer->SetPosition(midX, midY);
+
+          SetTextLayerOutlineProperties(
+            GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY));
         }
       }
       else
       {
-        if (layersCreated)
-        {
-          RemoveFromScene();
-          layersCreated = false;
-        }
+        RemoveFromScene();
       }
     }
   }
-}
\ No newline at end of file
+}
--- a/Framework/Scene2DViewport/LineMeasureTool.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Mon Jun 24 14:35:00 2019 +0200
@@ -20,12 +20,11 @@
 
 #pragma once
 
-#include "MeasureTools.h"
-
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/ScenePoint2D.h"
+#include "../Scene2D/TextSceneLayer.h"
+#include "MeasureTool.h"
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
@@ -38,14 +37,7 @@
   class LineMeasureTool : public MeasureTool
   {
   public:
-    LineMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
-      : MeasureTool(broker, controllerW)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textZIndex_(-1)
-    {
-
-    }
+    LineMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
 
     ~LineMeasureTool();
 
@@ -53,18 +45,18 @@
     void SetEnd(ScenePoint2D end);
     void Set(ScenePoint2D start, ScenePoint2D end);
 
+
+    virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE;
+
   private:
-    PolylineSceneLayer* GetPolylineLayer();
-    TextSceneLayer*     GetTextLayer();
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
 
   private:
-    ScenePoint2D start_;
-    ScenePoint2D end_;
-    bool         layersCreated;
-    int          polylineZIndex_;
-    int          textZIndex_;
+    ScenePoint2D   start_;
+    ScenePoint2D   end_;
+    boost::shared_ptr<LayerHolder> layerHolder_;
+    int            baseLayerIndex_;
   };
 
 }
--- a/Framework/Scene2DViewport/MeasureCommands.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -28,15 +28,17 @@
   void CreateMeasureCommand::Undo()
   {
     // simply disable the measure tool upon undo
+    GetMeasureTool()->Disable();
     GetController()->RemoveMeasureTool(GetMeasureTool());
   }
 
   void CreateMeasureCommand::Redo()
   {
+    GetMeasureTool()->Enable();
     GetController()->AddMeasureTool(GetMeasureTool());
   }
 
-  CreateMeasureCommand::CreateMeasureCommand(ViewportControllerWPtr controllerW)
+  CreateMeasureCommand::CreateMeasureCommand(boost::weak_ptr<ViewportController> controllerW)
     : TrackerCommand(controllerW)
   {
 
@@ -50,7 +52,7 @@
 
   CreateLineMeasureCommand::CreateLineMeasureCommand(
     MessageBroker&         broker, 
-    ViewportControllerWPtr controllerW,
+    boost::weak_ptr<ViewportController> controllerW,
     ScenePoint2D           point)
     : CreateMeasureCommand(controllerW)
     , measureTool_(
@@ -67,7 +69,7 @@
 
   CreateAngleMeasureCommand::CreateAngleMeasureCommand(
     MessageBroker&         broker, 
-    ViewportControllerWPtr controllerW,
+    boost::weak_ptr<ViewportController> controllerW,
     ScenePoint2D           point)
     : CreateMeasureCommand(controllerW)
     , measureTool_(
@@ -91,9 +93,9 @@
     measureTool_->SetSide2End(scenePos);
   }
 
-  ViewportControllerPtr TrackerCommand::GetController()
+  boost::shared_ptr<ViewportController> TrackerCommand::GetController()
   {
-    ViewportControllerPtr controller = controllerW_.lock();
+    boost::shared_ptr<ViewportController> controller = controllerW_.lock();
     assert(controller); // accessing dead object?
     return controller;
   }
--- a/Framework/Scene2DViewport/MeasureCommands.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.h	Mon Jun 24 14:35:00 2019 +0200
@@ -19,22 +19,23 @@
  **/
 #pragma once
 
-#include <Framework/Scene2D/Scene2D.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
+#include "../Scene2D/Scene2D.h"
 
 // to be moved into Stone
-#include "PointerTypes.h"
-#include "MeasureTools.h"
+#include "PredeclaredTypes.h"
+#include "MeasureTool.h"
 #include "LineMeasureTool.h"
 #include "AngleMeasureTool.h"
 
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
 namespace OrthancStone
 {
   class TrackerCommand : public boost::noncopyable
   {
   public:
-    TrackerCommand(ViewportControllerWPtr controllerW) 
+    TrackerCommand(boost::weak_ptr<ViewportController> controllerW) 
       : controllerW_(controllerW)
     {
 
@@ -43,20 +44,20 @@
     virtual void Redo() = 0;
 
   protected:
-    ViewportControllerPtr  GetController();
-    ViewportControllerWPtr controllerW_;
+    boost::shared_ptr<ViewportController>  GetController();
+    boost::weak_ptr<ViewportController> controllerW_;
   };
 
   class CreateMeasureCommand : public TrackerCommand
   {
   public:
-    CreateMeasureCommand(ViewportControllerWPtr controllerW);
+    CreateMeasureCommand(boost::weak_ptr<ViewportController> controllerW);
     ~CreateMeasureCommand();
     virtual void Undo() ORTHANC_OVERRIDE;
     virtual void Redo() ORTHANC_OVERRIDE;
   private:
     /** Must be implemented by the subclasses that create the actual tool */
-    virtual MeasureToolPtr GetMeasureTool() = 0;
+    virtual boost::shared_ptr<MeasureTool> GetMeasureTool() = 0;
   };
   
   class CreateLineMeasureCommand : public CreateMeasureCommand
@@ -64,18 +65,18 @@
   public:
     CreateLineMeasureCommand(
       MessageBroker&         broker, 
-      ViewportControllerWPtr controllerW,
+      boost::weak_ptr<ViewportController> controllerW,
       ScenePoint2D           point);
     
     // the starting position is set in the ctor
     void SetEnd(ScenePoint2D scenePos);
 
   private:
-    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
+    virtual boost::shared_ptr<MeasureTool> GetMeasureTool() ORTHANC_OVERRIDE
     {
       return measureTool_;
     }
-    LineMeasureToolPtr measureTool_;
+    boost::shared_ptr<LineMeasureTool> measureTool_;
   };
 
 
@@ -85,7 +86,7 @@
     /** Ctor sets end of side 1*/
     CreateAngleMeasureCommand(
       MessageBroker&         broker, 
-      ViewportControllerWPtr controllerW,
+      boost::weak_ptr<ViewportController> controllerW,
       ScenePoint2D           point);
 
     /** This method sets center*/
@@ -95,11 +96,11 @@
     void SetSide2End(ScenePoint2D scenePos);
 
   private:
-    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
+    virtual boost::shared_ptr<MeasureTool> GetMeasureTool() ORTHANC_OVERRIDE
     {
       return measureTool_;
     }
-    AngleMeasureToolPtr measureTool_;
+    boost::shared_ptr<AngleMeasureTool> measureTool_;
   };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTool.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,115 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "MeasureTool.h"
+
+#include <Core/Logging.h>
+#include <Core/Enumerations.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/constants/constants.hpp>
+
+namespace OrthancStone
+{
+  MeasureTool::~MeasureTool()
+  {
+
+  }
+
+  void MeasureTool::Enable()
+  {
+    enabled_ = true;
+    RefreshScene();
+  }
+
+  void MeasureTool::Disable()
+  {
+    enabled_ = false;
+    RefreshScene();
+  }
+
+  bool MeasureTool::IsEnabled() const
+  {
+    return enabled_;
+  }
+
+
+  boost::shared_ptr<const ViewportController> MeasureTool::GetController() const
+  {
+    boost::shared_ptr<const ViewportController> controller = controllerW_.lock();
+    if (!controller)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+        "Using dead ViewportController object!");
+    return controller;
+  }
+
+  boost::shared_ptr<ViewportController> MeasureTool::GetController()
+  {
+#if 1
+    return boost::const_pointer_cast<ViewportController>
+      (const_cast<const MeasureTool*>(this)->GetController());
+    //return boost::const_<boost::shared_ptr<ViewportController>>
+    //  (const_cast<const MeasureTool*>(this)->GetController());
+#else
+    boost::shared_ptr<ViewportController> controller = controllerW_.lock();
+    if (!controller)
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, 
+        "Using dead ViewportController object!");
+    return controller;
+#endif
+  }
+
+  boost::shared_ptr<Scene2D> MeasureTool::GetScene()
+  {
+    return GetController()->GetScene();
+  }
+
+  boost::shared_ptr<const Scene2D> MeasureTool::GetScene() const
+  {
+    return GetController()->GetScene();
+  }
+
+  MeasureTool::MeasureTool(MessageBroker& broker,
+    boost::weak_ptr<ViewportController> controllerW)
+    : IObserver(broker)
+    , controllerW_(controllerW)
+    , enabled_(true)
+  {
+    GetController()->RegisterObserverCallback(
+      new Callable<MeasureTool, ViewportController::SceneTransformChanged>
+      (*this, &MeasureTool::OnSceneTransformChanged));
+  }
+
+
+  bool MeasureTool::IsSceneAlive() const
+  {
+    boost::shared_ptr<ViewportController> controller = controllerW_.lock();
+    return (controller.get() != NULL);
+  }
+
+  void MeasureTool::OnSceneTransformChanged(
+    const ViewportController::SceneTransformChanged& message)
+  {
+    RefreshScene();
+  }
+
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTool.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,110 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/ScenePoint2D.h"
+#include "../Scene2D/TextSceneLayer.h"
+#include "../Scene2DViewport/PredeclaredTypes.h"
+#include "../Scene2DViewport/ViewportController.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+#include <cmath>
+
+namespace OrthancStone
+{
+  class MeasureTool : public IObserver
+  {
+  public:
+    virtual ~MeasureTool();
+
+    /**
+    Enabled tools are rendered in the scene.
+    */
+    void Enable();
+
+    /**
+    Disabled tools are not rendered in the scene. This is useful to be able
+    to use them as their own memento in command stacks (when a measure tool
+    creation command has been undone, the measure remains alive in the
+    command object but is disabled so that it can be redone later on easily)
+    */
+    void Disable();
+
+    /**
+    This method is called when the scene transform changes. It allows to 
+    recompute the visual elements whose content depend upon the scene transform
+    */
+    void OnSceneTransformChanged(
+      const ViewportController::SceneTransformChanged& message);
+    
+    /**
+    This function must be implemented by the measuring tool to return whether
+    a given point in scene coords is close to the measuring tool.
+
+    This is used for mouse hover highlighting.
+
+    It is assumed that if the pointer position leads to this function returning
+    true, then a click at that position will return a tracker to edit the 
+    measuring tool
+    */
+    virtual bool HitTest(ScenePoint2D p) const = 0;
+  protected:
+    MeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW);
+
+    /**
+    The measuring tool may exist in a standalone fashion, without any available
+    scene (because the controller is dead or dying). This call allows to check 
+    before accessing the scene.
+    */
+    bool IsSceneAlive() const;
+    
+    /**
+    This is the meat of the tool: this method must [create (if needed) and]
+    update the layers and their data according to the measure tool kind and
+    current state. This is repeatedly called during user interaction
+    */
+    virtual void RefreshScene() = 0;
+
+    boost::shared_ptr<const ViewportController> GetController() const;
+    boost::shared_ptr<ViewportController>      GetController();
+
+    boost::shared_ptr<const Scene2D>            GetScene() const;
+    boost::shared_ptr<Scene2D>                 GetScene();
+
+    /**
+    enabled_ is not accessible by subclasses because there is a state machine
+    that we do not wanna mess with
+    */
+    bool IsEnabled() const;
+
+  private:
+    boost::weak_ptr<ViewportController> controllerW_;
+    bool     enabled_;
+  };
+}
+
+extern void TrackerSample_SetInfoDisplayMessage(
+  std::string key, std::string value);
--- a/Framework/Scene2DViewport/MeasureTools.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#include "MeasureTools.h"
-
-#include <Core/Logging.h>
-#include <Core/Enumerations.h>
-#include <Core/OrthancException.h>
-
-#include <boost/math/constants/constants.hpp>
-
-using namespace Orthanc;
-
-namespace OrthancStone
-{
-
-  MeasureTool::~MeasureTool()
-  {
-
-  }
-
-  void MeasureTool::Enable()
-  {
-    enabled_ = true;
-    RefreshScene();
-  }
-
-  void MeasureTool::Disable()
-  {
-    enabled_ = false;
-    RefreshScene();
-  }
-
-  bool MeasureTool::IsEnabled() const
-  {
-    return enabled_;
-  }
-
-
-  ViewportControllerPtr MeasureTool::GetController()
-  {
-    ViewportControllerPtr controller = controllerW_.lock();
-    if (!controller)
-      throw OrthancException(ErrorCode_InternalError, 
-        "Using dead ViewportController object!");
-    return controller;
-  }
-
-  OrthancStone::Scene2DPtr MeasureTool::GetScene()
-  {
-    return GetController()->GetScene();
-  }
-
-  MeasureTool::MeasureTool(MessageBroker& broker,
-    ViewportControllerWPtr controllerW)
-    : IObserver(broker)
-    , controllerW_(controllerW)
-    , enabled_(true)
-  {
-    GetController()->RegisterObserverCallback(
-      new Callable<MeasureTool, ViewportController::SceneTransformChanged>
-      (*this, &MeasureTool::OnSceneTransformChanged));
-  }
-
-
-  bool MeasureTool::IsSceneAlive() const
-  {
-    ViewportControllerPtr controller = controllerW_.lock();
-    return (controller.get() != NULL);
-  }
-
-  void MeasureTool::OnSceneTransformChanged(
-    const ViewportController::SceneTransformChanged& message)
-  {
-    RefreshScene();
-  }
-
-
-}
-
--- a/Framework/Scene2DViewport/MeasureTools.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include <Framework/Scene2DViewport/PointerTypes.h>
-#include <Framework/Scene2DViewport/ViewportController.h>
-
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-
-#include <vector>
-#include <cmath>
-
-namespace OrthancStone
-{
-  class MeasureTool : public IObserver
-  {
-  public:
-    virtual ~MeasureTool();
-
-    /**
-    Enabled tools are rendered in the scene.
-    */
-    void Enable();
-
-    /**
-    Disabled tools are not rendered in the scene. This is useful to be able
-    to use them as their own memento in command stacks (when a measure tool
-    creation command has been undone, the measure remains alive in the
-    command object but is disabled so that it can be redone later on easily)
-    */
-    void Disable();
-
-    /**
-    This method is called when the scene transform changes. It allows to 
-    recompute the visual elements whose content depend upon the scene transform
-    */
-    void OnSceneTransformChanged(
-      const ViewportController::SceneTransformChanged& message);
-
-  protected:
-    MeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW);
-
-    /**
-    The measuring tool may exist in a standalone fashion, without any available
-    scene (because the controller is dead or dying). This call allows to check 
-    before accessing the scene.
-    */
-    bool IsSceneAlive() const;
-    
-    /**
-    This is the meat of the tool: this method must [create (if needed) and]
-    update the layers and their data according to the measure tool kind and
-    current state. This is repeatedly called during user interaction
-    */
-    virtual void RefreshScene() = 0;
-
-    ViewportControllerPtr GetController();
-    Scene2DPtr GetScene();
-    
-    /**
-    enabled_ is not accessible by subclasses because there is a state machine
-    that we do not wanna mess with
-    */
-    bool IsEnabled() const;
-
-  private:
-    ViewportControllerWPtr controllerW_;
-    bool     enabled_;
-  };
-}
-
-
-extern void TrackerSample_SetInfoDisplayMessage(
-  std::string key, std::string value);
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,8 +19,12 @@
  **/
 
 #include "MeasureToolsToolbox.h"
+#include "PredeclaredTypes.h"
+#include "LayerHolder.h"
+#include "ViewportController.h"
 
-#include <Framework/Scene2D/TextSceneLayer.h>
+#include "../Scene2D/TextSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
 
 #include <boost/math/constants/constants.hpp>
 
@@ -31,6 +35,24 @@
 
 namespace OrthancStone
 {
+    void GetPositionOnBisectingLine(
+      ScenePoint2D& result
+      , const ScenePoint2D& p1
+      , const ScenePoint2D& c
+      , const ScenePoint2D& p2
+      , const double d)
+    {
+      // TODO: fix correct half-plane
+      double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+      double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+      double angle = 0.5 * (p1cAngle + p2cAngle);
+      double unitVectorX = cos(angle);
+      double unitVectorY = sin(angle);
+      double posX = c.GetX() + d * unitVectorX;
+      double posY = c.GetX() + d * unitVectorY;
+      result = ScenePoint2D(posX, posY);
+    }
+
   double RadiansToDegrees(double angleRad)
   {
     static const double factor = 180.0 / g_pi;
@@ -38,27 +60,31 @@
   }
 
   void AddSquare(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
+    boost::shared_ptr<const Scene2D>     scene,
     const ScenePoint2D& centerS,
-    const double&       sideLength)
+    const double&       sideLengthS)
   {
+    // get the scaling factor 
+    const double sceneToCanvas = 
+      scene->GetSceneToCanvasTransform().ComputeZoom();
+
     chain.clear();
     chain.reserve(4);
-    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    ScenePoint2D centerC = centerS.Apply(scene->GetSceneToCanvasTransform());
     //TODO: take DPI into account 
-    double handleLX = centerC.GetX() - sideLength / 2;
-    double handleTY = centerC.GetY() - sideLength / 2;
-    double handleRX = centerC.GetX() + sideLength / 2;
-    double handleBY = centerC.GetY() + sideLength / 2;
+    double handleLX = centerC.GetX() - sideLengthS * sceneToCanvas * 0.5;
+    double handleTY = centerC.GetY() - sideLengthS * sceneToCanvas * 0.5;
+    double handleRX = centerC.GetX() + sideLengthS * sceneToCanvas * 0.5;
+    double handleBY = centerC.GetY() + sideLengthS * sceneToCanvas * 0.5;
     ScenePoint2D LTC(handleLX, handleTY);
     ScenePoint2D RTC(handleRX, handleTY);
     ScenePoint2D RBC(handleRX, handleBY);
     ScenePoint2D LBC(handleLX, handleBY);
 
-    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startLT = LTC.Apply(scene->GetCanvasToSceneTransform());
+    ScenePoint2D startRT = RTC.Apply(scene->GetCanvasToSceneTransform());
+    ScenePoint2D startRB = RBC.Apply(scene->GetCanvasToSceneTransform());
+    ScenePoint2D startLB = LBC.Apply(scene->GetCanvasToSceneTransform());
 
     chain.push_back(startLT);
     chain.push_back(startRT);
@@ -86,7 +112,6 @@
 
   void AddShortestArc(
       PolylineSceneLayer::Chain& chain
-    , const Scene2D&             scene
     , const ScenePoint2D&        p1
     , const ScenePoint2D&        c
     , const ScenePoint2D&        p2
@@ -96,30 +121,11 @@
     double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
     double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
     AddShortestArc(
-      chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
+      chain, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
   }
 
-  void GetPositionOnBisectingLine(
-    ScenePoint2D&       result
-    , const ScenePoint2D& p1
-    , const ScenePoint2D& c
-    , const ScenePoint2D& p2
-    , const double d)
-  {
-    // TODO: fix correct half-plane
-    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
-    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
-    double angle = 0.5*(p1cAngle + p2cAngle);
-    double unitVectorX = cos(angle);
-    double unitVectorY = sin(angle);
-    double posX = c.GetX() + d * unitVectorX;
-    double posY = c.GetX() + d * unitVectorY;
-    result = ScenePoint2D(posX, posY);
-  }
-   
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         centerS
     , const double&               radiusS
     , const double                startAngleRad
@@ -197,7 +203,6 @@
 #endif
 
   void AddCircle(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
     const ScenePoint2D& centerS,
     const double&       radiusS,
     const int           numSubdivisions)
@@ -277,60 +282,44 @@
 }
 #endif
 
-
-  namespace
-  {
-    /**
-    Helper function for outlined text rendering
-    */
-    TextSceneLayer* GetOutlineTextLayer(
-      Scene2D& scene, int baseLayerIndex, int index)
-    {
-      assert(scene.HasLayer(baseLayerIndex));
-      assert(index >= 0);
-      assert(index < 5);
-
-      ISceneLayer * layer = &(scene.GetLayer(baseLayerIndex + index));
-      TextSceneLayer * concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
-      assert(concreteLayer != NULL);
-      return concreteLayer;
-    }
-  }
-   
+  /**
+  This utility function assumes that the layer holder contains 5 text layers
+  and will use the first four ones for the text background and the fifth one
+  for the actual text
+  */
   void SetTextLayerOutlineProperties(
-    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p)
+    boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder, 
+    const char* text, ScenePoint2D p)
   {
     double xoffsets[5] = { 2, 0, -2, 0, 0 };
     double yoffsets[5] = { 0, -2, 0, 2, 0 };
 
     // get the scaling factor 
     const double pixelToScene =
-      scene.GetCanvasToSceneTransform().ComputeZoom();
+      scene->GetCanvasToSceneTransform().ComputeZoom();
 
     for (int i = 0; i < 5; ++i)
     {
-      TextSceneLayer* textLayer = GetOutlineTextLayer(scene, baseLayerIndex, i);
+      TextSceneLayer* textLayer = layerHolder->GetTextLayer(i);
       textLayer->SetText(text);
 
       if (i == 4)
-        textLayer->SetColor(0, 223, 81);
+      {
+        textLayer->SetColor(TEXT_COLOR_RED,
+                            TEXT_COLOR_GREEN,
+                            TEXT_COLOR_BLUE);
+      }
       else
-        textLayer->SetColor(0, 56, 21);
+      {
+        textLayer->SetColor(TEXT_OUTLINE_COLOR_RED,
+                            TEXT_OUTLINE_COLOR_GREEN,
+                            TEXT_OUTLINE_COLOR_BLUE);
+      }
 
       ScenePoint2D textAnchor;
-      //GetPositionOnBisectingLine(
-      //  textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene);
       textLayer->SetPosition(
         p.GetX() + xoffsets[i] * pixelToScene,
         p.GetY() + yoffsets[i] * pixelToScene);
     }
   }
-
-
-
-
-
-
-
-
 }
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Mon Jun 24 14:35:00 2019 +0200
@@ -18,8 +18,9 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/Scene2D.h>
+#include "PredeclaredTypes.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
 
 namespace OrthancStone
 {
@@ -30,10 +31,9 @@
   square sides are parallel to the canvas boundaries.
   */
   void AddSquare(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
+    boost::shared_ptr<const Scene2D>     scene,
     const ScenePoint2D& centerS,
-    const double&       sideLength);
-
+    const double&       sideLengthS);
 
   /**
     Creates an arc centered on c that goes
@@ -49,7 +49,6 @@
   */
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         p1
     , const ScenePoint2D&         c
     , const ScenePoint2D&         p2
@@ -64,7 +63,6 @@
   */
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         centerS
     , const double&               radiusS
     , const double                startAngleRad
@@ -182,6 +180,6 @@
   from layerIndex, up to (and not including) layerIndex+5. 
   */
   void SetTextLayerOutlineProperties(
-    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p);
-
+    boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder,
+    const char* text, ScenePoint2D p);
 }
--- a/Framework/Scene2DViewport/MeasureTrackers.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -21,12 +21,10 @@
 #include "MeasureTrackers.h"
 #include <Core/OrthancException.h>
 
-using namespace Orthanc;
-
 namespace OrthancStone
 {
 
-  CreateMeasureTracker::CreateMeasureTracker(ViewportControllerWPtr controllerW)
+  CreateMeasureTracker::CreateMeasureTracker(boost::weak_ptr<ViewportController> controllerW)
     : controllerW_(controllerW)
     , alive_(true)
     , commitResult_(true)
@@ -56,7 +54,7 @@
       command_->Undo();
   }
 
-  OrthancStone::Scene2DPtr CreateMeasureTracker::GetScene()
+  boost::shared_ptr<Scene2D> CreateMeasureTracker::GetScene()
   {
     return controllerW_.lock()->GetScene();
   }
--- a/Framework/Scene2DViewport/MeasureTrackers.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,10 +21,10 @@
 #pragma once
 
 #include "IFlexiblePointerTracker.h"
-#include "../../Framework/Scene2D/Scene2D.h"
-#include "../../Framework/Scene2D/PointerEvent.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/PointerEvent.h"
 
-#include "MeasureTools.h"
+#include "MeasureTool.h"
 #include "MeasureCommands.h"
 
 #include <vector>
@@ -37,15 +37,15 @@
     virtual void Cancel() ORTHANC_OVERRIDE;
     virtual bool IsAlive() const ORTHANC_OVERRIDE;
   protected:
-    CreateMeasureTracker(ViewportControllerWPtr controllerW);
+    CreateMeasureTracker(boost::weak_ptr<ViewportController> controllerW);
 
     ~CreateMeasureTracker();
   
   protected:
-    CreateMeasureCommandPtr         command_;
-    ViewportControllerWPtr          controllerW_;
+    boost::shared_ptr<CreateMeasureCommand>         command_;
+    boost::weak_ptr<ViewportController>          controllerW_;
     bool                            alive_;
-    Scene2DPtr                      GetScene();
+    boost::shared_ptr<Scene2D>                      GetScene();
 
   private:
     bool                            commitResult_;
--- a/Framework/Scene2DViewport/OneGesturePointerTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/OneGesturePointerTracker.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -23,14 +23,12 @@
 
 #include <Core/OrthancException.h>
 
-#include <Framework/StoneException.h>
-
-using namespace Orthanc;
+#include "../StoneException.h"
 
 namespace OrthancStone
 {
   OneGesturePointerTracker::OneGesturePointerTracker(
-    ViewportControllerWPtr controllerW)
+    boost::weak_ptr<ViewportController> controllerW)
     : controllerW_(controllerW)
     , alive_(true)
     , currentTouchCount_(1)
@@ -64,7 +62,7 @@
     return alive_;
   }
 
-  ViewportControllerPtr OneGesturePointerTracker::GetController()
+  boost::shared_ptr<ViewportController> OneGesturePointerTracker::GetController()
   {
     return controllerW_.lock();
   }
--- a/Framework/Scene2DViewport/OneGesturePointerTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/OneGesturePointerTracker.h	Mon Jun 24 14:35:00 2019 +0200
@@ -39,16 +39,16 @@
   class OneGesturePointerTracker : public IFlexiblePointerTracker
   {
   public:
-    OneGesturePointerTracker(ViewportControllerWPtr controllerW);
+    OneGesturePointerTracker(boost::weak_ptr<ViewportController> controllerW);
     virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE;
     virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE;
     virtual bool IsAlive() const ORTHANC_OVERRIDE;
   
   protected:
-    ViewportControllerPtr  GetController();
+    boost::shared_ptr<ViewportController>  GetController();
 
   private:
-    ViewportControllerWPtr controllerW_;
+    boost::weak_ptr<ViewportController> controllerW_;
     bool                   alive_;
     int                    currentTouchCount_;
   };
--- a/Framework/Scene2DViewport/PointerTypes.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +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/>.
- **/
-
-#pragma once
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-
-#include <vector>
-
-namespace OrthancStone
-{
-  class Scene2D;
-  typedef boost::shared_ptr<Scene2D> Scene2DPtr;
-
-  typedef boost::weak_ptr<Scene2D> Scene2DWPtr;
-
-  class MeasureTool;
-  typedef boost::shared_ptr<MeasureTool>
-    MeasureToolPtr;
-  typedef boost::weak_ptr<MeasureTool>
-    MeasureToolWPtr;
-
-  class LineMeasureTool;
-  typedef boost::shared_ptr<LineMeasureTool>
-    LineMeasureToolPtr;
-
-  class AngleMeasureTool;
-  typedef boost::shared_ptr<AngleMeasureTool>
-    AngleMeasureToolPtr;
-
-  class IPointerTracker;
-  typedef boost::shared_ptr<IPointerTracker>
-    PointerTrackerPtr;
-
-  class IFlexiblePointerTracker;
-  typedef boost::shared_ptr<IFlexiblePointerTracker>
-    FlexiblePointerTrackerPtr;
-
-  typedef boost::shared_ptr<LineMeasureTool>
-    LineMeasureToolPtr;
-
-  class CreateMeasureCommand;
-  typedef boost::shared_ptr<CreateMeasureCommand>
-    CreateMeasureCommandPtr;
-
-  class CreateLineMeasureCommand;
-  typedef boost::shared_ptr<CreateLineMeasureCommand>
-    CreateLineMeasureCommandPtr;
-
-  class CreateAngleMeasureCommand;
-  typedef boost::shared_ptr<CreateAngleMeasureCommand>
-    CreateAngleMeasureCommandPtr;
-
-
-  typedef boost::shared_ptr<Scene2D> Scene2DPtr;
-
-  class TrackerCommand;
-  typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr;
-
-  class ViewportController;
-  typedef boost::shared_ptr<ViewportController> ViewportControllerPtr;
-  typedef boost::weak_ptr<ViewportController> ViewportControllerWPtr;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/PredeclaredTypes.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,41 @@
+/**
+ * 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 <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+namespace OrthancStone
+
+{
+  class Scene2D;
+  class MeasureTool;
+  class LineMeasureTool;
+  class AngleMeasureTool;
+  class IPointerTracker;
+  class IFlexiblePointerTracker;
+  class CreateMeasureCommand;
+  class CreateLineMeasureCommand;
+  class CreateAngleMeasureCommand;
+  class TrackerCommand;
+  class ViewportController;
+  class LayerHolder;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/UndoStack.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "UndoStack.h"
+
+#include "MeasureCommands.h"
+
+#include "../StoneException.h"
+
+namespace OrthancStone
+{
+  UndoStack::UndoStack() : numAppliedCommands_(0)
+  {}
+
+  void UndoStack::PushCommand(boost::shared_ptr<TrackerCommand> command)
+  {
+    commandStack_.erase(
+      commandStack_.begin() + numAppliedCommands_,
+      commandStack_.end());
+
+    ORTHANC_ASSERT(std::find(commandStack_.begin(), commandStack_.end(), command)
+      == commandStack_.end(), "Duplicate command");
+    commandStack_.push_back(command);
+    numAppliedCommands_++;
+  }
+
+  void UndoStack::Undo()
+  {
+    ORTHANC_ASSERT(CanUndo(), "");
+    commandStack_[numAppliedCommands_ - 1]->Undo();
+    numAppliedCommands_--;
+  }
+
+  void UndoStack::Redo()
+  {
+    ORTHANC_ASSERT(CanRedo(), "");
+    commandStack_[numAppliedCommands_]->Redo();
+    numAppliedCommands_++;
+  }
+
+  bool UndoStack::CanUndo() const
+  {
+    return numAppliedCommands_ > 0;
+  }
+
+  bool UndoStack::CanRedo() const
+  {
+    return numAppliedCommands_ < commandStack_.size();
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/UndoStack.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,77 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace OrthancStone
+{
+  class TrackerCommand;
+
+  class UndoStack
+  {
+  public:
+    UndoStack();
+
+    /**
+    Stores a command :
+    - this first trims the undo stack to keep the first numAppliedCommands_
+    - then it adds the supplied command at the top of the undo stack
+
+    In other words, when a new command is pushed, all the undone (and not
+    redone) commands are removed.
+    */
+    void PushCommand(boost::shared_ptr<TrackerCommand> command);
+
+    /**
+    Undoes the command at the top of the undo stack, or throws if there is no
+    command to undo.
+    You can check "CanUndo" first to protect against extraneous redo.
+    */
+    void Undo();
+
+    /**
+    Redoes the command that is just above the last applied command in the undo
+    stack or throws if there is no command to redo.
+    You can check "CanRedo" first to protect against extraneous redo.
+    */
+    void Redo();
+
+    /** selfexpl */
+    bool CanUndo() const;
+
+    /** selfexpl */
+    bool CanRedo() const;
+  
+  private:
+    std::vector<boost::shared_ptr<TrackerCommand> > commandStack_;
+
+    /**
+    This is always between >= 0 and <= undoStack_.size() and gives the
+    position where the controller is in the undo stack.
+    - If numAppliedCommands_ > 0, one can undo
+    - If numAppliedCommands_ < numAppliedCommands_.size(), one can redo
+    */
+    size_t                      numAppliedCommands_;
+  };
+}
--- a/Framework/Scene2DViewport/ViewportController.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,24 +19,65 @@
  **/
 
 #include "ViewportController.h"
+
+#include "UndoStack.h"
 #include "MeasureCommands.h"
 
-#include <Framework/StoneException.h>
+#include "../StoneException.h"
 
 #include <boost/make_shared.hpp>
 
-using namespace Orthanc;
-
 namespace OrthancStone
 {
-  ViewportController::ViewportController(MessageBroker& broker)
+  ViewportController::ViewportController(boost::weak_ptr<UndoStack> undoStackW, MessageBroker& broker)
     : IObservable(broker)
-    , numAppliedCommands_(0)
+    , undoStackW_(undoStackW)
+    , canvasToSceneFactor_(0.0)
   {
     scene_ = boost::make_shared<Scene2D>();
   }
 
-  Scene2DPtr ViewportController::GetScene()
+  boost::shared_ptr<UndoStack> ViewportController::GetUndoStack()
+  {
+    return undoStackW_.lock();
+  }
+
+  boost::shared_ptr<const UndoStack> ViewportController::GetUndoStack() const
+  {
+    return undoStackW_.lock();
+  }
+
+  void ViewportController::PushCommand(boost::shared_ptr<TrackerCommand> command)
+  {
+    GetUndoStack()->PushCommand(command);
+  }
+
+  void ViewportController::Undo()
+  {
+    GetUndoStack()->Undo();
+  }
+
+  void ViewportController::Redo()
+  {
+    GetUndoStack()->Redo();
+  }
+
+  bool ViewportController::CanUndo() const
+  {
+    return GetUndoStack()->CanUndo();
+  }
+
+  bool ViewportController::CanRedo() const
+  {
+    return GetUndoStack()->CanRedo();
+  }
+  
+  boost::shared_ptr<const Scene2D> ViewportController::GetScene() const
+  {
+    return scene_;
+  }
+
+  boost::shared_ptr<Scene2D> ViewportController::GetScene()
   {
     return scene_;
   }
@@ -46,16 +87,16 @@
     throw StoneException(ErrorCode_NotImplemented);
   }
 
-  std::vector<MeasureToolPtr> ViewportController::HitTestMeasureTools(
+  std::vector<boost::shared_ptr<MeasureTool> > ViewportController::HitTestMeasureTools(
     ScenePoint2D p)
   {
-    std::vector<MeasureToolPtr> ret;
+    std::vector<boost::shared_ptr<MeasureTool> > ret;
     
-
-    //for (size_t i = 0; i < measureTools_.size(); ++i)
-    //{
-
-    //}
+    for (size_t i = 0; i < measureTools_.size(); ++i)
+    {
+      if (measureTools_[i]->HitTest(p))
+        ret.push_back(measureTools_[i]);
+    }
     return ret;
   }
 
@@ -74,6 +115,10 @@
   {
     scene_->SetSceneToCanvasTransform(transform);
     BroadcastMessage(SceneTransformChanged(*this));
+    
+    // update the canvas to scene factor
+    canvasToSceneFactor_ = 0.0;
+    canvasToSceneFactor_ = GetCanvasToSceneFactor();
   }
 
   void ViewportController::FitContent(
@@ -83,55 +128,51 @@
     BroadcastMessage(SceneTransformChanged(*this));
   }
 
-  void ViewportController::PushCommand(TrackerCommandPtr command)
-  {
-    commandStack_.erase(
-      commandStack_.begin() + numAppliedCommands_,
-      commandStack_.end());
-    
-    ORTHANC_ASSERT(std::find(commandStack_.begin(), commandStack_.end(), command) 
-      == commandStack_.end(), "Duplicate command");
-    commandStack_.push_back(command);
-    numAppliedCommands_++;
-  }
-
-  void ViewportController::Undo()
-  {
-    ORTHANC_ASSERT(CanUndo(), "");
-    commandStack_[numAppliedCommands_-1]->Undo();
-    numAppliedCommands_--;
-  }
-
-  void ViewportController::Redo()
-  {
-    ORTHANC_ASSERT(CanRedo(), "");
-    commandStack_[numAppliedCommands_]->Redo();
-    numAppliedCommands_++;
-  }
-
-  bool ViewportController::CanUndo() const
-  {
-    return numAppliedCommands_ > 0;
-  }
-
-  bool ViewportController::CanRedo() const
-  {
-    return numAppliedCommands_ < commandStack_.size();
-  }
-
-  void ViewportController::AddMeasureTool(MeasureToolPtr measureTool)
+  void ViewportController::AddMeasureTool(boost::shared_ptr<MeasureTool> measureTool)
   {
     ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool)
       == measureTools_.end(), "Duplicate measure tool");
     measureTools_.push_back(measureTool);
   }
 
-  void ViewportController::RemoveMeasureTool(MeasureToolPtr measureTool)
+  void ViewportController::RemoveMeasureTool(boost::shared_ptr<MeasureTool> measureTool)
   {
     ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool)
       != measureTools_.end(), "Measure tool not found");
-    measureTools_.push_back(measureTool);
+    measureTools_.erase(
+      std::remove(measureTools_.begin(), measureTools_.end(), measureTool), 
+      measureTools_.end());
+  }
+
+
+  double ViewportController::GetCanvasToSceneFactor() const
+  {
+    if (canvasToSceneFactor_ == 0)
+    {
+      canvasToSceneFactor_ =
+        GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+    }
+    return canvasToSceneFactor_;
   }
 
+  double ViewportController::GetHandleSideLengthS() const
+  {
+    return HANDLE_SIDE_LENGTH_CANVAS_COORD * GetCanvasToSceneFactor();
+  }
+
+  double ViewportController::GetAngleToolArcRadiusS() const
+  {
+    return ARC_RADIUS_CANVAS_COORD * GetCanvasToSceneFactor();
+  }
+
+  double ViewportController::GetHitTestMaximumDistanceS() const
+  {
+    return HIT_TEST_MAX_DISTANCE_CANVAS_COORD * GetCanvasToSceneFactor();
+  }
+
+  double ViewportController::GetAngleTopTextLabelDistanceS() const
+  {
+    return TEXT_CENTER_DISTANCE_CANVAS_COORD * GetCanvasToSceneFactor();
+  }
 }
 
--- a/Framework/Scene2DViewport/ViewportController.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.h	Mon Jun 24 14:35:00 2019 +0200
@@ -20,16 +20,37 @@
 
 #pragma once
 
-#include "PointerTypes.h"
+#include "PredeclaredTypes.h"
 
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/PointerEvent.h>
-#include <Framework/Scene2DViewport/IFlexiblePointerTracker.h>
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/PointerEvent.h"
+#include "../Scene2DViewport/IFlexiblePointerTracker.h"
 
 #include <stack>
 
 namespace OrthancStone
 {
+  class UndoStack;
+
+  const double ARC_RADIUS_CANVAS_COORD = 30.0;
+  const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90;
+
+  const double HANDLE_SIDE_LENGTH_CANVAS_COORD = 10.0;
+  const double HIT_TEST_MAX_DISTANCE_CANVAS_COORD = 15.0;
+
+  const uint8_t TEXT_COLOR_RED = 0;
+  const uint8_t TEXT_COLOR_GREEN = 223;
+  const uint8_t TEXT_COLOR_BLUE = 81;
+
+  const uint8_t TOOL_LINES_COLOR_RED = 0;
+  const uint8_t TOOL_LINES_COLOR_GREEN = 223;
+  const uint8_t TOOL_LINES_COLOR_BLUE = 21;
+
+
+  const uint8_t TEXT_OUTLINE_COLOR_RED = 0;
+  const uint8_t TEXT_OUTLINE_COLOR_GREEN = 56;
+  const uint8_t TEXT_OUTLINE_COLOR_BLUE = 21;
+
   /**
   This object is responsible for hosting a scene, responding to messages from
   the model and updating the scene accordingly.
@@ -48,9 +69,10 @@
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \
       SceneTransformChanged, ViewportController);
 
-    ViewportController(MessageBroker& broker);
+    ViewportController(boost::weak_ptr<UndoStack> undoStackW, MessageBroker& broker);
 
-    Scene2DPtr GetScene();
+    boost::shared_ptr<const Scene2D> GetScene() const;
+    boost::shared_ptr<Scene2D>      GetScene();
 
     /** 
     This method is called by the GUI system and should update/delete the
@@ -63,13 +85,13 @@
     (in scene coords). A tracker can then be requested from the chosen 
     measure tool, if needed
     */
-    std::vector<MeasureToolPtr> HitTestMeasureTools(ScenePoint2D p);
+    std::vector<boost::shared_ptr<MeasureTool> > HitTestMeasureTools(ScenePoint2D p);
 
     /**
     With this method, the object takes ownership of the supplied tracker and
     updates it according to user interaction
     */
-    void SetActiveTracker(FlexiblePointerTrackerPtr tracker);
+    void SetActiveTracker(boost::shared_ptr<IFlexiblePointerTracker> tracker);
 
     /** Forwarded to the underlying scene */
     const AffineTransform2D& GetCanvasToSceneTransform() const;
@@ -83,54 +105,68 @@
     /** Forwarded to the underlying scene, and broadcasted to the observers */
     void FitContent(unsigned int canvasWidth, unsigned int canvasHeight);
 
-    /** 
-    Stores a command : 
-    - this first trims the undo stack to keep the first numAppliedCommands_ 
-    - then it adds the supplied command at the top of the undo stack
+    /** Adds a new measure tool */
+    void AddMeasureTool(boost::shared_ptr<MeasureTool> measureTool);
+
+    /** Removes a measure tool or throws if it cannot be found */
+    void RemoveMeasureTool(boost::shared_ptr<MeasureTool> measureTool);
 
-    In other words, when a new command is pushed, all the undone (and not 
-    redone) commands are removed.
+    /**
+    The square handle side length in *scene* coordinates
     */
-    void PushCommand(TrackerCommandPtr command);
+    double GetHandleSideLengthS() const;
+
+    /**
+    The angle measure too arc  radius in *scene* coordinates
+
+    Note: you might wonder why this is not part of the AngleMeasureTool itself,
+    but we prefer to put all such constants in the same location, to ease 
+    */
+    double GetAngleToolArcRadiusS() const;
 
     /**
-    Undoes the command at the top of the undo stack, or throws if there is no
-    command to undo.
-    You can check "CanUndo" first to protect against extraneous redo.
+    The hit test maximum distance in *scene* coordinates.
+    If a pointer event is less than GetHandleSideLengthS() to a GUI element,
+    the hit test for this GUI element is seen as true
     */
+    double GetHitTestMaximumDistanceS() const;
+
+    /**
+    Distance between the top of the angle measuring tool and the center of 
+    the label showing the actual measure, in *scene* coordinates
+    */
+    double GetAngleTopTextLabelDistanceS() const;
+
+
+    /** forwarded to the UndoStack */
+    void PushCommand(boost::shared_ptr<TrackerCommand> command);
+
+    /** forwarded to the UndoStack */
     void Undo();
 
-    /**
-    Redoes the command that is just above the last applied command in the undo
-    stack or throws if there is no command to redo. 
-    You can check "CanRedo" first to protect against extraneous redo.
-    */
+    /** forwarded to the UndoStack */
     void Redo();
 
-    /** selfexpl */
+    /** forwarded to the UndoStack */
     bool CanUndo() const;
 
-    /** selfexpl */
+    /** forwarded to the UndoStack */
     bool CanRedo() const;
 
-    /** Adds a new measure tool */
-    void AddMeasureTool(MeasureToolPtr measureTool);
-
-    /** Removes a measure tool or throws if it cannot be found */
-    void RemoveMeasureTool(MeasureToolPtr measureTool);
 
   private:
-    std::vector<TrackerCommandPtr> commandStack_;
+    double GetCanvasToSceneFactor() const;
+
+    boost::weak_ptr<UndoStack>                   undoStackW_;
+
+    boost::shared_ptr<UndoStack>                 GetUndoStack();
+    boost::shared_ptr<const UndoStack>           GetUndoStack() const;
+
+    std::vector<boost::shared_ptr<MeasureTool> > measureTools_;
+    boost::shared_ptr<Scene2D>                   scene_;
+    boost::shared_ptr<IFlexiblePointerTracker>   tracker_;
     
-    /**
-    This is always between >= 0 and <= undoStack_.size() and gives the 
-    position where the controller is in the undo stack. 
-    - If numAppliedCommands_ > 0, one can undo
-    - If numAppliedCommands_ < numAppliedCommands_.size(), one can redo
-    */
-    size_t                         numAppliedCommands_;
-    std::vector<MeasureToolPtr>    measureTools_;
-    Scene2DPtr                     scene_;
-    FlexiblePointerTrackerPtr      tracker_;
+    // this is cached
+    mutable double              canvasToSceneFactor_;
   };
 }
--- a/Framework/SmartLoader.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,291 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SmartLoader.h"
-#include "Layers/DicomSeriesVolumeSlicer.h"
-#include "Messages/MessageForwarder.h"
-#include "Core/Images/Image.h"
-#include "Framework/Widgets/SliceViewerWidget.h"
-#include "Framework/StoneException.h"
-#include "Framework/Layers/FrameRenderer.h"
-#include "Core/Logging.h"
-
-namespace Deprecated
-{
-  enum CachedSliceStatus
-  {
-    CachedSliceStatus_ScheduledToLoad,
-    CachedSliceStatus_GeometryLoaded,
-    CachedSliceStatus_ImageLoaded
-  };
-
-  class SmartLoader::CachedSlice : public IVolumeSlicer
-  {
-  public:
-    class RendererFactory : public LayerReadyMessage::IRendererFactory
-    {
-    private:
-      const CachedSlice&  that_;
-
-    public:
-      RendererFactory(const CachedSlice& that) :
-        that_(that)
-      {
-      }
-
-      virtual ILayerRenderer* CreateRenderer() const
-      {
-        bool isFull = (that_.effectiveQuality_ == OrthancStone::SliceImageQuality_FullPng ||
-                       that_.effectiveQuality_ == OrthancStone::SliceImageQuality_FullPam);
-
-        return FrameRenderer::CreateRenderer(*that_.image_, *that_.slice_, isFull);
-      }
-    };
-    
-    unsigned int                    sliceIndex_;
-    std::auto_ptr<Slice>            slice_;
-    boost::shared_ptr<Orthanc::ImageAccessor>   image_;
-    OrthancStone::SliceImageQuality               effectiveQuality_;
-    CachedSliceStatus               status_;
-
-  public:
-    CachedSlice(OrthancStone::MessageBroker& broker) :
-    IVolumeSlicer(broker)
-    {
-    }
-
-    virtual ~CachedSlice()
-    {
-    }
-
-    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
-                           const OrthancStone::CoordinateSystem3D& viewportSlice)
-    {
-      // TODO: viewportSlice is not used !!!!
-      slice_->GetExtent(points);
-      return true;
-    }
-
-    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
-    {
-      // TODO: viewportSlice is not used !!!!
-
-      // it has already been loaded -> trigger the "layer ready" message immediately otherwise, do nothing now.  The LayerReady will be triggered
-      // once the VolumeSlicer is ready
-      if (status_ == CachedSliceStatus_ImageLoaded)
-      {
-        LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId();
-
-        RendererFactory factory(*this);   
-        BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry()));
-      }
-      else
-      {
-        LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is not loaded yet): " << slice_->GetOrthancInstanceId();
-      }
-    }
-
-    CachedSlice* Clone() const
-    {
-      CachedSlice* output = new CachedSlice(GetBroker());
-      output->sliceIndex_ = sliceIndex_;
-      output->slice_.reset(slice_->Clone());
-      output->image_ = image_;
-      output->effectiveQuality_ = effectiveQuality_;
-      output->status_ = status_;
-
-      return output;
-    }
-
-  };
-
-
-  SmartLoader::SmartLoader(OrthancStone::MessageBroker& broker,  
-                           OrthancApiClient& orthancApiClient) :
-    IObservable(broker),
-    IObserver(broker),
-    imageQuality_(OrthancStone::SliceImageQuality_FullPam),
-    orthancApiClient_(orthancApiClient)
-  {
-  }
-
-  void SmartLoader::SetFrameInWidget(SliceViewerWidget& sliceViewer, 
-                                     size_t layerIndex, 
-                                     const std::string& instanceId, 
-                                     unsigned int frame)
-  {
-    // TODO: check if this frame has already been loaded or is already being loaded.
-    // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately"
-    //   (it can not be immediate because Observers needs to register first and this is done after this method returns)
-    // - if currently loading, we need to return an object that will observe the existing VolumeSlicer and forward
-    //   the messages to its observables
-    // in both cases, we must be carefull about objects lifecycle !!!
-
-    std::auto_ptr<IVolumeSlicer> layerSource;
-    std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
-    SmartLoader::CachedSlice* cachedSlice = NULL;
-
-    if (cachedSlices_.find(sliceKeyId) != cachedSlices_.end()) // && cachedSlices_[sliceKeyId]->status_ == CachedSliceStatus_Loaded)
-    {
-      layerSource.reset(cachedSlices_[sliceKeyId]->Clone());
-      cachedSlice = dynamic_cast<SmartLoader::CachedSlice*>(layerSource.get());
-    }
-    else
-    {
-      layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_));
-      dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_);
-      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady));
-      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady));
-      layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady));
-      dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame);
-    }
-
-    // make sure that the widget registers the events before we trigger them
-    if (sliceViewer.GetLayerCount() == layerIndex)
-    {
-      sliceViewer.AddLayer(layerSource.release());
-    }
-    else if (sliceViewer.GetLayerCount() > layerIndex)
-    {
-      sliceViewer.ReplaceLayer(layerIndex, layerSource.release());
-    }
-    else
-    {
-      throw OrthancStone::StoneException(OrthancStone::ErrorCode_CanOnlyAddOneLayerAtATime);
-    }
-
-    if (cachedSlice != NULL)
-    {
-      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice));
-    }
-
-  }
-
-  void SmartLoader::PreloadSlice(const std::string instanceId, 
-                                 unsigned int frame)
-  {
-    // TODO: reactivate -> need to be able to ScheduleLayerLoading in IVolumeSlicer without calling ScheduleLayerCreation
-    return;
-    // TODO: check if it is already in the cache
-
-
-
-    // create the slice in the cache with "empty" data
-    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
-    cachedSlice->slice_.reset(new Slice(instanceId, frame));
-    cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad;
-    std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
-
-    LOG(WARNING) << "Will preload: " << sliceKeyId;
-
-    cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice);
-
-    std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_));
-
-    dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_);
-    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady));
-    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady));
-    layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady));
-    dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame);
-
-    // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache
-    preloadingInstances_[sliceKeyId] = boost::shared_ptr<IVolumeSlicer>(layerSource.release());
-  }
-
-
-//  void PreloadStudy(const std::string studyId)
-//  {
-//    /* TODO */
-//  }
-
-//  void PreloadSeries(const std::string seriesId)
-//  {
-//    /* TODO */
-//  }
-
-
-  void SmartLoader::OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message)
-  {
-    const DicomSeriesVolumeSlicer& source =
-      dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin());
-
-    // save/replace the slice in cache
-    const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount()
-    std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
-                              boost::lexical_cast<std::string>(slice.GetFrame()));
-
-    LOG(WARNING) << "Geometry ready: " << sliceKeyId;
-
-    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
-    cachedSlice->slice_.reset(slice.Clone());
-    cachedSlice->effectiveQuality_ = source.GetImageQuality();
-    cachedSlice->status_ = CachedSliceStatus_GeometryLoaded;
-
-    cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice);
-
-    // re-emit original Layer message to observers
-    BroadcastMessage(message);
-  }
-
-
-  void SmartLoader::OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message)
-  {
-    // save/replace the slice in cache
-    const Slice& slice = message.GetSlice();
-    std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
-                              boost::lexical_cast<std::string>(slice.GetFrame()));
-
-    LOG(WARNING) << "Image ready: " << sliceKeyId;
-
-    boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
-    cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame()));
-    cachedSlice->effectiveQuality_ = message.GetImageQuality();
-    cachedSlice->slice_.reset(message.GetSlice().Clone());
-    cachedSlice->status_ = CachedSliceStatus_ImageLoaded;
-
-    cachedSlices_[sliceKeyId] = cachedSlice;
-
-    // re-emit original Layer message to observers
-    BroadcastMessage(message);
-  }
-
-
-  void SmartLoader::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message)
-  {
-    const DicomSeriesVolumeSlicer& source =
-      dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin());
-    
-    const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() ?
-    std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
-                              boost::lexical_cast<std::string>(slice.GetFrame()));
-
-    LOG(WARNING) << "Layer ready: " << sliceKeyId;
-
-    // remove the slice from the preloading slices now that it has been fully loaded and it is referenced in the cache
-    if (preloadingInstances_.find(sliceKeyId) != preloadingInstances_.end())
-    {
-      preloadingInstances_.erase(sliceKeyId);
-    }
-
-    // re-emit original Layer message to observers
-    BroadcastMessage(message);
-  }
-}
--- a/Framework/SmartLoader.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-#include <map>
-
-#include "Layers/DicomSeriesVolumeSlicer.h"
-#include "Messages/IObservable.h"
-#include "Toolbox/OrthancApiClient.h"
-
-namespace Deprecated
-{
-  class SliceViewerWidget;
-
-  class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver
-  {
-    class CachedSlice;
-
-  protected:
-    typedef std::map<std::string, boost::shared_ptr<SmartLoader::CachedSlice> > CachedSlices;
-    CachedSlices cachedSlices_;
-
-    typedef std::map<std::string, boost::shared_ptr<IVolumeSlicer> > PreloadingInstances;
-    PreloadingInstances preloadingInstances_;
-
-    OrthancStone::SliceImageQuality     imageQuality_;
-    OrthancApiClient&     orthancApiClient_;
-
-  public:
-    SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient);  // TODO: add maxPreloadStorageSizeInBytes
-
-//    void PreloadStudy(const std::string studyId);
-//    void PreloadSeries(const std::string seriesId);
-    void PreloadSlice(const std::string instanceId, unsigned int frame);
-
-    void SetImageQuality(OrthancStone::SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
-
-    void SetFrameInWidget(SliceViewerWidget& sliceViewer, size_t layerIndex, const std::string& instanceId, unsigned int frame);
-
-    void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId);
-
-  private:
-    void OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message);
-    void OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message);
-    void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message);
-
-  };
-
-}
--- a/Framework/StoneEnumerations.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/StoneEnumerations.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,6 +23,22 @@
 
 #include <string>
 
+
+namespace Deprecated
+{
+  enum SliceImageQuality
+  {
+    SliceImageQuality_FullPng,  // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth)
+    SliceImageQuality_FullPam,  // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN)
+    SliceImageQuality_Jpeg50,
+    SliceImageQuality_Jpeg90,
+    SliceImageQuality_Jpeg95,
+
+    SliceImageQuality_InternalRaw   // downloads the raw pixels data as they are stored in the DICOM file (internal use only)
+  };  
+}
+
+
 namespace OrthancStone
 {
   enum SliceOffsetMode
@@ -85,17 +101,6 @@
     KeyboardKeys_Down = 40
   };
 
-  enum SliceImageQuality
-  {
-    SliceImageQuality_FullPng,  // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth)
-    SliceImageQuality_FullPam,  // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN)
-    SliceImageQuality_Jpeg50,
-    SliceImageQuality_Jpeg90,
-    SliceImageQuality_Jpeg95,
-
-    SliceImageQuality_InternalRaw   // downloads the raw pixels data as they are stored in the DICOM file (internal use only)
-  };
-
   enum SopClassUid
   {
     SopClassUid_Other,
@@ -115,23 +120,6 @@
     BitmapAnchor_TopRight
   };
 
-  enum ControlPointType
-  {
-    ControlPoint_TopLeftCorner = 0,
-    ControlPoint_TopRightCorner = 1,
-    ControlPoint_BottomRightCorner = 2,
-    ControlPoint_BottomLeftCorner = 3
-  };
-
-  enum PhotometricDisplayMode
-  {
-    PhotometricDisplayMode_Default,
-
-    PhotometricDisplayMode_Monochrome1,
-    PhotometricDisplayMode_Monochrome2
-  };
-
-  
   SopClassUid StringToSopClassUid(const std::string& source);
 
   void ComputeWindowing(float& targetCenter,
--- a/Framework/StoneException.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/StoneException.h	Mon Jun 24 14:35:00 2019 +0200
@@ -115,11 +115,44 @@
 
 // See https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts
 // (or google "Multiple lines macro C++ faq lite" if link is dead)
-#define ORTHANC_ASSERT(cond,streamChainMessage) \
+#define ORTHANC_ASSERT2(cond,streamChainMessage) \
     if (!(cond)) { \
       std::stringstream sst; \
       sst << "Assertion failed. Condition = \"" #cond "\" Message = \"" << streamChainMessage << "\""; \
       std::string sstr = sst.str(); \
-      throw OrthancException(ErrorCode_InternalError,sstr.c_str()); \
+      throw ::Orthanc::OrthancException(::Orthanc::ErrorCode_InternalError,sstr.c_str()); \
+    } else (void)0
+
+#define ORTHANC_ASSERT1(cond) \
+    if (!(cond)) { \
+      std::stringstream sst; \
+      sst << "Assertion failed. Condition = \"" #cond "\""; \
+      std::string sstr = sst.str(); \
+      throw ::Orthanc::OrthancException(::Orthanc::ErrorCode_InternalError,sstr.c_str()); \
     } else (void)0
 
+# define ORTHANC_EXPAND( x ) x 
+# define GET_ORTHANC_ASSERT(_1,_2,NAME,...) NAME
+# define ORTHANC_ASSERT(...) ORTHANC_EXPAND(GET_ORTHANC_ASSERT(__VA_ARGS__, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(__VA_ARGS__))
+
+/*
+Explanation:
+
+ORTHANC_ASSERT(a)
+ORTHANC_EXPAND(GET_ORTHANC_ASSERT(a, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(a))
+ORTHANC_EXPAND(ORTHANC_ASSERT1(a))
+ORTHANC_ASSERT1(a)
+
+ORTHANC_ASSERT(a,b)
+ORTHANC_EXPAND(GET_ORTHANC_ASSERT(a, b, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(a,b))
+ORTHANC_EXPAND(ORTHANC_ASSERT2(a,b))
+ORTHANC_ASSERT2(a,b)
+
+Note: ORTHANC_EXPAND is required for some older compilers (MS v100 cl.exe )
+*/
+
+
+
+
+
+
--- a/Framework/StoneInitialization.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/StoneInitialization.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -31,6 +31,10 @@
 #  include "../Applications/Sdl/SdlWindow.h"
 #endif
 
+#if ORTHANC_ENABLE_CURL == 1
+#include <Core/HttpClient.h>
+#endif
+
 namespace OrthancStone
 {
 #if ORTHANC_ENABLE_LOGGING_PLUGIN == 1
@@ -48,6 +52,10 @@
 #if ORTHANC_ENABLE_SDL == 1
     OrthancStone::SdlWindow::GlobalInitialize();
 #endif
+
+#if ORTHANC_ENABLE_CURL == 1
+    Orthanc::HttpClient::GlobalInitialize();
+#endif
   }
 
   void StoneFinalize()
@@ -56,6 +64,10 @@
     OrthancStone::SdlWindow::GlobalFinalize();
 #endif
     
+#if ORTHANC_ENABLE_CURL == 1
+    Orthanc::HttpClient::GlobalFinalize();
+#endif
+
     Orthanc::Logging::Finalize();
   }
 }
--- a/Framework/Toolbox/BaseWebService.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +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 "BaseWebService.h"
-
-#include <Core/OrthancException.h>
-#include "Framework/Messages/IObservable.h"
-#include "Platforms/Generic/IOracleCommand.h"
-#include <boost/shared_ptr.hpp>
-
-namespace Deprecated
-{
-
-
-  class BaseWebService::BaseWebServicePayload : public Orthanc::IDynamicObject
-  {
-  private:
-    std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage> >   userSuccessHandler_;
-    std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> >     userFailureHandler_;
-    std::auto_ptr< Orthanc::IDynamicObject>                                   userPayload_;
-
-  public:
-    BaseWebServicePayload(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* userSuccessHandler,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* userFailureHandler,
-                          Orthanc::IDynamicObject* userPayload) :
-      userSuccessHandler_(userSuccessHandler),
-      userFailureHandler_(userFailureHandler),
-      userPayload_(userPayload)
-    {
-    }
-
-    void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const
-    {
-      if (userSuccessHandler_.get() != NULL)
-      {
-        // recreate a success message with the user payload
-        IWebService::HttpRequestSuccessMessage successMessage(message.GetUri(),
-                                                              message.GetAnswer(),
-                                                              message.GetAnswerSize(),
-                                                              message.GetAnswerHttpHeaders(),
-                                                              userPayload_.get());
-        userSuccessHandler_->Apply(successMessage);
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-    void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const
-    {
-      if (userFailureHandler_.get() != NULL)
-      {
-        // recreate a failure message with the user payload
-        IWebService::HttpRequestErrorMessage failureMessage(message.GetUri(),
-                                                            userPayload_.get());
-
-        userFailureHandler_->Apply(failureMessage);
-      }
-    }
-
-  };
-
-
-  void BaseWebService::GetAsync(const std::string& uri,
-                                const HttpHeaders& headers,
-                                Orthanc::IDynamicObject* payload  /* takes ownership */,
-                                OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
-                                OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-                                unsigned int timeoutInSeconds)
-  {
-    if (cache_.find(uri) == cache_.end())
-    {
-      GetAsyncInternal(uri, headers,
-                       new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered
-                       new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestSuccessMessage>
-                       (*this, &BaseWebService::CacheAndNotifyHttpSuccess),
-                       new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestErrorMessage>
-                       (*this, &BaseWebService::NotifyHttpError),
-                       timeoutInSeconds);
-    }
-    else
-    {
-      // create a command and "post" it to the Oracle so it is executed and commited "later"
-      NotifyHttpSuccessLater(cache_[uri], payload, successCallback);
-    }
-
-  }
-
-
-
-  void BaseWebService::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
-  {
-    if (message.HasPayload())
-    {
-      dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleSuccess(message);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-  void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
-  {
-    cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message));
-    NotifyHttpSuccess(message);
-  }
-
-  void BaseWebService::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message)
-  {
-    if (message.HasPayload())
-    {
-      dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleFailure(message);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-
-
-}
--- a/Framework/Toolbox/BaseWebService.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +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/>.
- **/
-
-
-#pragma once
-
-#include "IWebService.h"
-
-#include <string>
-#include <map>
-
-namespace Deprecated
-{
-  // This is an intermediate of IWebService that implements some caching on
-  // the HTTP GET requests
-  class BaseWebService : public IWebService, public OrthancStone::IObserver
-  {
-  public:
-    class CachedHttpRequestSuccessMessage
-    {
-    protected:
-      std::string                    uri_;
-      void*                          answer_;
-      size_t                         answerSize_;
-      IWebService::HttpHeaders       answerHeaders_;
-
-    public:
-      CachedHttpRequestSuccessMessage(const IWebService::HttpRequestSuccessMessage& message) :
-        uri_(message.GetUri()),
-        answerSize_(message.GetAnswerSize()),
-        answerHeaders_(message.GetAnswerHttpHeaders())
-      {
-        answer_ =  malloc(answerSize_);
-        memcpy(answer_, message.GetAnswer(), answerSize_);
-      }
-
-      ~CachedHttpRequestSuccessMessage()
-      {
-        free(answer_);
-      }
-
-      const std::string& GetUri() const
-      {
-        return uri_;
-      }
-
-      const void* GetAnswer() const
-      {
-        return answer_;
-      }
-
-      size_t GetAnswerSize() const
-      {
-        return answerSize_;
-      }
-
-      const IWebService::HttpHeaders&  GetAnswerHttpHeaders() const
-      {
-        return answerHeaders_;
-      }
-
-    };
-  protected:
-    class BaseWebServicePayload;
-
-    bool          cacheEnabled_;
-    std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage> > cache_;  // TODO: this is currently an infinite cache !
-
-  public:
-
-    BaseWebService(OrthancStone::MessageBroker& broker) :
-      IWebService(broker),
-      IObserver(broker),
-      cacheEnabled_(true)
-    {
-    }
-
-    virtual ~BaseWebService()
-    {
-    }
-
-    virtual void EnableCache(bool enable)
-    {
-      cacheEnabled_ = enable;
-    }
-
-    virtual void GetAsync(const std::string& uri,
-                          const HttpHeaders& headers,
-                          Orthanc::IDynamicObject* payload  /* takes ownership */,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                          unsigned int timeoutInSeconds = 60);
-
-  protected:
-    virtual void GetAsyncInternal(const std::string& uri,
-                          const HttpHeaders& headers,
-                          Orthanc::IDynamicObject* payload  /* takes ownership */,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                          unsigned int timeoutInSeconds = 60) = 0;
-
-    virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage,
-                                        Orthanc::IDynamicObject* payload, // takes ownership
-                                        OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback) = 0;
-
-  private:
-    void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
-
-    void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message);
-
-    void CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
-
-  };
-}
--- a/Framework/Toolbox/CoordinateSystem3D.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/CoordinateSystem3D.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -143,6 +143,19 @@
   }
 
 
+  void CoordinateSystem3D::SetOrigin(const Vector& origin)
+  {
+    if (origin.size() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      origin_ = origin;
+    }
+  }
+
+
   Vector CoordinateSystem3D::MapSliceToWorldCoordinates(double x,
                                                         double y) const
   {
@@ -189,9 +202,9 @@
   }
 
 
-  bool CoordinateSystem3D::GetDistance(double& distance,
-                                       const CoordinateSystem3D& a,
-                                       const CoordinateSystem3D& b)
+  bool CoordinateSystem3D::ComputeDistance(double& distance,
+                                           const CoordinateSystem3D& a,
+                                           const CoordinateSystem3D& b)
   {
     bool opposite;   // Ignored
 
--- a/Framework/Toolbox/CoordinateSystem3D.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/CoordinateSystem3D.h	Mon Jun 24 14:35:00 2019 +0200
@@ -86,6 +86,8 @@
       return axisY_;
     }
 
+    void SetOrigin(const Vector& origin);
+
     Vector MapSliceToWorldCoordinates(double x,
                                       double y) const;
     
@@ -104,8 +106,8 @@
                        const Vector& direction) const;
 
     // Returns "false" is the two planes are not parallel
-    static bool GetDistance(double& distance,
-                            const CoordinateSystem3D& a,
-                            const CoordinateSystem3D& b);
+    static bool ComputeDistance(double& distance,
+                                const CoordinateSystem3D& a,
+                                const CoordinateSystem3D& b);
   };
 }
--- a/Framework/Toolbox/DicomFrameConverter.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,282 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomFrameConverter.h"
-
-#include "LinearAlgebra.h"
-
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-
-namespace Deprecated
-{
-  static const Orthanc::DicomTag IMAGE_TAGS[] =
-  {
-    Orthanc::DICOM_TAG_BITS_STORED,
-    Orthanc::DICOM_TAG_DOSE_GRID_SCALING,
-    Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION,
-    Orthanc::DICOM_TAG_PIXEL_REPRESENTATION,
-    Orthanc::DICOM_TAG_RESCALE_INTERCEPT,
-    Orthanc::DICOM_TAG_RESCALE_SLOPE,
-    Orthanc::DICOM_TAG_WINDOW_CENTER,
-    Orthanc::DICOM_TAG_WINDOW_WIDTH
-  };
-
-  
-  void DicomFrameConverter::SetDefaultParameters()
-  {
-    isSigned_ = true;
-    isColor_ = false;
-    hasRescale_ = false;
-    rescaleIntercept_ = 0;
-    rescaleSlope_ = 1;
-    hasDefaultWindow_ = false;
-    defaultWindowCenter_ = 128;
-    defaultWindowWidth_ = 256;
-    expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-  }
-
-
-  void DicomFrameConverter::ReadParameters(const Orthanc::DicomMap& dicom)
-  {
-    SetDefaultParameters();
-
-    OrthancStone::Vector c, w;
-    if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-        OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
-        c.size() > 0 && 
-        w.size() > 0)
-    {
-      hasDefaultWindow_ = true;
-      defaultWindowCenter_ = static_cast<float>(c[0]);
-      defaultWindowWidth_ = static_cast<float>(w[0]);
-    }
-
-    int32_t tmp;
-    if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION))
-    {
-      // Type 1 tag, must be present
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    isSigned_ = (tmp == 1);
-
-    double doseGridScaling;
-    bool isRTDose = false;
-    
-    if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
-        dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
-    {
-      hasRescale_ = true;
-    }
-    else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
-    {
-      // This is for RT-DOSE
-      hasRescale_ = true;
-      isRTDose = true;
-      rescaleIntercept_ = 0;
-      rescaleSlope_ = doseGridScaling;
-
-      if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_BITS_STORED))
-      {
-        // Type 1 tag, must be present
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      switch (tmp)
-      {
-        case 16:
-          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-          break;
-
-        case 32:
-          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-    }
-
-    std::string photometric;
-    if (dicom.CopyToString(photometric, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, false))
-    {
-      photometric = Orthanc::Toolbox::StripSpaces(photometric);
-    }
-    else
-    {
-      // Type 1 tag, must be present
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    photometric_ = Orthanc::StringToPhotometricInterpretation(photometric.c_str());
-    
-    isColor_ = (photometric != "MONOCHROME1" &&
-                photometric != "MONOCHROME2");
-
-    // TODO Add more checks, e.g. on the number of bytes per value
-    // (cf. DicomImageInformation.h in Orthanc)
-
-    if (!isRTDose)
-    {
-      if (isColor_)
-      {
-        expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
-      }
-      else if (isSigned_)
-      {
-        expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
-      }
-      else
-      {
-        expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-      }
-    }
-  }
-
-  
-  void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom)
-  {
-    Orthanc::DicomMap converted;
-
-    for (size_t i = 0; i < sizeof(IMAGE_TAGS) / sizeof(Orthanc::DicomTag); i++)
-    {
-      OrthancPlugins::DicomTag tag(IMAGE_TAGS[i].GetGroup(), IMAGE_TAGS[i].GetElement());
-    
-      std::string value;
-      if (dicom.GetStringValue(value, tag))
-      {
-        converted.SetValue(IMAGE_TAGS[i], value, false);
-      }
-    }
-
-    ReadParameters(converted);
-  }
-    
-
-  void DicomFrameConverter::ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const
-  {
-    assert(sizeof(float) == 4);
-
-    if (source.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    if (source->GetFormat() == GetExpectedPixelFormat() &&
-        source->GetFormat() == Orthanc::PixelFormat_RGB24)
-    {
-      // No conversion has to be done, check out (*)
-      return;
-    }
-    else
-    {
-      source.reset(ConvertFrame(*source));
-    }
-  }
-
-
-  Orthanc::ImageAccessor* DicomFrameConverter::ConvertFrame(const Orthanc::ImageAccessor& source) const
-  {
-    assert(sizeof(float) == 4);
-
-    Orthanc::PixelFormat sourceFormat = source.GetFormat();
-
-    if (sourceFormat != GetExpectedPixelFormat())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-
-    if (sourceFormat == Orthanc::PixelFormat_RGB24)
-    {
-      // This is the case of a color image. No conversion has to be done (*)
-      std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_RGB24, 
-                                                                 source.GetWidth(), 
-                                                                 source.GetHeight(),
-                                                                 false));
-      Orthanc::ImageProcessing::Copy(*converted, source);
-      return converted.release();
-    }
-    else
-    {
-      assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 ||
-             sourceFormat == Orthanc::PixelFormat_Grayscale32 ||
-             sourceFormat == Orthanc::PixelFormat_SignedGrayscale16);
-
-      // This is the case of a grayscale frame. Convert it to Float32.
-      std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
-                                                                 source.GetWidth(), 
-                                                                 source.GetHeight(),
-                                                                 false));
-      Orthanc::ImageProcessing::Convert(*converted, source);
-
-      // Correct rescale slope/intercept if need be
-      ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32);
-      
-      return converted.release();
-    }
-  }
-
-
-  void DicomFrameConverter::ApplyRescale(Orthanc::ImageAccessor& image,
-                                         bool useDouble) const
-  {
-    if (image.GetFormat() != Orthanc::PixelFormat_Float32)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }
-    
-    if (hasRescale_)
-    {
-      for (unsigned int y = 0; y < image.GetHeight(); y++)
-      {
-        float* p = reinterpret_cast<float*>(image.GetRow(y));
-
-        if (useDouble)
-        {
-          // Slower, accurate implementation using double
-          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
-          {
-            double value = static_cast<double>(*p);
-            *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
-          }
-        }
-        else
-        {
-          // Fast, approximate implementation using float
-          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
-          {
-            *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
-          }
-        }
-      }
-    }
-  }
-
-  
-  double DicomFrameConverter::Apply(double x) const
-  {
-    return x * rescaleSlope_ + rescaleIntercept_;
-  }
-
-}
--- a/Framework/Toolbox/DicomFrameConverter.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <Plugins/Samples/Common/IDicomDataset.h>
-#include <Core/DicomFormat/DicomMap.h>
-#include <Core/Images/ImageAccessor.h>
-
-#include <memory>
-
-namespace Deprecated
-{
-  /**
-   * This class is responsible for converting the pixel format of a
-   * DICOM frame coming from Orthanc, into a pixel format that is
-   * suitable for Stone, given the relevant DICOM tags:
-   * - Color frames will stay in the RGB24 format.
-   * - Grayscale frames will be converted to the Float32 format.
-   **/
-  class DicomFrameConverter
-  {
-  private:
-    bool    isSigned_;
-    bool    isColor_;
-    bool    hasRescale_;
-    double  rescaleIntercept_;
-    double  rescaleSlope_;
-    bool    hasDefaultWindow_;
-    double  defaultWindowCenter_;
-    double  defaultWindowWidth_;
-    
-    Orthanc::PhotometricInterpretation  photometric_;
-    Orthanc::PixelFormat                expectedPixelFormat_;
-
-    void SetDefaultParameters();
-
-  public:
-    DicomFrameConverter()
-    {
-      SetDefaultParameters();
-    }
-
-    ~DicomFrameConverter()
-    {
-      // TODO: check whether this dtor is called or not
-      // An MSVC warning explains that declaring an
-      // std::auto_ptr with a forward-declared type
-      // prevents its dtor from being called. Does not
-      // seem an issue here (only POD types inside), but
-      // definitely something to keep an eye on.
-      (void)0;
-    }
-
-    // AM: this is required to serialize/deserialize it
-    DicomFrameConverter(
-        bool isSigned,
-        bool isColor,
-        bool hasRescale,
-        double rescaleIntercept,
-        double rescaleSlope,
-        bool hasDefaultWindow,
-        double defaultWindowCenter,
-        double defaultWindowWidth,
-        Orthanc::PhotometricInterpretation photometric,
-        Orthanc::PixelFormat expectedPixelFormat
-        ):
-      isSigned_(isSigned),
-      isColor_(isColor),
-      hasRescale_(hasRescale),
-      rescaleIntercept_(rescaleIntercept),
-      rescaleSlope_(rescaleSlope),
-      hasDefaultWindow_(hasDefaultWindow),
-      defaultWindowCenter_(defaultWindowCenter),
-      defaultWindowWidth_(defaultWindowWidth),
-      photometric_(photometric),
-      expectedPixelFormat_(expectedPixelFormat)
-    {}
-
-    void GetParameters(bool& isSigned,
-                       bool& isColor,
-                       bool& hasRescale,
-                       double& rescaleIntercept,
-                       double& rescaleSlope,
-                       bool& hasDefaultWindow,
-                       double& defaultWindowCenter,
-                       double& defaultWindowWidth,
-                       Orthanc::PhotometricInterpretation& photometric,
-                       Orthanc::PixelFormat& expectedPixelFormat) const
-    {
-      isSigned = isSigned_;
-      isColor = isColor_;
-      hasRescale = hasRescale_;
-      rescaleIntercept = rescaleIntercept_;
-      rescaleSlope = rescaleSlope_;
-      hasDefaultWindow = hasDefaultWindow_;
-      defaultWindowCenter = defaultWindowCenter_;
-      defaultWindowWidth = defaultWindowWidth_;
-      photometric = photometric_;
-      expectedPixelFormat = expectedPixelFormat_;
-    }
-
-    Orthanc::PixelFormat GetExpectedPixelFormat() const
-    {
-      return expectedPixelFormat_;
-    }
-
-    Orthanc::PhotometricInterpretation GetPhotometricInterpretation() const
-    {
-      return photometric_;
-    }
-
-    void ReadParameters(const Orthanc::DicomMap& dicom);
-
-    void ReadParameters(const OrthancPlugins::IDicomDataset& dicom);
-
-    bool HasDefaultWindow() const
-    {
-      return hasDefaultWindow_;
-    }
-    
-    double GetDefaultWindowCenter() const
-    {
-      return defaultWindowCenter_;
-    }
-    
-    double GetDefaultWindowWidth() const
-    {
-      return defaultWindowWidth_;
-    }
-
-    double GetRescaleIntercept() const
-    {
-      return rescaleIntercept_;
-    }
-
-    double GetRescaleSlope() const
-    {
-      return rescaleSlope_;
-    }
-
-    void ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const;
-
-    Orthanc::ImageAccessor* ConvertFrame(const Orthanc::ImageAccessor& source) const;
-
-    void ApplyRescale(Orthanc::ImageAccessor& image,
-                      bool useDouble) const;
-
-    double Apply(double x) const;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomInstanceParameters.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,397 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomInstanceParameters.h"
+
+#include "../Scene2D/ColorTextureSceneLayer.h"
+#include "../Scene2D/FloatTextureSceneLayer.h"
+#include "../Toolbox/GeometryToolbox.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+
+namespace OrthancStone
+{
+  void DicomInstanceParameters::Data::ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
+  {
+    // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
+
+    {
+      std::string increment;
+
+      if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
+      {
+        Orthanc::Toolbox::ToUpperCase(increment);
+        if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
+        {
+          LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
+          return;
+        }
+      }
+    }
+
+    if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
+        frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
+    {
+      LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
+      frameOffsets_.clear();
+    }
+    else
+    {
+      if (frameOffsets_.size() >= 2)
+      {
+        thickness_ = std::abs(frameOffsets_[1] - frameOffsets_[0]);
+      }
+    }
+  }
+
+
+  DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) :
+    imageInformation_(dicom)
+  {
+    if (imageInformation_.GetNumberOfFrames() <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
+        !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
+        !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+        
+    std::string s;
+    if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      sopClassUid_ = StringToSopClassUid(s);
+    }
+
+    if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
+    {
+      thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
+    }
+
+    GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
+
+    std::string position, orientation;
+    if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
+        dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
+    {
+      geometry_ = CoordinateSystem3D(position, orientation);
+    }
+
+    if (sopClassUid_ == SopClassUid_RTDose)
+    {
+      ComputeDoseOffsets(dicom);
+    }
+
+    isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
+                imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
+
+    double doseGridScaling;
+
+    if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
+        dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
+    {
+      hasRescale_ = true;
+    }
+    else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
+    {
+      hasRescale_ = true;
+      rescaleIntercept_ = 0;
+      rescaleSlope_ = doseGridScaling;
+    }
+    else
+    {
+      hasRescale_ = false;
+    }
+
+    Vector c, w;
+    if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+        LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
+        c.size() > 0 && 
+        w.size() > 0)
+    {
+      hasDefaultWindowing_ = true;
+      defaultWindowingCenter_ = static_cast<float>(c[0]);
+      defaultWindowingWidth_ = static_cast<float>(w[0]);
+    }
+    else
+    {
+      hasDefaultWindowing_ = false;
+    }
+
+    if (sopClassUid_ == SopClassUid_RTDose)
+    {
+      switch (imageInformation_.GetBitsStored())
+      {
+        case 16:
+          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+          break;
+
+        case 32:
+          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      } 
+    }
+    else if (isColor_)
+    {
+      expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
+    }
+    else if (imageInformation_.IsSigned())
+    {
+      expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
+    }
+    else
+    {
+      expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+    }
+  }
+
+
+  CoordinateSystem3D  DicomInstanceParameters::Data::GetFrameGeometry(unsigned int frame) const
+  {
+    if (frame == 0)
+    {
+      return geometry_;
+    }
+    else if (frame >= imageInformation_.GetNumberOfFrames())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else if (sopClassUid_ == SopClassUid_RTDose)
+    {
+      if (frame >= frameOffsets_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      return CoordinateSystem3D(
+        geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
+        geometry_.GetAxisX(),
+        geometry_.GetAxisY());
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+  bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame,
+                                                         const CoordinateSystem3D& plane) const
+  {
+    if (frame >= imageInformation_.GetNumberOfFrames())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    CoordinateSystem3D tmp = geometry_;
+
+    if (frame != 0)
+    {
+      tmp = GetFrameGeometry(frame);
+    }
+
+    double distance;
+
+    return (CoordinateSystem3D::ComputeDistance(distance, tmp, plane) &&
+            distance <= thickness_ / 2.0);
+  }
+
+      
+  void DicomInstanceParameters::Data::ApplyRescale(Orthanc::ImageAccessor& image,
+                                                   bool useDouble) const
+  {
+    if (image.GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+    
+    if (hasRescale_)
+    {
+      const unsigned int width = image.GetWidth();
+      const unsigned int height = image.GetHeight();
+        
+      for (unsigned int y = 0; y < height; y++)
+      {
+        float* p = reinterpret_cast<float*>(image.GetRow(y));
+
+        if (useDouble)
+        {
+          // Slower, accurate implementation using double
+          for (unsigned int x = 0; x < width; x++, p++)
+          {
+            double value = static_cast<double>(*p);
+            *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
+          }
+        }
+        else
+        {
+          // Fast, approximate implementation using float
+          for (unsigned int x = 0; x < width; x++, p++)
+          {
+            *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
+          }
+        }
+      }
+    }
+  }
+
+  double DicomInstanceParameters::GetRescaleIntercept() const
+  {
+    if (data_.hasRescale_)
+    {
+      return data_.rescaleIntercept_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  double DicomInstanceParameters::GetRescaleSlope() const
+  {
+    if (data_.hasRescale_)
+    {
+      return data_.rescaleSlope_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  float DicomInstanceParameters::GetDefaultWindowingCenter() const
+  {
+    if (data_.hasDefaultWindowing_)
+    {
+      return data_.defaultWindowingCenter_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  float DicomInstanceParameters::GetDefaultWindowingWidth() const
+  {
+    if (data_.hasDefaultWindowing_)
+    {
+      return data_.defaultWindowingWidth_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  Orthanc::ImageAccessor* DicomInstanceParameters::ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const
+  {
+    std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                               pixelData.GetWidth(), 
+                                                               pixelData.GetHeight(),
+                                                               false));
+    Orthanc::ImageProcessing::Convert(*converted, pixelData);
+
+    // Correct rescale slope/intercept if need be
+    data_.ApplyRescale(*converted, (pixelData.GetFormat() == Orthanc::PixelFormat_Grayscale32));
+
+    return converted.release();
+  }
+    
+
+
+  TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture
+  (const Orthanc::ImageAccessor& pixelData) const
+  {
+    assert(sizeof(float) == 4);
+
+    Orthanc::PixelFormat sourceFormat = pixelData.GetFormat();
+
+    if (sourceFormat != GetExpectedPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (sourceFormat == Orthanc::PixelFormat_RGB24)
+    {
+      // This is the case of a color image. No conversion has to be done.
+      return new ColorTextureSceneLayer(pixelData);
+    }
+    else
+    {
+      // This is the case of a grayscale frame. Convert it to Float32.
+      std::auto_ptr<FloatTextureSceneLayer> texture;
+
+      if (pixelData.GetFormat() == Orthanc::PixelFormat_Float32)
+      {
+        texture.reset(new FloatTextureSceneLayer(pixelData));
+      }
+      else
+      {
+        std::auto_ptr<Orthanc::ImageAccessor> converted(ConvertToFloat(pixelData));
+        texture.reset(new FloatTextureSceneLayer(*converted));
+      }
+
+      if (data_.hasDefaultWindowing_)
+      {
+        texture->SetCustomWindowing(data_.defaultWindowingCenter_,
+                                    data_.defaultWindowingWidth_);
+      }
+        
+      return texture.release();
+    }
+  }
+
+
+  LookupTableTextureSceneLayer* DicomInstanceParameters::CreateLookupTableTexture
+  (const Orthanc::ImageAccessor& pixelData) const
+  {
+    std::auto_ptr<FloatTextureSceneLayer> texture;
+
+    if (pixelData.GetFormat() == Orthanc::PixelFormat_Float32)
+    {
+      return new LookupTableTextureSceneLayer(pixelData);
+    }
+    else
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> converted(ConvertToFloat(pixelData));
+      return new LookupTableTextureSceneLayer(*converted);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomInstanceParameters.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,195 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../StoneEnumerations.h"
+#include "../Scene2D/LookupTableTextureSceneLayer.h"
+#include "../Toolbox/CoordinateSystem3D.h"
+
+#include <Core/IDynamicObject.h>
+#include <Core/DicomFormat/DicomImageInformation.h>
+
+namespace OrthancStone
+{
+  class DicomInstanceParameters :
+    public Orthanc::IDynamicObject  /* to be used as a payload to SlicesSorter */
+  {
+    // This class supersedes the deprecated "DicomFrameConverter"
+
+  private:
+    struct Data   // Struct to ease the copy constructor
+    {
+      std::string                       orthancInstanceId_;
+      std::string                       studyInstanceUid_;
+      std::string                       seriesInstanceUid_;
+      std::string                       sopInstanceUid_;
+      Orthanc::DicomImageInformation    imageInformation_;
+      SopClassUid                       sopClassUid_;
+      double                            thickness_;
+      double                            pixelSpacingX_;
+      double                            pixelSpacingY_;
+      CoordinateSystem3D                geometry_;
+      Vector                            frameOffsets_;
+      bool                              isColor_;
+      bool                              hasRescale_;
+      double                            rescaleIntercept_;
+      double                            rescaleSlope_;
+      bool                              hasDefaultWindowing_;
+      float                             defaultWindowingCenter_;
+      float                             defaultWindowingWidth_;
+      Orthanc::PixelFormat              expectedPixelFormat_;
+
+      void ComputeDoseOffsets(const Orthanc::DicomMap& dicom);
+
+      Data(const Orthanc::DicomMap& dicom);
+
+      CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const;
+
+      bool IsPlaneWithinSlice(unsigned int frame,
+                              const CoordinateSystem3D& plane) const;
+      
+      void ApplyRescale(Orthanc::ImageAccessor& image,
+                        bool useDouble) const;
+    };
+
+    
+    Orthanc::ImageAccessor* ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const;
+    
+
+    Data  data_;
+
+
+  public:
+    DicomInstanceParameters(const DicomInstanceParameters& other) :
+    data_(other.data_)
+    {
+    }
+
+    DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
+      data_(dicom)
+    {
+    }
+
+    DicomInstanceParameters* Clone() const
+    {
+      return new DicomInstanceParameters(*this);
+    }
+
+    void SetOrthancInstanceIdentifier(const std::string& id)
+    {
+      data_.orthancInstanceId_ = id;
+    }
+
+    const std::string& GetOrthancInstanceIdentifier() const
+    {
+      return data_.orthancInstanceId_;
+    }
+
+    const Orthanc::DicomImageInformation& GetImageInformation() const
+    {
+      return data_.imageInformation_;
+    }
+
+    const std::string& GetStudyInstanceUid() const
+    {
+      return data_.studyInstanceUid_;
+    }
+
+    const std::string& GetSeriesInstanceUid() const
+    {
+      return data_.seriesInstanceUid_;
+    }
+
+    const std::string& GetSopInstanceUid() const
+    {
+      return data_.sopInstanceUid_;
+    }
+
+    SopClassUid GetSopClassUid() const
+    {
+      return data_.sopClassUid_;
+    }
+
+    double GetThickness() const
+    {
+      return data_.thickness_;
+    }
+
+    double GetPixelSpacingX() const
+    {
+      return data_.pixelSpacingX_;
+    }
+
+    double GetPixelSpacingY() const
+    {
+      return data_.pixelSpacingY_;
+    }
+
+    const CoordinateSystem3D&  GetGeometry() const
+    {
+      return data_.geometry_;
+    }
+
+    CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
+    {
+      return data_.GetFrameGeometry(frame);
+    }
+
+    bool IsPlaneWithinSlice(unsigned int frame,
+                            const CoordinateSystem3D& plane) const
+    {
+      return data_.IsPlaneWithinSlice(frame, plane);
+    }
+
+    bool IsColor() const
+    {
+      return data_.isColor_;
+    }
+
+    bool HasRescale() const
+    {
+      return data_.hasRescale_;
+    }
+
+    double GetRescaleIntercept() const;
+
+    double GetRescaleSlope() const;
+
+    bool HasDefaultWindowing() const
+    {
+      return data_.hasDefaultWindowing_;
+    }
+
+    float GetDefaultWindowingCenter() const;
+
+    float GetDefaultWindowingWidth() const;
+
+    Orthanc::PixelFormat GetExpectedPixelFormat() const
+    {
+      return data_.expectedPixelFormat_;
+    }
+
+    TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& pixelData) const;
+
+    LookupTableTextureSceneLayer* CreateLookupTableTexture(const Orthanc::ImageAccessor& pixelData) const;
+  };
+}
--- a/Framework/Toolbox/DicomStructureSet.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,6 @@
 #include "DicomStructureSet.h"
 
 #include "../Toolbox/GeometryToolbox.h"
-#include "../Toolbox/MessagingToolbox.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -31,7 +30,6 @@
 
 #include <limits>
 #include <stdio.h>
-#include <boost/lexical_cast.hpp>
 #include <boost/geometry.hpp>
 #include <boost/geometry/geometries/point_xy.hpp>
 #include <boost/geometry/geometries/polygon.hpp>
@@ -367,9 +365,7 @@
 
   DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags)
   {
-    using namespace OrthancPlugins;
-
-    DicomDatasetReader reader(tags);
+    OrthancPlugins::DicomDatasetReader reader(tags);
     
     size_t count, tmp;
     if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) ||
@@ -385,18 +381,18 @@
     for (size_t i = 0; i < count; i++)
     {
       structures_[i].interpretation_ = reader.GetStringValue
-        (DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
-                   DICOM_TAG_RT_ROI_INTERPRETED_TYPE),
+        (OrthancPlugins::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
+                                   DICOM_TAG_RT_ROI_INTERPRETED_TYPE),
          "No interpretation");
 
       structures_[i].name_ = reader.GetStringValue
-        (DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
-                   DICOM_TAG_ROI_NAME),
+        (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
+                                   DICOM_TAG_ROI_NAME),
          "No interpretation");
 
       Vector color;
-      if (ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                             DICOM_TAG_ROI_DISPLAY_COLOR)) &&
+      if (ParseVector(color, tags, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                             DICOM_TAG_ROI_DISPLAY_COLOR)) &&
           color.size() == 3)
       {
         structures_[i].red_ = ConvertColor(color[0]);
@@ -411,37 +407,55 @@
       }
 
       size_t countSlices;
-      if (!tags.GetSequenceSize(countSlices, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                       DICOM_TAG_CONTOUR_SEQUENCE)))
+      if (!tags.GetSequenceSize(countSlices, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                                       DICOM_TAG_CONTOUR_SEQUENCE)))
       {
         countSlices = 0;
       }
 
-      LOG(WARNING) << "New RT structure: \"" << structures_[i].name_ 
+      LOG(INFO) << "New RT structure: \"" << structures_[i].name_ 
                    << "\" with interpretation \"" << structures_[i].interpretation_
                    << "\" containing " << countSlices << " slices (color: " 
                    << static_cast<int>(structures_[i].red_) << "," 
                    << static_cast<int>(structures_[i].green_) << ","
                    << static_cast<int>(structures_[i].blue_) << ")";
 
+      // These temporary variables avoid allocating many vectors in the loop below
+      OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                DICOM_TAG_CONTOUR_SEQUENCE, 0,
+                                                DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
+
+      OrthancPlugins::DicomPath geometricTypePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                  DICOM_TAG_CONTOUR_SEQUENCE, 0,
+                                                  DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
+      
+      OrthancPlugins::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                  DICOM_TAG_CONTOUR_SEQUENCE, 0,
+                                                  DICOM_TAG_CONTOUR_IMAGE_SEQUENCE);
+
+      OrthancPlugins::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                       DICOM_TAG_CONTOUR_SEQUENCE, 0,
+                                                       DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
+                                                       DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
+
+      OrthancPlugins::DicomPath contourDataPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                DICOM_TAG_CONTOUR_SEQUENCE, 0,
+                                                DICOM_TAG_CONTOUR_DATA);
+
       for (size_t j = 0; j < countSlices; j++)
       {
         unsigned int countPoints;
 
-        if (!reader.GetUnsignedIntegerValue
-            (countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                    DICOM_TAG_CONTOUR_SEQUENCE, j,
-                                    DICOM_TAG_NUMBER_OF_CONTOUR_POINTS)))
+        countPointsPath.SetPrefixIndex(1, j);
+        if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath))
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
         }
             
         //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices";
 
-        std::string type = reader.GetMandatoryStringValue
-          (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                     DICOM_TAG_CONTOUR_SEQUENCE, j,
-                     DICOM_TAG_CONTOUR_GEOMETRIC_TYPE));
+        geometricTypePath.SetPrefixIndex(1, j);
+        std::string type = reader.GetMandatoryStringValue(geometricTypePath);
         if (type != "CLOSED_PLANAR")
         {
           LOG(WARNING) << "Ignoring contour with geometry type: " << type;
@@ -449,24 +463,19 @@
         }
 
         size_t size;
-        if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                  DICOM_TAG_CONTOUR_SEQUENCE, j,
-                                                  DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) ||
+
+        imageSequencePath.SetPrefixIndex(1, j);
+        if (!tags.GetSequenceSize(size, imageSequencePath) ||
             size != 1)
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
         }
 
-        std::string sopInstanceUid = reader.GetMandatoryStringValue
-          (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                     DICOM_TAG_CONTOUR_SEQUENCE, j,
-                     DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
-                     DICOM_TAG_REFERENCED_SOP_INSTANCE_UID));
-        
-        std::string slicesData = reader.GetMandatoryStringValue
-          (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                     DICOM_TAG_CONTOUR_SEQUENCE, j,
-                     DICOM_TAG_CONTOUR_DATA));
+        referencedInstancePath.SetPrefixIndex(1, j);
+        std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
+
+        contourDataPath.SetPrefixIndex(1, j);        
+        std::string slicesData = reader.GetMandatoryStringValue(contourDataPath);
 
         Vector points;
         if (!LinearAlgebra::ParseVector(points, slicesData) ||
@@ -531,6 +540,13 @@
   }
 
 
+  Color DicomStructureSet::GetStructureColor(size_t index) const
+  {
+    const Structure& s = GetStructure(index);
+    return Color(s.red_, s.green_, s.blue_);
+  }
+  
+    
   void DicomStructureSet::GetStructureColor(uint8_t& red,
                                             uint8_t& green,
                                             uint8_t& blue,
@@ -668,50 +684,9 @@
   }
 
   
-  DicomStructureSet* DicomStructureSet::SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc,
-                                                        const std::string& instanceId)
-  {
-    const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
-    OrthancPlugins::FullOrthancDataset dataset(orthanc, uri);
-
-    std::auto_ptr<DicomStructureSet> result(new DicomStructureSet(dataset));
-
-    std::set<std::string> instances;
-    result->GetReferencedInstances(instances);
-
-    for (std::set<std::string>::const_iterator it = instances.begin();
-         it != instances.end(); ++it)
-    {
-      Json::Value lookup;
-      MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it);
-
-      if (lookup.type() != Json::arrayValue ||
-          lookup.size() != 1 ||
-          !lookup[0].isMember("Type") ||
-          !lookup[0].isMember("Path") ||
-          lookup[0]["Type"].type() != Json::stringValue ||
-          lookup[0]["ID"].type() != Json::stringValue ||
-          lookup[0]["Type"].asString() != "Instance")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
-      }
-
-      OrthancPlugins::FullOrthancDataset slice
-        (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags");
-      Orthanc::DicomMap m;
-      MessagingToolbox::ConvertDataset(m, slice);
-      result->AddReferencedSlice(m);
-    }
-
-    result->CheckReferencedSlices();
-
-    return result.release();
-  }
-
-
   bool DicomStructureSet::ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
-                                           Structure& structure,
-                                           const CoordinateSystem3D& slice)
+                                           const Structure& structure,
+                                           const CoordinateSystem3D& slice) const
   {
     polygons.clear();
 
@@ -722,7 +697,7 @@
     {
       // This is an axial projection
 
-      for (Polygons::iterator polygon = structure.polygons_.begin();
+      for (Polygons::const_iterator polygon = structure.polygons_.begin();
            polygon != structure.polygons_.end(); ++polygon)
       {
         if (polygon->IsOnSlice(slice))
@@ -748,7 +723,7 @@
       // Sagittal or coronal projection
       std::vector<BoostPolygon> projected;
   
-      for (Polygons::iterator polygon = structure.polygons_.begin();
+      for (Polygons::const_iterator polygon = structure.polygons_.begin();
            polygon != structure.polygons_.end(); ++polygon)
       {
         double x1, y1, x2, y2;
--- a/Framework/Toolbox/DicomStructureSet.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,6 +23,7 @@
 
 #include "CoordinateSystem3D.h"
 #include "Extent2D.h"
+#include "../Scene2D/Color.h"
 
 #include <Plugins/Samples/Common/FullOrthancDataset.h>
 
@@ -135,13 +136,13 @@
     Structure& GetStructure(size_t index);
   
     bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
-                          Structure& structure,
-                          const CoordinateSystem3D& slice);
+                          const Structure& structure,
+                          const CoordinateSystem3D& slice) const;
 
   public:
     DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance);
 
-    size_t GetStructureCount() const
+    size_t GetStructuresCount() const
     {
       return structures_.size();
     }
@@ -152,6 +153,9 @@
 
     const std::string& GetStructureInterpretation(size_t index) const;
 
+    Color GetStructureColor(size_t index) const;
+
+    // TODO - remove
     void GetStructureColor(uint8_t& red,
                            uint8_t& green,
                            uint8_t& blue,
@@ -170,12 +174,9 @@
 
     Vector GetNormal() const;
 
-    static DicomStructureSet* SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc,
-                                              const std::string& instanceId);
-
     bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
                           size_t index,
-                          const CoordinateSystem3D& slice)
+                          const CoordinateSystem3D& slice) const
     {
       return ProjectStructure(polygons, GetStructure(index), slice);
     }
--- a/Framework/Toolbox/DownloadStack.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DownloadStack.h"
-
-#include <Core/OrthancException.h>
-
-#include <cassert>
-
-namespace Deprecated
-{
-  bool DownloadStack::CheckInvariants() const
-  {
-    std::vector<bool> dequeued(nodes_.size(), true);
-
-    int i = firstNode_;
-    while (i != NIL)
-    {
-      const Node& node = nodes_[i];
-
-      dequeued[i] = false;
-
-      if (node.next_ != NIL &&
-          nodes_[node.next_].prev_ != i)
-      {
-        return false;
-      }
-
-      if (node.prev_ != NIL &&
-          nodes_[node.prev_].next_ != i)
-      {
-        return false;
-      }
-
-      i = nodes_[i].next_;
-    }
-
-    for (size_t i = 0; i < nodes_.size(); i++)
-    {
-      if (nodes_[i].dequeued_ != dequeued[i])
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  DownloadStack::DownloadStack(unsigned int size)
-  {
-    nodes_.resize(size);
-
-    if (size == 0)
-    {
-      firstNode_ = NIL;
-    }
-    else
-    {
-      for (size_t i = 0; i < size; i++)
-      {
-        nodes_[i].prev_ = static_cast<int>(i - 1);
-        nodes_[i].next_ = static_cast<int>(i + 1);
-        nodes_[i].dequeued_ = false;
-      }
-
-      nodes_.front().prev_ = NIL;
-      nodes_.back().next_ = NIL;
-      firstNode_ = 0;
-    }
-
-    assert(CheckInvariants());
-  }
-
-
-  DownloadStack::~DownloadStack()
-  {
-    assert(CheckInvariants());    
-  }
-
-
-  bool DownloadStack::Pop(unsigned int& value)
-  {
-    assert(CheckInvariants());
-
-    if (firstNode_ == NIL)
-    {
-      for (size_t i = 0; i < nodes_.size(); i++)
-      {
-        assert(nodes_[i].dequeued_);
-      }
-
-      return false;
-    }
-    else
-    {
-      assert(firstNode_ >= 0 && firstNode_ < static_cast<int>(nodes_.size()));
-      value = firstNode_;
-
-      Node& node = nodes_[firstNode_];
-      assert(node.prev_ == NIL);
-      assert(!node.dequeued_);
-
-      node.dequeued_ = true;
-      firstNode_ = node.next_;
-
-      if (firstNode_ != NIL)
-      {
-        nodes_[firstNode_].prev_ = NIL;
-      }
-
-      return true;
-    }
-  }
-
-
-  void DownloadStack::SetTopNodeInternal(unsigned int value)
-  {
-    assert(CheckInvariants());
-
-    Node& node = nodes_[value];
-
-    if (node.dequeued_)
-    {
-      // This node has already been processed by the download thread, nothing to do
-      return;
-    }
-
-    // Remove the node from the list
-    if (node.prev_ == NIL)
-    {
-      assert(firstNode_ == static_cast<int>(value));
-      
-      // This is already the top node in the list, nothing to do
-      return;
-    }
-
-    nodes_[node.prev_].next_ = node.next_;
-
-    if (node.next_ != NIL)
-    {
-      nodes_[node.next_].prev_ = node.prev_;
-    }
-
-    // Add back the node at the top of the list
-    assert(firstNode_ != NIL);
-
-    Node& old = nodes_[firstNode_];
-    assert(old.prev_ == NIL);
-    assert(!old.dequeued_);
-    node.prev_ = NIL;
-    node.next_ = firstNode_;
-    old.prev_ = value;
-
-    firstNode_ = value;
-  }
-
-  
-  void DownloadStack::SetTopNode(unsigned int value)
-  {
-    if (value >= nodes_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    SetTopNodeInternal(value);
-  }
-
-
-  void DownloadStack::SetTopNodePermissive(int value)
-  {
-    if (value >= 0 &&
-        value < static_cast<int>(nodes_.size()))
-    {
-      SetTopNodeInternal(value);
-    }
-  }
-}
--- a/Framework/Toolbox/DownloadStack.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <vector>
-#include <boost/noncopyable.hpp>
-
-namespace Deprecated
-{
-  class DownloadStack : public boost::noncopyable
-  {
-  private:
-    static const int NIL = -1;
-
-    // This is a doubly-linked list
-    struct Node
-    {
-      int   next_;
-      int   prev_;
-      bool  dequeued_;
-    };
-
-    std::vector<Node>   nodes_;
-    int                 firstNode_;
-
-    bool CheckInvariants() const;
-
-    void SetTopNodeInternal(unsigned int value);  
-
-  public:
-    DownloadStack(unsigned int size);
-
-    ~DownloadStack();
-
-    bool Pop(unsigned int& value);
-
-    void SetTopNode(unsigned int value);  
-
-    void SetTopNodePermissive(int value);
-  };
-}
--- a/Framework/Toolbox/FiniteProjectiveCamera.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -299,6 +299,7 @@
   static void ApplyRaytracerInternal(Orthanc::ImageAccessor& target,
                                      const FiniteProjectiveCamera& camera,
                                      const ImageBuffer3D& source,
+                                     const VolumeImageGeometry& geometry,
                                      VolumeProjection projection)
   {
     if (source.GetFormat() != SourceFormat ||
@@ -315,8 +316,8 @@
     LOG(WARNING) << "Output image size: " << target.GetWidth() << "x" << target.GetHeight();
     LOG(WARNING) << "Output pixel format: " << Orthanc::EnumerationToString(target.GetFormat());
 
-    std::auto_ptr<OrthancStone::ParallelSlices> slices(source.GetGeometry(projection));
-    const OrthancStone::Vector pixelSpacing = source.GetGeometry().GetVoxelDimensions(projection);
+    const unsigned int slicesCount = geometry.GetProjectionDepth(projection);
+    const OrthancStone::Vector pixelSpacing = geometry.GetVoxelDimensions(projection);
     const unsigned int targetWidth = target.GetWidth();
     const unsigned int targetHeight = target.GetHeight();
 
@@ -327,11 +328,11 @@
 
     typedef SubpixelReader<SourceFormat, ImageInterpolation_Nearest>  SourceReader;
     
-    for (size_t z = 0; z < slices->GetSliceCount(); z++)
+    for (unsigned int z = 0; z < slicesCount; z++)
     {
-      LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount();
-      
-      const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z);
+      LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slicesCount;
+
+      OrthancStone::CoordinateSystem3D slice = geometry.GetProjectionSlice(projection, z);
       OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, static_cast<unsigned int>(z));
 
       SourceReader pixelReader(sliceReader.GetAccessor());
@@ -422,6 +423,7 @@
 
   Orthanc::ImageAccessor*
   FiniteProjectiveCamera::ApplyRaytracer(const ImageBuffer3D& source,
+                                         const VolumeImageGeometry& geometry,
                                          Orthanc::PixelFormat targetFormat,
                                          unsigned int targetWidth,
                                          unsigned int targetHeight,
@@ -440,14 +442,14 @@
     {
       ApplyRaytracerInternal<Orthanc::PixelFormat_Grayscale16,
                              Orthanc::PixelFormat_Grayscale16, true>
-        (*target, *this, source, projection);
+        (*target, *this, source, geometry, projection);
     }
     else if (targetFormat == Orthanc::PixelFormat_Grayscale16 &&
              source.GetFormat() == Orthanc::PixelFormat_Grayscale16 && !mip)
     {
       ApplyRaytracerInternal<Orthanc::PixelFormat_Grayscale16,
                              Orthanc::PixelFormat_Grayscale16, false>
-        (*target, *this, source, projection);
+        (*target, *this, source, geometry, projection);
     }
     else
     {
--- a/Framework/Toolbox/FiniteProjectiveCamera.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/FiniteProjectiveCamera.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,6 +23,7 @@
 
 #include "LinearAlgebra.h"
 #include "../Volumes/ImageBuffer3D.h"
+#include "../Volumes/VolumeImageGeometry.h"
 
 namespace OrthancStone
 {
@@ -109,6 +110,7 @@
     Vector ApplyGeneral(const Vector& v) const;
 
     Orthanc::ImageAccessor* ApplyRaytracer(const ImageBuffer3D& source,
+                                           const VolumeImageGeometry& geometry,
                                            Orthanc::PixelFormat targetFormat,
                                            unsigned int targetWidth,
                                            unsigned int targetHeight,
--- a/Framework/Toolbox/IDelayedCallExecutor.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +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/>.
- **/
-
-
-#pragma once
-
-#include "../../Framework/Messages/IObserver.h"
-#include "../../Framework/Messages/ICallable.h"
-
-#include <Core/IDynamicObject.h>
-#include <Core/Logging.h>
-
-#include <string>
-#include <map>
-
-namespace Deprecated
-{
-  // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript).
-  class IDelayedCallExecutor : public boost::noncopyable
-  {
-  protected:
-    OrthancStone::MessageBroker& broker_;
-    
-  public:
-    ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage);
-
-    IDelayedCallExecutor(OrthancStone::MessageBroker& broker) :
-      broker_(broker)
-    {
-    }
-
-    
-    virtual ~IDelayedCallExecutor()
-    {
-    }
-
-    
-    virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,
-                          unsigned int timeoutInMs = 1000) = 0;
-  };
-}
--- a/Framework/Toolbox/ISeriesLoader.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ParallelSlices.h"
-
-#include <Images/ImageAccessor.h>
-#include <Plugins/Samples/Common/IDicomDataset.h>
-
-namespace OrthancStone
-{
-  class ISeriesLoader : public boost::noncopyable
-  {
-  public:
-    virtual ~ISeriesLoader()
-    {
-    }
-    
-    virtual ParallelSlices& GetGeometry() = 0;
-
-    virtual Orthanc::PixelFormat GetPixelFormat() = 0;
-
-    virtual unsigned int GetWidth() = 0;
-
-    virtual unsigned int GetHeight() = 0;
-
-    virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index) = 0;
-
-    // This downloads the frame from Orthanc. The resulting pixel
-    // format must be Grayscale8, Grayscale16, SignedGrayscale16 or
-    // RGB24. Orthanc Stone assumes the conversion of the photometric
-    // interpretation is done by Orthanc.
-    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0;
-
-    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
-                                                      unsigned int quality) = 0;
-
-    virtual bool IsJpegAvailable() = 0;
-  };
-}
--- a/Framework/Toolbox/IWebService.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "IWebService.h"
-
-#include <Core/OrthancException.h>
-
-
-namespace Deprecated
-{
-  const Orthanc::IDynamicObject&
-  IWebService::HttpRequestSuccessMessage::GetPayload() const
-  {
-    if (HasPayload())
-    {
-      return *payload_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-    
-
-  const Orthanc::IDynamicObject&
-  IWebService::HttpRequestErrorMessage::GetPayload() const
-  {
-    if (HasPayload())
-    {
-      return *payload_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-}
--- a/Framework/Toolbox/IWebService.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../../Framework/Messages/IObserver.h"
-#include "../../Framework/Messages/ICallable.h"
-
-#include <Core/IDynamicObject.h>
-#include <Core/Logging.h>
-
-#include <string>
-#include <map>
-
-namespace Deprecated
-{
-  // The IWebService performs HTTP requests.
-  // Since applications can run in native or WASM environment and, since
-  // in a WASM environment, the WebService is asynchronous, the IWebservice
-  // also implements an asynchronous interface: you must schedule a request
-  // and you'll be notified when the response/error is ready.
-  class IWebService : public boost::noncopyable
-  {
-  protected:
-    OrthancStone::MessageBroker& broker_;
-    
-  public:
-    typedef std::map<std::string, std::string> HttpHeaders;
-
-    class HttpRequestSuccessMessage : public OrthancStone::IMessage
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      const std::string&             uri_;
-      const void*                    answer_;
-      size_t                         answerSize_;
-      const HttpHeaders&             answerHeaders_;
-      const Orthanc::IDynamicObject* payload_;
-
-    public:
-      HttpRequestSuccessMessage(const std::string& uri,
-                                const void* answer,
-                                size_t answerSize,
-                                const HttpHeaders& answerHeaders,
-                                const Orthanc::IDynamicObject* payload) :
-        uri_(uri),
-        answer_(answer),
-        answerSize_(answerSize),
-        answerHeaders_(answerHeaders),
-        payload_(payload)
-      {
-      }
-
-      const std::string& GetUri() const
-      {
-        return uri_;
-      }
-
-      const void* GetAnswer() const
-      {
-        return answer_;
-      }
-
-      size_t GetAnswerSize() const
-      {
-        return answerSize_;
-      }
-
-      const HttpHeaders&  GetAnswerHttpHeaders() const
-      {
-        return answerHeaders_;
-      }
-
-      bool HasPayload() const
-      {
-        return payload_ != NULL;
-      }
-
-      const Orthanc::IDynamicObject& GetPayload() const;
-    };
-    
-
-    class HttpRequestErrorMessage : public OrthancStone::IMessage
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      const std::string&              uri_;
-      const Orthanc::IDynamicObject*  payload_;
-
-    public:
-      HttpRequestErrorMessage(const std::string& uri,
-                              const Orthanc::IDynamicObject* payload) :
-        uri_(uri),
-        payload_(payload)
-      {
-      }
-
-      const std::string& GetUri() const
-      {
-        return uri_;
-      }
-
-      bool HasPayload() const
-      {
-        return payload_ != NULL;
-      }
-
-      const Orthanc::IDynamicObject& GetPayload() const;
-    };
-
-
-    IWebService(OrthancStone::MessageBroker& broker) :
-      broker_(broker)
-    {
-    }
-
-    
-    virtual ~IWebService()
-    {
-    }
-
-    virtual void EnableCache(bool enable) = 0;
-    
-    virtual void GetAsync(const std::string& uri,
-                          const HttpHeaders& headers,
-                          Orthanc::IDynamicObject* payload  /* takes ownership */,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
-                          OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                          unsigned int timeoutInSeconds = 60) = 0;
-
-    virtual void PostAsync(const std::string& uri,
-                           const HttpHeaders& headers,
-                           const std::string& body,
-                           Orthanc::IDynamicObject* payload  /* takes ownership */,
-                           OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
-                           OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                           unsigned int timeoutInSeconds = 60) = 0;
-
-    virtual void DeleteAsync(const std::string& uri,
-                             const HttpHeaders& headers,
-                             Orthanc::IDynamicObject* payload  /* takes ownership */,
-                             OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
-                             OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                             unsigned int timeoutInSeconds = 60) = 0;
-  };
-}
--- a/Framework/Toolbox/LinearAlgebra.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/LinearAlgebra.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -63,21 +63,40 @@
                      const std::string& value)
     {
       std::vector<std::string> items;
-      Orthanc::Toolbox::TokenizeString(items, value, '\\');
+      Orthanc::Toolbox::TokenizeString(items, Orthanc::Toolbox::StripSpaces(value), '\\');
 
       target.resize(items.size());
 
       for (size_t i = 0; i < items.size(); i++)
       {
+        /**
+         * We try and avoid the use of "boost::lexical_cast<>" here,
+         * as it is very slow. As we are parsing many doubles, we
+         * prefer to use the standard "std::stod" function if
+         * available: http://www.cplusplus.com/reference/string/stod/
+         **/
+          
+#if __cplusplus >= 201103L  // Is C++11 enabled?
         try
         {
-          target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i]));
+          target[i] = std::stod(items[i]);
+        }
+        catch (std::exception&)
+        {
+          target.clear();
+          return false;
+        }
+#else  // Fallback implementation using Boost
+        try
+        {
+          target[i] = boost::lexical_cast<double>(items[i]);
         }
         catch (boost::bad_lexical_cast&)
         {
           target.clear();
           return false;
         }
+#endif
       }
 
       return true;
--- a/Framework/Toolbox/MessagingToolbox.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,456 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "MessagingToolbox.h"
-
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Images/JpegReader.h>
-#include <Core/Images/PngReader.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-#include <Core/Logging.h>
-
-#include <boost/lexical_cast.hpp>
-#include <json/reader.h>
-#include <json/writer.h>
-
-namespace OrthancStone
-{
-  namespace MessagingToolbox
-  {
-    static bool ParseVersion(std::string& version,
-                             unsigned int& major,
-                             unsigned int& minor,
-                             unsigned int& patch,
-                             const Json::Value& info)
-    {
-      if (info.type() != Json::objectValue ||
-          !info.isMember("Version") ||
-          info["Version"].type() != Json::stringValue)
-      {
-        return false;
-      }
-
-      version = info["Version"].asString();
-      if (version == "mainline")
-      {
-        // Some arbitrary high values Orthanc versions will never reach ;)
-        major = 999;
-        minor = 999;
-        patch = 999;
-        return true;
-      }
-
-      std::vector<std::string> tokens;
-      Orthanc::Toolbox::TokenizeString(tokens, version, '.');
-      
-      if (tokens.size() != 2 &&
-          tokens.size() != 3)
-      {
-        return false;
-      }
-
-      int a, b, c;
-      try
-      {
-        a = boost::lexical_cast<int>(tokens[0]);
-        b = boost::lexical_cast<int>(tokens[1]);
-
-        if (tokens.size() == 3)
-        {
-          c = boost::lexical_cast<int>(tokens[2]);
-        }
-        else
-        {
-          c = 0;
-        }
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return false;
-      }
-
-      if (a < 0 ||
-          b < 0 ||
-          c < 0)
-      {
-        return false;
-      }
-      else
-      {
-        major = static_cast<unsigned int>(a);
-        minor = static_cast<unsigned int>(b);
-        patch = static_cast<unsigned int>(c);
-        return true;
-      }         
-    }
-
-
-    bool ParseJson(Json::Value& target,
-                   const void* content,
-                   size_t size)
-    {
-      Json::Reader reader;
-      return reader.parse(reinterpret_cast<const char*>(content),
-                          reinterpret_cast<const char*>(content) + size,
-                          target);
-    }
-
-    void JsonToString(std::string& target,
-                      const Json::Value& source)
-    {
-      Json::FastWriter writer;
-      target = writer.write(source);
-    }
-
-    static void ParseJsonException(Json::Value& target,
-                                   const std::string& source)
-    {
-      Json::Reader reader;
-      if (!reader.parse(source, target))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    }
-
-
-    void RestApiGet(Json::Value& target,
-                    OrthancPlugins::IOrthancConnection& orthanc,
-                    const std::string& uri)
-    {
-      std::string tmp;
-      orthanc.RestApiGet(tmp, uri);
-      ParseJsonException(target, tmp);
-    }
-
-
-    void RestApiPost(Json::Value& target,
-                     OrthancPlugins::IOrthancConnection& orthanc,
-                     const std::string& uri,
-                     const std::string& body)
-    {
-      std::string tmp;
-      orthanc.RestApiPost(tmp, uri, body);
-      ParseJsonException(target, tmp);
-    }
-
-
-    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc)
-    {
-      try
-      {
-        Json::Value json;
-        RestApiGet(json, orthanc, "/plugins/web-viewer");
-        return json.type() == Json::objectValue;
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        return false;
-      }
-    }
-
-
-    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc)
-    {
-      Json::Value json;
-      std::string version;
-      unsigned int major, minor, patch;
-
-      try
-      {
-        RestApiGet(json, orthanc, "/system");
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        LOG(ERROR) << "Cannot connect to your Orthanc server";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-        
-      if (!ParseVersion(version, major, minor, patch, json))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version;
-
-      // Stone is only compatible with Orthanc >= 1.3.1
-      if (major < 1 ||
-          (major == 1 && minor < 3) ||
-          (major == 1 && minor == 3 && patch < 1))
-      {
-        return false;
-      }
-
-      try
-      {
-        RestApiGet(json, orthanc, "/plugins/web-viewer");       
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        // The Web viewer is not installed, this is OK
-        LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled";
-        return true;
-      }
-
-      if (!ParseVersion(version, major, minor, patch, json))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version;
-
-      return (major >= 3 ||
-              (major == 2 && minor >= 2));
-    }
-
-
-    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                        const std::string& instance,
-                                        unsigned int frame,
-                                        Orthanc::PixelFormat targetFormat)
-    {
-      std::string uri = ("instances/" + instance + "/frames/" + 
-                         boost::lexical_cast<std::string>(frame));
-
-      std::string compressed;
-
-      switch (targetFormat)
-      {
-        case Orthanc::PixelFormat_RGB24:
-          orthanc.RestApiGet(compressed, uri + "/preview");
-          break;
-
-        case Orthanc::PixelFormat_Grayscale16:
-          orthanc.RestApiGet(compressed, uri + "/image-uint16");
-          break;
-
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          orthanc.RestApiGet(compressed, uri + "/image-int16");
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      
-      std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader);
-      result->ReadFromMemory(compressed);
-
-      if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16)
-      {
-        if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-        {
-          result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-      }
-
-      return result.release();
-    }
-
-
-    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                            const std::string& instance,
-                                            unsigned int frame,
-                                            unsigned int quality,
-                                            Orthanc::PixelFormat targetFormat)
-    {
-      if (quality <= 0 || 
-          quality > 100)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      // This requires the official Web viewer plugin to be installed!
-      std::string uri = ("web-viewer/instances/jpeg" + 
-                         boost::lexical_cast<std::string>(quality) + 
-                         "-" + instance + "_" + 
-                         boost::lexical_cast<std::string>(frame));
-
-      Json::Value encoded;
-      RestApiGet(encoded, orthanc, uri);
-
-      if (encoded.type() != Json::objectValue ||
-          !encoded.isMember("Orthanc") ||
-          encoded["Orthanc"].type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      Json::Value& info = encoded["Orthanc"];
-      if (!info.isMember("PixelData") ||
-          !info.isMember("Stretched") ||
-          !info.isMember("Compression") ||
-          info["Compression"].type() != Json::stringValue ||
-          info["PixelData"].type() != Json::stringValue ||
-          info["Stretched"].type() != Json::booleanValue ||
-          info["Compression"].asString() != "Jpeg")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }          
-
-      bool isSigned = false;
-      bool isStretched = info["Stretched"].asBool();
-
-      if (info.isMember("IsSigned"))
-      {
-        if (info["IsSigned"].type() != Json::booleanValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }          
-        else
-        {
-          isSigned = info["IsSigned"].asBool();
-        }
-      }
-
-      std::string jpeg;
-      Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
-
-      std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
-      reader->ReadFromMemory(jpeg);
-
-      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
-      {
-        if (targetFormat != Orthanc::PixelFormat_RGB24)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-
-        if (isSigned || isStretched)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-        else
-        {
-          return reader.release();
-        }
-      }
-
-      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      if (!isStretched)
-      {
-        if (targetFormat != reader->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-
-        return reader.release();
-      }
-
-      int32_t stretchLow = 0;
-      int32_t stretchHigh = 0;
-
-      if (!info.isMember("StretchLow") ||
-          !info.isMember("StretchHigh") ||
-          info["StretchLow"].type() != Json::intValue ||
-          info["StretchHigh"].type() != Json::intValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      stretchLow = info["StretchLow"].asInt();
-      stretchHigh = info["StretchHigh"].asInt();
-
-      if (stretchLow < -32768 ||
-          stretchHigh > 65535 ||
-          (stretchLow < 0 && stretchHigh > 32767))
-      {
-        // This range cannot be represented with a uint16_t or an int16_t
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
-      }
-
-      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
-      std::auto_ptr<Orthanc::ImageAccessor> image
-        (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false));
-
-      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-      float offset = static_cast<float>(stretchLow) / scaling;
-      
-      Orthanc::ImageProcessing::Convert(*image, *reader);
-      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
-
-#if 0
-      /*info.removeMember("PixelData");
-        std::cout << info.toStyledString();*/
-      
-      int64_t a, b;
-      Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image);
-      std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl;
-#endif
-
-      return image.release();
-    }
-
-
-    static void AddTag(Orthanc::DicomMap& target,
-                       const OrthancPlugins::IDicomDataset& source,
-                       const Orthanc::DicomTag& tag)
-    {
-      OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement());
-      
-      std::string value;
-      if (source.GetStringValue(value, key))
-      {
-        target.SetValue(tag, value, false);
-      }
-    }
-
-    
-    void ConvertDataset(Orthanc::DicomMap& target,
-                        const OrthancPlugins::IDicomDataset& source)
-    {
-      target.Clear();
-
-      AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED);
-      AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED);
-      AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS);
-      AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING);
-      AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER);
-      AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR);
-      AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT);
-      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
-      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT);
-      AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES);
-      AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION);
-      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION);
-      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING);
-      AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION);
-      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
-      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE);
-      AddTag(target, source, Orthanc::DICOM_TAG_ROWS);
-      AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL);
-      AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
-      AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS);
-      AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID);
-      AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID);
-      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER);
-      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH);
-    }
-  }
-}
--- a/Framework/Toolbox/MessagingToolbox.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../StoneEnumerations.h"
-
-#include <Core/DicomFormat/DicomMap.h>
-#include <Core/Images/ImageAccessor.h>
-#include <Plugins/Samples/Common/IDicomDataset.h>
-#include <Plugins/Samples/Common/IOrthancConnection.h>
-
-#include <json/value.h>
-
-namespace OrthancStone
-{
-  namespace MessagingToolbox
-  {
-    bool ParseJson(Json::Value& target,
-                   const void* content,
-                   size_t size);
-
-    void JsonToString(std::string& target,
-                      const Json::Value& source);
-
-
-    void RestApiGet(Json::Value& target,
-                    OrthancPlugins::IOrthancConnection& orthanc,
-                    const std::string& uri);
-
-    void RestApiPost(Json::Value& target,
-                     OrthancPlugins::IOrthancConnection& orthanc,
-                     const std::string& uri,
-                     const std::string& body);
-
-    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc);
-
-    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc);
-
-    // This downloads the image from Orthanc and keeps its pixel
-    // format unchanged (will be either Grayscale8, Grayscale16,
-    // SignedGrayscale16, or RGB24)
-    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                        const std::string& instance,
-                                        unsigned int frame,
-                                        Orthanc::PixelFormat targetFormat);
-
-    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                            const std::string& instance,
-                                            unsigned int frame,
-                                            unsigned int quality,
-                                            Orthanc::PixelFormat targetFormat);
-
-    void ConvertDataset(Orthanc::DicomMap& target,
-                        const OrthancPlugins::IDicomDataset& source);
-  }
-}
--- a/Framework/Toolbox/OrientedBoundingBox.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,268 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrientedBoundingBox.h"
-
-#include "GeometryToolbox.h"
-
-#include <Core/OrthancException.h>
-
-#include <cassert>
-
-namespace OrthancStone
-{
-  OrientedBoundingBox::OrientedBoundingBox(const ImageBuffer3D& image)
-  {
-    unsigned int n = image.GetDepth();
-    if (n < 1)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);      
-    }
-
-    const CoordinateSystem3D& geometry = image.GetGeometry().GetAxialGeometry();
-    Vector dim = image.GetGeometry().GetVoxelDimensions(VolumeProjection_Axial);
-
-    u_ = geometry.GetAxisX();
-    v_ = geometry.GetAxisY();
-    w_ = geometry.GetNormal();
-
-    hu_ = static_cast<double>(image.GetWidth() * dim[0] / 2.0);
-    hv_ = static_cast<double>(image.GetHeight() * dim[1] / 2.0);
-    hw_ = static_cast<double>(image.GetDepth() * dim[2] / 2.0);
-      
-    c_ = (geometry.GetOrigin() + 
-          (hu_ - dim[0] / 2.0) * u_ +
-          (hv_ - dim[1] / 2.0) * v_ +
-          (hw_ - dim[2] / 2.0) * w_);
-  }
-
-
-  bool OrientedBoundingBox::HasIntersectionWithPlane(std::vector<Vector>& points,
-                                                     const Vector& normal,
-                                                     double d) const
-  {
-    assert(normal.size() == 3);
-
-    double r = (hu_ * fabs(boost::numeric::ublas::inner_prod(normal, u_)) +
-                hv_ * fabs(boost::numeric::ublas::inner_prod(normal, v_)) +
-                hw_ * fabs(boost::numeric::ublas::inner_prod(normal, w_)));
-
-    double s = boost::numeric::ublas::inner_prod(normal, c_) + d;
-
-    if (fabs(s) >= r)
-    {
-      // No intersection, or intersection is reduced to a single point
-      return false;
-    }
-    else
-    {
-      Vector p;
-
-      // Loop over all the 12 edges (segments) of the oriented
-      // bounding box, and check whether they intersect the plane
-        
-      // X-aligned edges
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
-           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      // Y-aligned edges
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
-           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      // Z-aligned edges
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
-           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      if (GeometryToolbox::IntersectPlaneAndSegment
-          (p, normal, d,
-           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_,
-           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
-      {
-        points.push_back(p);
-      }
-
-      return true;
-    }
-  }
-
-
-  bool OrientedBoundingBox::HasIntersection(std::vector<Vector>& points,
-                                            const CoordinateSystem3D& plane) const
-  {
-    // From the vector equation of a 3D plane (specified by origin
-    // and normal), to the general equation of a 3D plane (which
-    // looses information about the origin of the coordinate system)
-    const Vector& normal = plane.GetNormal();
-    const Vector& origin = plane.GetOrigin();
-    double d = -(normal[0] * origin[0] + normal[1] * origin[1] + normal[2] * origin[2]);
-
-    return HasIntersectionWithPlane(points, normal, d);
-  }
-  
-
-  bool OrientedBoundingBox::Contains(const Vector& p) const
-  {
-    assert(p.size() == 3);
-
-    const Vector q = p - c_;
-
-    return (fabs(boost::numeric::ublas::inner_prod(q, u_)) <= hu_ &&
-            fabs(boost::numeric::ublas::inner_prod(q, v_)) <= hv_ &&
-            fabs(boost::numeric::ublas::inner_prod(q, w_)) <= hw_);
-  }
-
-  
-  void OrientedBoundingBox::FromInternalCoordinates(Vector& target,
-                                                    double x,
-                                                    double y,
-                                                    double z) const
-  {
-    target = (c_ +
-              u_ * 2.0 * hu_ * (x - 0.5) +
-              v_ * 2.0 * hv_ * (y - 0.5) +
-              w_ * 2.0 * hw_ * (z - 0.5));
-  }
-
-  
-  void OrientedBoundingBox::FromInternalCoordinates(Vector& target,
-                                                    const Vector& source) const
-  {
-    assert(source.size() == 3);
-    FromInternalCoordinates(target, source[0], source[1], source[2]);
-  }
-
-  
-  void OrientedBoundingBox::ToInternalCoordinates(Vector& target,
-                                                  const Vector& source) const
-  {
-    assert(source.size() == 3);
-    const Vector q = source - c_;
-
-    double x = boost::numeric::ublas::inner_prod(q, u_) / (2.0 * hu_) + 0.5;
-    double y = boost::numeric::ublas::inner_prod(q, v_) / (2.0 * hv_) + 0.5;
-    double z = boost::numeric::ublas::inner_prod(q, w_) / (2.0 * hw_) + 0.5;
-
-    LinearAlgebra::AssignVector(target, x, y, z);
-  }
-
-
-  bool OrientedBoundingBox::ComputeExtent(Extent2D& extent,
-                                          const CoordinateSystem3D& plane) const
-  {
-    extent.Reset();
-    
-    std::vector<Vector> points;
-    if (HasIntersection(points, plane))
-    {
-      for (size_t i = 0; i < points.size(); i++)
-      {
-        double x, y;
-        plane.ProjectPoint(x, y, points[i]);
-        extent.AddPoint(x, y);
-      }
-      
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-}
--- a/Framework/Toolbox/OrientedBoundingBox.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Extent2D.h"
-#include "LinearAlgebra.h"
-#include "../Volumes/ImageBuffer3D.h"
-
-namespace OrthancStone
-{
-  class OrientedBoundingBox : public boost::noncopyable
-  {
-  private:
-    Vector  c_;   // center
-    Vector  u_;   // normalized width vector
-    Vector  v_;   // normalized height vector
-    Vector  w_;   // normalized depth vector
-    double  hu_;  // half width
-    double  hv_;  // half height
-    double  hw_;  // half depth
-
-  public:
-    OrientedBoundingBox(const ImageBuffer3D& image);
-
-    const Vector& GetCenter() const
-    {
-      return c_;
-    }
-
-    bool HasIntersectionWithPlane(std::vector<Vector>& points,
-                                  const Vector& normal,
-                                  double d) const;
-
-    bool HasIntersection(std::vector<Vector>& points,
-                         const CoordinateSystem3D& plane) const;
-
-    bool Contains(const Vector& p) const;
-
-    void FromInternalCoordinates(Vector& target,
-                                 double x,
-                                 double y,
-                                 double z) const;
-
-    void FromInternalCoordinates(Vector& target,
-                                 const Vector& source) const;
-
-    void ToInternalCoordinates(Vector& target,
-                               const Vector& source) const;
-
-    bool ComputeExtent(Extent2D& extent,
-                       const CoordinateSystem3D& plane) const;
-  };
-}
-
--- a/Framework/Toolbox/OrthancApiClient.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,337 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#include "OrthancApiClient.h"
-
-#include "MessagingToolbox.h"
-#include "Framework/Toolbox/MessagingToolbox.h"
-
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  const Orthanc::IDynamicObject& OrthancApiClient::JsonResponseReadyMessage::GetPayload() const
-  {
-    if (HasPayload())
-    {
-      return *payload_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-  
-  
-  const Orthanc::IDynamicObject& OrthancApiClient::BinaryResponseReadyMessage::GetPayload() const
-  {
-    if (HasPayload())
-    {
-      return *payload_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-  
-  
-  const Orthanc::IDynamicObject& OrthancApiClient::EmptyResponseReadyMessage::GetPayload() const
-  {
-    if (HasPayload())
-    {
-      return *payload_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-  
-  
-  class OrthancApiClient::WebServicePayload : public Orthanc::IDynamicObject
-  {
-  private:
-    std::auto_ptr< OrthancStone::MessageHandler<EmptyResponseReadyMessage> >             emptyHandler_;
-    std::auto_ptr< OrthancStone::MessageHandler<JsonResponseReadyMessage> >              jsonHandler_;
-    std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> >            binaryHandler_;
-    std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> >  failureHandler_;
-    std::auto_ptr< Orthanc::IDynamicObject >                               userPayload_;
-
-    void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const
-    {
-      if (failureHandler_.get() != NULL)
-      {
-        failureHandler_->Apply(IWebService::HttpRequestErrorMessage
-                               (message.GetUri(), userPayload_.get()));
-      }
-    }
-    
-  public:
-    WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler,
-                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
-                      Orthanc::IDynamicObject* userPayload) :
-      emptyHandler_(handler),
-      failureHandler_(failureHandler),
-      userPayload_(userPayload)
-    {
-      if (handler == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler,
-                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
-                      Orthanc::IDynamicObject* userPayload) :
-      binaryHandler_(handler),
-      failureHandler_(failureHandler),
-      userPayload_(userPayload)
-    {
-      if (handler == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler,
-                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler,
-                      Orthanc::IDynamicObject* userPayload) :
-      jsonHandler_(handler),
-      failureHandler_(failureHandler),
-      userPayload_(userPayload)
-    {
-      if (handler == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const
-    {
-      if (emptyHandler_.get() != NULL)
-      {
-        emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage
-                             (message.GetUri(), userPayload_.get()));
-      }
-      else if (binaryHandler_.get() != NULL)
-      {
-        binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage
-                              (message.GetUri(), message.GetAnswer(),
-                               message.GetAnswerSize(), userPayload_.get()));
-      }
-      else if (jsonHandler_.get() != NULL)
-      {
-        Json::Value response;
-        if (OrthancStone::MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize()))
-        {
-          jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage
-                              (message.GetUri(), response, userPayload_.get()));
-        }
-        else
-        {
-          NotifyConversionError(message);
-        }
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-    void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const
-    {
-      if (failureHandler_.get() != NULL)
-      {
-        failureHandler_->Apply(IWebService::HttpRequestErrorMessage
-                               (message.GetUri(), userPayload_.get()));
-      }
-    }
-  };
-
-
-  OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker,
-                                     IWebService& web,
-                                     const std::string& baseUrl) :
-    IObservable(broker),
-    IObserver(broker),
-    web_(web),
-    baseUrl_(baseUrl)
-  {
-  }
-
-
-  void OrthancApiClient::GetJsonAsync(
-      const std::string& uri,
-      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload)
-  {
-    IWebService::HttpHeaders emptyHeaders;
-    web_.GetAsync(baseUrl_ + uri,
-                  emptyHeaders,
-                  new WebServicePayload(successCallback, failureCallback, payload),
-                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                  (*this, &OrthancApiClient::NotifyHttpSuccess),
-                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                  (*this, &OrthancApiClient::NotifyHttpError));
-  }
-
-
-  void OrthancApiClient::GetBinaryAsync(
-      const std::string& uri,
-      const std::string& contentType,
-      OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload)
-  {
-    IWebService::HttpHeaders headers;
-    headers["Accept"] = contentType;
-    GetBinaryAsync(uri, headers, successCallback, failureCallback, payload);
-  }
-
-  void OrthancApiClient::GetBinaryAsync(
-      const std::string& uri,
-      const IWebService::HttpHeaders& headers,
-      OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload)
-  {
-    // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str());
-
-    web_.GetAsync(baseUrl_ + uri, headers,
-                  new WebServicePayload(successCallback, failureCallback, payload),
-                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                  (*this, &OrthancApiClient::NotifyHttpSuccess),
-                  new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                  (*this, &OrthancApiClient::NotifyHttpError));
-  }
-
-  
-  void OrthancApiClient::PostBinaryAsyncExpectJson(
-      const std::string& uri,
-      const std::string& body,
-      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload)
-  {
-    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
-                   new WebServicePayload(successCallback, failureCallback, payload),
-                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                   (*this, &OrthancApiClient::NotifyHttpSuccess),
-                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                   (*this, &OrthancApiClient::NotifyHttpError));
-
-  }
-
-  void OrthancApiClient::PostBinaryAsync(
-      const std::string& uri,
-      const std::string& body)
-  {
-    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL);
-  }
-
-  void OrthancApiClient::PostBinaryAsync(
-      const std::string& uri,
-      const std::string& body,
-      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload   /* takes ownership */)
-  {
-    web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body,
-                   new WebServicePayload(successCallback, failureCallback, payload),
-                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                   (*this, &OrthancApiClient::NotifyHttpSuccess),
-                   new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                   (*this, &OrthancApiClient::NotifyHttpError));
-  }
-
-  void OrthancApiClient::PostJsonAsyncExpectJson(
-      const std::string& uri,
-      const Json::Value& data,
-      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload)
-  {
-    std::string body;
-    OrthancStone::MessagingToolbox::JsonToString(body, data);
-    return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload);
-  }
-
-  void OrthancApiClient::PostJsonAsync(
-      const std::string& uri,
-      const Json::Value& data)
-  {
-    std::string body;
-    OrthancStone::MessagingToolbox::JsonToString(body, data);
-    return PostBinaryAsync(uri, body);
-  }
-
-  void OrthancApiClient::PostJsonAsync(
-      const std::string& uri,
-      const Json::Value& data,
-      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload   /* takes ownership */)
-  {
-    std::string body;
-    OrthancStone::MessagingToolbox::JsonToString(body, data);
-    return PostBinaryAsync(uri, body, successCallback, failureCallback, payload);
-  }
-
-  void OrthancApiClient::DeleteAsync(
-      const std::string& uri,
-      OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
-      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
-      Orthanc::IDynamicObject* payload)
-  {
-    web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(),
-                     new WebServicePayload(successCallback, failureCallback, payload),
-                     new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                     (*this, &OrthancApiClient::NotifyHttpSuccess),
-                     new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                     (*this, &OrthancApiClient::NotifyHttpError));
-  }
-
-
-  void OrthancApiClient::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
-  {
-    if (message.HasPayload())
-    {
-      dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleSuccess(message);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-  void OrthancApiClient::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message)
-  {
-    if (message.HasPayload())
-    {
-      dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleFailure(message);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-}
--- a/Framework/Toolbox/OrthancApiClient.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,243 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <boost/shared_ptr.hpp>
-#include <json/json.h>
-
-#include "IWebService.h"
-#include "../Messages/IObservable.h"
-#include "../Messages/Promise.h"
-
-namespace Deprecated
-{
-  class OrthancApiClient :
-      public OrthancStone::IObservable,
-      public OrthancStone::IObserver
-  {
-  public:
-    class JsonResponseReadyMessage : public OrthancStone::IMessage
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      const std::string&              uri_;
-      const Json::Value&              json_;
-      const Orthanc::IDynamicObject*  payload_;
-
-    public:
-      JsonResponseReadyMessage(const std::string& uri,
-                               const Json::Value& json,
-                               const Orthanc::IDynamicObject* payload) :
-        uri_(uri),
-        json_(json),
-        payload_(payload)
-      {
-      }
-
-      const std::string& GetUri() const
-      {
-        return uri_;
-      }
-
-      const Json::Value& GetJson() const
-      {
-        return json_;
-      }
-
-      bool HasPayload() const
-      {
-        return payload_ != NULL;
-      }
-
-      const Orthanc::IDynamicObject& GetPayload() const;
-    };
-    
-
-    class BinaryResponseReadyMessage : public OrthancStone::IMessage
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      const std::string&              uri_;
-      const void*                     answer_;
-      size_t                          answerSize_;
-      const Orthanc::IDynamicObject*  payload_;
-
-    public:
-      BinaryResponseReadyMessage(const std::string& uri,
-                                 const void* answer,
-                                 size_t answerSize,
-                                 const Orthanc::IDynamicObject* payload) :
-        uri_(uri),
-        answer_(answer),
-        answerSize_(answerSize),
-        payload_(payload)
-      {
-      }
-
-      const std::string& GetUri() const
-      {
-        return uri_;
-      }
-
-      const void* GetAnswer() const
-      {
-        return answer_;
-      }
-
-      size_t GetAnswerSize() const
-      {
-        return answerSize_;
-      }
-
-      bool HasPayload() const
-      {
-        return payload_ != NULL;
-      }
-
-      const Orthanc::IDynamicObject& GetPayload() const;
-    };
-
-
-    class EmptyResponseReadyMessage : public OrthancStone::IMessage
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      const std::string&              uri_;
-      const Orthanc::IDynamicObject*  payload_;
-
-    public:
-      EmptyResponseReadyMessage(const std::string& uri,
-                                const Orthanc::IDynamicObject* payload) :
-        uri_(uri),
-        payload_(payload)
-      {
-      }
-
-      const std::string& GetUri() const
-      {
-        return uri_;
-      }
-
-      bool HasPayload() const
-      {
-        return payload_ != NULL;
-      }
-
-      const Orthanc::IDynamicObject& GetPayload() const;
-    };
-
-    
-
-  private:
-    class WebServicePayload;
-
-  protected:
-    IWebService&  web_;
-    std::string   baseUrl_;
-
-  public:
-    OrthancApiClient(OrthancStone::MessageBroker& broker,
-                     IWebService& web,
-                     const std::string& baseUrl);
-    
-    virtual ~OrthancApiClient()
-    {
-    }
-
-    const std::string& GetBaseUrl() const {return baseUrl_;}
-
-    // schedule a GET request expecting a JSON response.
-    void GetJsonAsync(const std::string& uri,
-                      OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
-                      OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                      Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    // schedule a GET request expecting a binary response.
-    void GetBinaryAsync(const std::string& uri,
-                        const std::string& contentType,
-                        OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
-                        OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                        Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    // schedule a GET request expecting a binary response.
-    void GetBinaryAsync(const std::string& uri,
-                        const IWebService::HttpHeaders& headers,
-                        OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback,
-                        OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                        Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    // schedule a POST request expecting a JSON response.
-    void PostBinaryAsyncExpectJson(const std::string& uri,
-                                   const std::string& body,
-                                   OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
-                                   OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                                   Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    // schedule a POST request expecting a JSON response.
-    void PostJsonAsyncExpectJson(const std::string& uri,
-                                 const Json::Value& data,
-                                 OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback,
-                                 OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                                 Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    // schedule a POST request and don't mind the response.
-    void PostJsonAsync(const std::string& uri,
-                       const Json::Value& data);
-
-    // schedule a POST request and don't expect any response.
-    void PostJsonAsync(const std::string& uri,
-                       const Json::Value& data,
-                       OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
-                       OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                       Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-
-    // schedule a POST request and don't mind the response.
-    void PostBinaryAsync(const std::string& uri,
-                         const std::string& body);
-
-    // schedule a POST request and don't expect any response.
-    void PostBinaryAsync(const std::string& uri,
-                         const std::string& body,
-                         OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
-                         OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                         Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    // schedule a DELETE request expecting an empty response.
-    void DeleteAsync(const std::string& uri,
-                     OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback,
-                     OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
-                     Orthanc::IDynamicObject* payload = NULL   /* takes ownership */);
-
-    void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
-
-    void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message);
-
-  private:
-    void HandleFromCache(const std::string& uri,
-                         const IWebService::HttpHeaders& headers,
-                         Orthanc::IDynamicObject* payload /* takes ownership */);
-  };
-}
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,904 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrthancSlicesLoader.h"
-
-#include "MessagingToolbox.h"
-
-#include <Core/Compression/GzipCompressor.h>
-#include <Core/Endianness.h>
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Images/JpegReader.h>
-#include <Core/Images/PngReader.h>
-#include <Core/Images/PamReader.h>
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-#include <Plugins/Samples/Common/FullOrthancDataset.h>
-
-#include <boost/lexical_cast.hpp>
-
-
-
-/**
- * TODO This is a SLOW implementation of base64 decoding, because
- * "Orthanc::Toolbox::DecodeBase64()" does not work properly with
- * WASM. UNDERSTAND WHY.
- * https://stackoverflow.com/a/34571089/881731
- **/
-static std::string base64_decode(const std::string &in)
-{
-  std::string out;
-  
-  std::vector<int> T(256,-1);
-  for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
-  
-  int val=0, valb=-8;
-  for (size_t i = 0; i < in.size(); i++) {
-    unsigned char c = in[i];
-    if (T[c] == -1) break;
-    val = (val<<6) + T[c];
-    valb += 6;
-    if (valb>=0) {
-      out.push_back(char((val>>valb)&0xFF));
-      valb-=8;
-    }
-  }
-  return out;
-}
-
-
-
-namespace Deprecated
-{
-  class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
-  {
-  private:
-    Mode               mode_;
-    unsigned int       frame_;
-    unsigned int       sliceIndex_;
-    const Slice*       slice_;
-    std::string        instanceId_;
-    OrthancStone::SliceImageQuality  quality_;
-
-    Operation(Mode mode) :
-      mode_(mode)
-    {
-    }
-
-  public:
-    Mode GetMode() const
-    {
-      return mode_;
-    }
-
-    OrthancStone::SliceImageQuality GetQuality() const
-    {
-      assert(mode_ == Mode_LoadImage ||
-             mode_ == Mode_LoadRawImage);
-      return quality_;
-    }
-
-    unsigned int GetSliceIndex() const
-    {
-      assert(mode_ == Mode_LoadImage ||
-             mode_ == Mode_LoadRawImage);
-      return sliceIndex_;
-    }
-
-    const Slice& GetSlice() const
-    {
-      assert(mode_ == Mode_LoadImage ||
-             mode_ == Mode_LoadRawImage);
-      assert(slice_ != NULL);
-      return *slice_;
-    }
-
-    unsigned int GetFrame() const
-    {
-      assert(mode_ == Mode_FrameGeometry);
-      return frame_;
-    }
-
-    const std::string& GetInstanceId() const
-    {
-      assert(mode_ == Mode_FrameGeometry ||
-             mode_ == Mode_InstanceGeometry);
-      return instanceId_;
-    }
-
-    static Operation* DownloadInstanceGeometry(const std::string& instanceId)
-    {
-      std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
-      operation->instanceId_ = instanceId;
-      return operation.release();
-    }
-
-    static Operation* DownloadFrameGeometry(const std::string& instanceId,
-                                            unsigned int frame)
-    {
-      std::auto_ptr<Operation> operation(new Operation(Mode_FrameGeometry));
-      operation->instanceId_ = instanceId;
-      operation->frame_ = frame;
-      return operation.release();
-    }
-
-    static Operation* DownloadSliceImage(unsigned int  sliceIndex,
-                                         const Slice&  slice,
-                                         OrthancStone::SliceImageQuality quality)
-    {
-      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
-      tmp->sliceIndex_ = sliceIndex;
-      tmp->slice_ = &slice;
-      tmp->quality_ = quality;
-      return tmp.release();
-    }
-
-    static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
-                                            const Slice&  slice)
-    {
-      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage));
-      tmp->sliceIndex_ = sliceIndex;
-      tmp->slice_ = &slice;
-      tmp->quality_ = OrthancStone::SliceImageQuality_InternalRaw;
-      return tmp.release();
-    }
-
-    static Operation* DownloadDicomFile(const Slice&  slice)
-    {
-      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile));
-      tmp->slice_ = &slice;
-      return tmp.release();
-    }
-
-  };
-
-  void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
-                                                    const Orthanc::ImageAccessor& image)
-  {
-    OrthancSlicesLoader::SliceImageReadyMessage msg
-      (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
-    BroadcastMessage(msg);
-  }
-  
-  
-  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation)
-  {
-    OrthancSlicesLoader::SliceImageErrorMessage msg
-      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
-    BroadcastMessage(msg);
-  }
-  
-  
-  void OrthancSlicesLoader::SortAndFinalizeSlices()
-  {
-    bool ok = slices_.Sort();
-    
-    state_ = State_GeometryReady;
-    
-    if (ok)
-    {
-      LOG(INFO) << "Loaded a series with " << slices_.GetSlicesCount() << " slice(s)";
-      BroadcastMessage(SliceGeometryReadyMessage(*this));
-    }
-    else
-    {
-      LOG(ERROR) << "This series is empty";
-      BroadcastMessage(SliceGeometryErrorMessage(*this));
-    }
-  }
-  
-  void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message)
-  {
-    BroadcastMessage(SliceGeometryErrorMessage(*this));
-    state_ = State_Error;
-  }
-
-  void OrthancSlicesLoader::OnSliceImageError(const IWebService::HttpRequestErrorMessage& message)
-  {
-    NotifySliceImageError(dynamic_cast<const Operation&>(message.GetPayload()));
-    state_ = State_Error;
-  }
-
-  void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    const Json::Value& series = message.GetJson();
-    Json::Value::Members instances = series.getMemberNames();
-    
-    slices_.Reserve(instances.size());
-    
-    for (size_t i = 0; i < instances.size(); i++)
-    {
-      OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
-      
-      Orthanc::DicomMap dicom;
-      OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
-      
-      unsigned int frames;
-      if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
-      {
-        frames = 1;
-      }
-      
-      for (unsigned int frame = 0; frame < frames; frame++)
-      {
-        std::auto_ptr<Slice> slice(new Slice);
-        if (slice->ParseOrthancFrame(dicom, instances[i], frame))
-        {
-          OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
-          slices_.AddSlice(geometry, slice.release());
-        }
-        else
-        {
-          LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i];
-        }
-      }
-    }
-    
-    SortAndFinalizeSlices();
-  }
-  
-  void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    const Json::Value& tags = message.GetJson();
-    const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId();
-
-    OrthancPlugins::FullOrthancDataset dataset(tags);
-    
-    Orthanc::DicomMap dicom;
-    OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
-
-    unsigned int frames;
-    if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
-    {
-      frames = 1;
-    }
-    
-    LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)";
-    
-    for (unsigned int frame = 0; frame < frames; frame++)
-    {
-      std::auto_ptr<Slice> slice(new Slice);
-      if (slice->ParseOrthancFrame(dicom, instanceId, frame))
-      {
-        OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
-        slices_.AddSlice(geometry, slice.release());
-      }
-      else
-      {
-        LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        BroadcastMessage(SliceGeometryErrorMessage(*this));
-        return;
-      }
-    }
-    
-    SortAndFinalizeSlices();
-  }
-  
-  
-  void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    const Json::Value& tags = message.GetJson();
-    const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId();
-    unsigned int frame = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetFrame();
-
-    OrthancPlugins::FullOrthancDataset dataset(tags);
-    
-    state_ = State_GeometryReady;
-    
-    Orthanc::DicomMap dicom;
-    OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
-    
-    std::auto_ptr<Slice> slice(new Slice);
-    if (slice->ParseOrthancFrame(dicom, instanceId, frame))
-    {
-      LOG(INFO) << "Loaded instance geometry " << instanceId;
-
-      OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
-      slices_.AddSlice(geometry, slice.release());
-      
-      BroadcastMessage(SliceGeometryReadyMessage(*this));
-    }
-    else
-    {
-      LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      BroadcastMessage(SliceGeometryErrorMessage(*this));
-    }
-  }
-  
-  
-  void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message)
-  {
-    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
-    std::auto_ptr<Orthanc::ImageAccessor>  image;
-    
-    try
-    {
-      image.reset(new Orthanc::PngReader);
-      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize());
-    }
-    catch (Orthanc::OrthancException&)
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-    
-    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
-        image->GetHeight() != operation.GetSlice().GetHeight())
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-
-    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
-        Orthanc::PixelFormat_SignedGrayscale16)
-    {
-      if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-      {
-        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-      }
-      else
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-    }
-    
-    NotifySliceImageSuccess(operation, *image);
-  }
-  
-  void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message)
-  {
-    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
-    std::auto_ptr<Orthanc::ImageAccessor>  image;
-
-    try
-    {
-      image.reset(new Orthanc::PamReader);
-      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize());
-    }
-    catch (Orthanc::OrthancException&)
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-
-    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
-        image->GetHeight() != operation.GetSlice().GetHeight())
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-
-    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
-        Orthanc::PixelFormat_SignedGrayscale16)
-    {
-      if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-      {
-        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-      }
-      else
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-    }
-
-    NotifySliceImageSuccess(operation, *image);
-  }
-
-
-  void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
-
-    const Json::Value& encoded = message.GetJson();
-    if (encoded.type() != Json::objectValue ||
-        !encoded.isMember("Orthanc") ||
-        encoded["Orthanc"].type() != Json::objectValue)
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-    
-    const Json::Value& info = encoded["Orthanc"];
-    if (!info.isMember("PixelData") ||
-        !info.isMember("Stretched") ||
-        !info.isMember("Compression") ||
-        info["Compression"].type() != Json::stringValue ||
-        info["PixelData"].type() != Json::stringValue ||
-        info["Stretched"].type() != Json::booleanValue ||
-        info["Compression"].asString() != "Jpeg")
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-    
-    bool isSigned = false;
-    bool isStretched = info["Stretched"].asBool();
-    
-    if (info.isMember("IsSigned"))
-    {
-      if (info["IsSigned"].type() != Json::booleanValue)
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-      else
-      {
-        isSigned = info["IsSigned"].asBool();
-      }
-    }
-    
-    std::auto_ptr<Orthanc::ImageAccessor> reader;
-    
-    {
-      std::string jpeg;
-      //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
-      jpeg = base64_decode(info["PixelData"].asString());
-      
-      try
-      {
-        reader.reset(new Orthanc::JpegReader);
-        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-    }
-    
-    Orthanc::PixelFormat expectedFormat =
-      operation.GetSlice().GetConverter().GetExpectedPixelFormat();
-    
-    if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
-    {
-      if (expectedFormat != Orthanc::PixelFormat_RGB24)
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-      
-      if (isSigned || isStretched)
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-      else
-      {
-        NotifySliceImageSuccess(operation, *reader);
-        return;
-      }
-    }
-    
-    if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-    
-    if (!isStretched)
-    {
-      if (expectedFormat != reader->GetFormat())
-      {
-        NotifySliceImageError(operation);
-        return;
-      }
-      else
-      {
-        NotifySliceImageSuccess(operation, *reader);
-        return;
-      }
-    }
-    
-    int32_t stretchLow = 0;
-    int32_t stretchHigh = 0;
-    
-    if (!info.isMember("StretchLow") ||
-        !info.isMember("StretchHigh") ||
-        info["StretchLow"].type() != Json::intValue ||
-        info["StretchHigh"].type() != Json::intValue)
-    {
-      NotifySliceImageError(operation);
-      return;
-    }
-    
-    stretchLow = info["StretchLow"].asInt();
-    stretchHigh = info["StretchHigh"].asInt();
-    
-    if (stretchLow < -32768 ||
-        stretchHigh > 65535 ||
-        (stretchLow < 0 && stretchHigh > 32767))
-    {
-      // This range cannot be represented with a uint16_t or an int16_t
-      NotifySliceImageError(operation);
-      return;
-    }
-    
-    // Decode a grayscale JPEG 8bpp image coming from the Web viewer
-    std::auto_ptr<Orthanc::ImageAccessor> image
-      (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
-
-    Orthanc::ImageProcessing::Convert(*image, *reader);
-    reader.reset();
-    
-    float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-    
-    if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
-    {
-      float offset = static_cast<float>(stretchLow) / scaling;
-      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
-    }
-    
-    NotifySliceImageSuccess(operation, *image);
-  }
-
-
-  class StringImage : public Orthanc::ImageAccessor
-  {
-  private:
-    std::string  buffer_;
-    
-  public:
-    StringImage(Orthanc::PixelFormat format,
-                unsigned int width,
-                unsigned int height,
-                std::string& buffer)
-    {
-      if (buffer.size() != Orthanc::GetBytesPerPixel(format) * width * height)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-      
-      buffer_.swap(buffer);  // The source buffer is now empty
-      
-      void* data = (buffer_.empty() ? NULL : &buffer_[0]);
-      
-      AssignWritable(format, width, height,
-                     Orthanc::GetBytesPerPixel(format) * width, data);
-    }
-  };
-  
-  void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message)
-  {
-    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
-    Orthanc::GzipCompressor compressor;
-    
-    std::string raw;
-    compressor.Uncompress(raw, message.GetAnswer(), message.GetAnswerSize());
-    
-    const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation();
-    
-    if (info.GetBitsAllocated() == 32 &&
-        info.GetBitsStored() == 32 &&
-        info.GetHighBit() == 31 &&
-        info.GetChannelCount() == 1 &&
-        !info.IsSigned() &&
-        info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 &&
-        raw.size() == info.GetWidth() * info.GetHeight() * 4)
-    {
-      // This is the case of RT-DOSE (uint32_t values)
-      
-      std::auto_ptr<Orthanc::ImageAccessor> image
-        (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
-                         info.GetHeight(), raw));
-      
-      // TODO - Only for big endian
-      for (unsigned int y = 0; y < image->GetHeight(); y++)
-      {
-        uint32_t *p = reinterpret_cast<uint32_t*>(image->GetRow(y));
-        for (unsigned int x = 0; x < image->GetWidth(); x++, p++)
-        {
-          *p = le32toh(*p);
-        }
-      }
-      
-      NotifySliceImageSuccess(operation, *image);
-    }
-    else if (info.GetBitsAllocated() == 16 &&
-             info.GetBitsStored() == 16 &&
-             info.GetHighBit() == 15 &&
-             info.GetChannelCount() == 1 &&
-             !info.IsSigned() &&
-             info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 &&
-             raw.size() == info.GetWidth() * info.GetHeight() * 2)
-    {
-      std::auto_ptr<Orthanc::ImageAccessor> image
-        (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
-                         info.GetHeight(), raw));
-      
-      // TODO - Big endian ?
-      
-      NotifySliceImageSuccess(operation, *image);
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-  }
-  
-  
-  OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
-                                           OrthancApiClient& orthanc) :
-    OrthancStone::IObservable(broker),
-    OrthancStone::IObserver(broker),
-    orthanc_(orthanc),
-    state_(State_Initialization)
-  {
-  }
-  
-  
-  void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
-  {
-    if (state_ != State_Initialization)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      state_ = State_LoadingGeometry;
-      orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags",
-                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry),
-                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
-                            NULL);
-    }
-  }
-  
-  void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
-  {
-    if (state_ != State_Initialization)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      state_ = State_LoadingGeometry;
-      
-      // Tag "3004-000c" is "Grid Frame Offset Vector", which is
-      // mandatory to read RT DOSE, but is too long to be returned by default
-      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
-                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry),
-                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
-                            Operation::DownloadInstanceGeometry(instanceId));
-    }
-  }
-  
-  
-  void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId,
-                                              unsigned int frame)
-  {
-    if (state_ != State_Initialization)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      state_ = State_LoadingGeometry;
-
-      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags",
-                            new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry),
-                            new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
-                            Operation::DownloadFrameGeometry(instanceId, frame));
-    }
-  }
-  
-  
-  bool OrthancSlicesLoader::IsGeometryReady() const
-  {
-    return state_ == State_GeometryReady;
-  }
-  
-  
-  size_t OrthancSlicesLoader::GetSlicesCount() const
-  {
-    if (state_ != State_GeometryReady)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    
-    return slices_.GetSlicesCount();
-  }
-  
-  
-  const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
-  {
-    if (state_ != State_GeometryReady)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    return dynamic_cast<const Slice&>(slices_.GetSlicePayload(index));
-  }
-  
-  
-  bool OrthancSlicesLoader::LookupSlice(size_t& index,
-                                        const OrthancStone::CoordinateSystem3D& plane) const
-  {
-    if (state_ != State_GeometryReady)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    double distance;
-    return (slices_.LookupClosestSlice(index, distance, plane) &&
-            distance <= GetSlice(index).GetThickness() / 2.0);
-  }
-  
-  
-  void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
-                                                  size_t index)
-  {
-    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
-                       boost::lexical_cast<std::string>(slice.GetFrame()));
-    
-    switch (slice.GetConverter().GetExpectedPixelFormat())
-    {
-      case Orthanc::PixelFormat_RGB24:
-        uri += "/preview";
-        break;
-      
-      case Orthanc::PixelFormat_Grayscale16:
-        uri += "/image-uint16";
-        break;
-      
-      case Orthanc::PixelFormat_SignedGrayscale16:
-        uri += "/image-int16";
-        break;
-      
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    
-    orthanc_.GetBinaryAsync(uri, "image/png",
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        OrthancApiClient::BinaryResponseReadyMessage>
-          (*this, &OrthancSlicesLoader::ParseSliceImagePng),
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        IWebService::HttpRequestErrorMessage>
-          (*this, &OrthancSlicesLoader::OnSliceImageError),
-      Operation::DownloadSliceImage(
-        static_cast<unsigned int>(index), slice, OrthancStone::SliceImageQuality_FullPng));
-}
-  
-  void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
-                                                  size_t index)
-  {
-    std::string uri = 
-      ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
-      boost::lexical_cast<std::string>(slice.GetFrame()));
-
-    switch (slice.GetConverter().GetExpectedPixelFormat())
-    {
-      case Orthanc::PixelFormat_RGB24:
-        uri += "/preview";
-        break;
-
-      case Orthanc::PixelFormat_Grayscale16:
-        uri += "/image-uint16";
-        break;
-
-      case Orthanc::PixelFormat_SignedGrayscale16:
-        uri += "/image-int16";
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        OrthancApiClient::BinaryResponseReadyMessage>
-          (*this, &OrthancSlicesLoader::ParseSliceImagePam),
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        IWebService::HttpRequestErrorMessage>
-          (*this, &OrthancSlicesLoader::OnSliceImageError),
-      Operation::DownloadSliceImage(static_cast<unsigned int>(index), 
-                                    slice, OrthancStone::SliceImageQuality_FullPam));
-  }
-
-
-  
-  void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
-                                                   size_t index,
-                                                   OrthancStone::SliceImageQuality quality)
-  {
-    unsigned int value;
-    
-    switch (quality)
-    {
-      case OrthancStone::SliceImageQuality_Jpeg50:
-        value = 50;
-        break;
-
-      case OrthancStone::SliceImageQuality_Jpeg90:
-        value = 90;
-        break;
-
-      case OrthancStone::SliceImageQuality_Jpeg95:
-        value = 95;
-        break;
-      
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    
-    // This requires the official Web viewer plugin to be installed!
-    std::string uri = ("/web-viewer/instances/jpeg" +
-                       boost::lexical_cast<std::string>(value) +
-                       "-" + slice.GetOrthancInstanceId() + "_" +
-                       boost::lexical_cast<std::string>(slice.GetFrame()));
-
-    orthanc_.GetJsonAsync(uri,
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        OrthancApiClient::JsonResponseReadyMessage>
-          (*this, &OrthancSlicesLoader::ParseSliceImageJpeg),
-      new OrthancStone::Callable<OrthancSlicesLoader, 
-        IWebService::HttpRequestErrorMessage>
-          (*this, &OrthancSlicesLoader::OnSliceImageError),
-        Operation::DownloadSliceImage(
-          static_cast<unsigned int>(index), slice, quality));
-  }
-  
-  
-  
-  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
-                                                   OrthancStone::SliceImageQuality quality)
-  {
-    if (state_ != State_GeometryReady)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    
-    const Slice& slice = GetSlice(index);
-    
-    if (slice.HasOrthancDecoding())
-    {
-      switch (quality)
-      {
-        case OrthancStone::SliceImageQuality_FullPng:
-          ScheduleSliceImagePng(slice, index);
-          break;
-        case OrthancStone::SliceImageQuality_FullPam:
-          ScheduleSliceImagePam(slice, index);
-          break;
-        default:
-          ScheduleSliceImageJpeg(slice, index, quality);
-      }
-    }
-    else
-    {
-      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
-                         boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
-      orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(),
-        new OrthancStone::Callable<OrthancSlicesLoader, 
-          OrthancApiClient::BinaryResponseReadyMessage>
-            (*this, &OrthancSlicesLoader::ParseSliceRawImage),
-        new OrthancStone::Callable<OrthancSlicesLoader,
-          IWebService::HttpRequestErrorMessage>
-            (*this, &OrthancSlicesLoader::OnSliceImageError),
-        Operation::DownloadSliceRawImage(
-          static_cast<unsigned int>(index), slice));
-    }
-  }
-}
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Messages/IObservable.h"
-#include "../StoneEnumerations.h"
-#include "IWebService.h"
-#include "OrthancApiClient.h"
-#include "SlicesSorter.h"
-#include "Slice.h"
-
-#include <Core/Images/Image.h>
-
-
-namespace Deprecated
-{
-  class OrthancSlicesLoader : public OrthancStone::IObservable, public OrthancStone::IObserver
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryErrorMessage, OrthancSlicesLoader);
-
-    
-    class SliceImageReadyMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      unsigned int                   sliceIndex_;
-      const Slice&                   slice_;
-      const Orthanc::ImageAccessor&  image_;
-      OrthancStone::SliceImageQuality  effectiveQuality_;
-
-    public:
-      SliceImageReadyMessage(const OrthancSlicesLoader& origin,
-                             unsigned int sliceIndex,
-                             const Slice& slice,
-                             const Orthanc::ImageAccessor& image,
-                             OrthancStone::SliceImageQuality effectiveQuality) :
-        OriginMessage(origin),
-        sliceIndex_(sliceIndex),
-        slice_(slice),
-        image_(image),
-        effectiveQuality_(effectiveQuality)
-      {
-      }
-
-      unsigned int GetSliceIndex() const
-      {
-        return sliceIndex_;
-      }
-
-      const Slice& GetSlice() const
-      {
-        return slice_;
-      }
-
-      const Orthanc::ImageAccessor& GetImage() const
-      {
-        return image_;
-      }
-
-      OrthancStone::SliceImageQuality GetEffectiveQuality() const
-      {
-        return effectiveQuality_;
-      }        
-    };
-    
-
-    class SliceImageErrorMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      const Slice&       slice_;
-      unsigned int       sliceIndex_;
-      OrthancStone::SliceImageQuality  effectiveQuality_;
-
-    public:
-      SliceImageErrorMessage(const OrthancSlicesLoader& origin,
-                             unsigned int sliceIndex,
-                             const Slice& slice,
-                             OrthancStone::SliceImageQuality effectiveQuality) :
-        OriginMessage(origin),
-        slice_(slice),
-        sliceIndex_(sliceIndex),
-        effectiveQuality_(effectiveQuality)
-      {
-      }
-      unsigned int GetSliceIndex() const
-      {
-        return sliceIndex_;
-      }
-
-      const Slice& GetSlice() const
-      {
-        return slice_;
-      }
-
-      OrthancStone::SliceImageQuality GetEffectiveQuality() const
-      {
-        return effectiveQuality_;
-      }        
-    };
-    
-  private:
-    enum State
-    {
-      State_Error,
-      State_Initialization,
-      State_LoadingGeometry,
-      State_GeometryReady
-    };
-    
-    enum Mode
-    {
-      Mode_SeriesGeometry,
-      Mode_InstanceGeometry,
-      Mode_FrameGeometry,
-      Mode_LoadImage,
-      Mode_LoadRawImage,
-      Mode_LoadDicomFile
-    };
-
-    class Operation;
-
-    OrthancApiClient&  orthanc_;
-    State         state_;
-    OrthancStone::SlicesSorter  slices_;
-
-    void NotifySliceImageSuccess(const Operation& operation,
-                                 const Orthanc::ImageAccessor& image);
-    
-    void NotifySliceImageError(const Operation& operation);
-
-    void OnGeometryError(const IWebService::HttpRequestErrorMessage& message);
-
-    void OnSliceImageError(const IWebService::HttpRequestErrorMessage& message);
-
-    void ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message);
-
-    void ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message);
-
-    void ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message);
-
-    void ScheduleSliceImagePng(const Slice& slice,
-                               size_t index);
-
-    void ScheduleSliceImagePam(const Slice& slice,
-                               size_t index);
-
-    void ScheduleSliceImageJpeg(const Slice& slice,
-                                size_t index,
-                                OrthancStone::SliceImageQuality quality);
-
-    void SortAndFinalizeSlices();
-    
-  public:
-    OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
-                        //ISliceLoaderObserver& callback,
-                        OrthancApiClient& orthancApi);
-
-    void ScheduleLoadSeries(const std::string& seriesId);
-
-    void ScheduleLoadInstance(const std::string& instanceId);
-
-    void ScheduleLoadFrame(const std::string& instanceId,
-                           unsigned int frame);
-
-    bool IsGeometryReady() const;
-
-    size_t GetSlicesCount() const;
-
-    const Slice& GetSlice(size_t index) const;
-
-    bool LookupSlice(size_t& index,
-                     const OrthancStone::CoordinateSystem3D& plane) const;
-
-    void ScheduleLoadSliceImage(size_t index,
-                                OrthancStone::SliceImageQuality requestedQuality);
-  };
-}
--- a/Framework/Toolbox/ParallelSlices.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ParallelSlices.h"
-
-#include "GeometryToolbox.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  ParallelSlices::ParallelSlices()
-  {
-    LinearAlgebra::AssignVector(normal_, 0, 0, 1);
-  }
-
-
-  ParallelSlices::ParallelSlices(const ParallelSlices& other)
-  {
-    normal_ = other.normal_;
-
-    slices_.resize(other.slices_.size());
-
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      assert(other.slices_[i] != NULL);
-      slices_[i] = new CoordinateSystem3D(*other.slices_[i]);
-    }
-  }
-
-
-  ParallelSlices::~ParallelSlices()
-  {
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      if (slices_[i] != NULL)
-      {
-        delete slices_[i];
-        slices_[i] = NULL;
-      }
-    }
-  }
-
-
-  void ParallelSlices::AddSlice(const CoordinateSystem3D& slice)
-  {
-    if (slices_.empty())
-    {
-      normal_ = slice.GetNormal();
-      slices_.push_back(new CoordinateSystem3D(slice));
-    }
-    else if (GeometryToolbox::IsParallel(slice.GetNormal(), normal_))
-    {
-      slices_.push_back(new CoordinateSystem3D(slice));
-    }
-    else
-    {
-      LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
-  void ParallelSlices::AddSlice(const Vector& origin,
-                                const Vector& axisX,
-                                const Vector& axisY)
-  {
-    CoordinateSystem3D slice(origin, axisX, axisY);
-    AddSlice(slice);
-  }
-
-
-  const CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const
-  {
-    if (index >= slices_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      return *slices_[index];
-    }
-  }
-
-
-  bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice,
-                                           double& closestDistance,
-                                           const Vector& origin) const
-  {
-    if (slices_.empty())
-    {
-      return false;
-    }
-
-    double reference = boost::numeric::ublas::inner_prod(origin, normal_);
-
-    closestSlice = 0;
-    closestDistance = std::numeric_limits<double>::infinity();
-
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference);
-
-      if (distance < closestDistance)
-      {
-        closestSlice = i;
-        closestDistance = distance;
-      }
-    }
-
-    return true;
-  }
-
-
-  ParallelSlices* ParallelSlices::Reverse() const
-  {
-    std::auto_ptr<ParallelSlices> reversed(new ParallelSlices);
-
-    for (size_t i = slices_.size(); i > 0; i--)
-    {
-      const CoordinateSystem3D& slice = *slices_[i - 1];
-
-      reversed->AddSlice(slice.GetOrigin(),
-                         -slice.GetAxisX(),
-                         slice.GetAxisY());
-    }
-
-    return reversed.release();
-  }
-}
--- a/Framework/Toolbox/ParallelSlices.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CoordinateSystem3D.h"
-
-namespace OrthancStone
-{
-  class ParallelSlices : public boost::noncopyable
-  {
-  private:
-    Vector                            normal_;
-    std::vector<CoordinateSystem3D*>  slices_;
-    
-    ParallelSlices& operator= (const ParallelSlices& other);  // Forbidden
-
-  public:
-    ParallelSlices();
-
-    ParallelSlices(const ParallelSlices& other);
-
-    ~ParallelSlices();
-
-    const Vector& GetNormal() const
-    {
-      return normal_;
-    }
-
-    void AddSlice(const CoordinateSystem3D& slice);
-
-    void AddSlice(const Vector& origin,
-                  const Vector& axisX,
-                  const Vector& axisY);
-
-    size_t GetSliceCount() const
-    {
-      return slices_.size();
-    }
-
-    const CoordinateSystem3D& GetSlice(size_t index) const;
-
-    bool ComputeClosestSlice(size_t& closestSlice,
-                             double& closestDistance,
-                             const Vector& origin) const;
-
-    ParallelSlices* Reverse() const;
-  };
-}
--- a/Framework/Toolbox/ParallelSlicesCursor.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,221 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ParallelSlicesCursor.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  size_t ParallelSlicesCursor::GetDefaultSlice()
-  {
-    if (slices_.get() == NULL)
-    {
-      return 0;
-    }
-    else
-    {
-      return slices_->GetSliceCount() / 2;
-    }
-  }
-
-
-  size_t ParallelSlicesCursor::GetSliceCount()
-  {
-    if (slices_.get() == NULL)
-    {
-      return 0;
-    }
-    else
-    {
-      return slices_->GetSliceCount();
-    }
-  }
-
-
-  CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice)
-  {
-    if (slices_.get() == NULL)
-    {
-      return CoordinateSystem3D();
-    }
-    else
-    {
-      return slices_->GetSlice(slice);
-    }
-  }
-
-
-  void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices)
-  {
-    slices_.reset(new ParallelSlices(slices));
-
-    currentSlice_ = GetDefaultSlice();
-  }
-
-
-  CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice()
-  {
-    if (slices_.get() != NULL &&
-        currentSlice_ < slices_->GetSliceCount())
-    {
-      return slices_->GetSlice(currentSlice_);
-    }
-    else
-    {
-      return CoordinateSystem3D();  // No slice is available, return the canonical geometry
-    }
-  }
-
-
-  bool ParallelSlicesCursor::SetDefaultSlice()
-  {
-    size_t slice = GetDefaultSlice();
-
-    if (currentSlice_ != slice)
-    {
-      currentSlice_ = slice;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool ParallelSlicesCursor::ApplyOffset(SliceOffsetMode mode,
-                                         int offset)
-  {
-    if (slices_.get() == NULL)
-    {
-      return false;
-    }
-
-    int count = static_cast<int>(slices_->GetSliceCount());
-    if (count == 0)
-    {
-      return false;
-    }
-
-    int slice;
-    if (static_cast<int>(currentSlice_) >= count)
-    {
-      slice = count - 1;
-    }
-    else
-    {
-      slice = static_cast<int>(currentSlice_);
-    }
-
-    switch (mode)
-    {
-      case SliceOffsetMode_Absolute:
-      {
-        slice = offset;
-        break;
-      }
-
-      case SliceOffsetMode_Relative:
-      {
-        slice += offset;
-        break;
-      }
-
-      case SliceOffsetMode_Loop:
-      {
-        slice += offset;
-        while (slice < 0)
-        {
-          slice += count;
-        }
-
-        while (slice >= count)
-        {
-          slice -= count;
-        }
-
-        break;
-      }
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    if (slice < 0)
-    {
-      slice = 0;
-    }
-
-    if (slice >= count)
-    {
-      slice = count - 1;
-    }
-
-    if (slice != static_cast<int>(currentSlice_))
-    {
-      currentSlice_ = static_cast<int>(slice);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  bool ParallelSlicesCursor::ApplyWheelEvent(MouseWheelDirection direction,
-                                             KeyboardModifiers modifiers)
-  {
-    int offset = (modifiers & KeyboardModifiers_Control ? 10 : 1);
-
-    switch (direction)
-    {
-      case MouseWheelDirection_Down:
-        return ApplyOffset(SliceOffsetMode_Relative, -offset);
-
-      case MouseWheelDirection_Up:
-        return ApplyOffset(SliceOffsetMode_Relative, offset);
-
-      default:
-        return false;
-    }
-  }
-
-
-  bool ParallelSlicesCursor::LookupSliceContainingPoint(const Vector& p)
-  {
-    size_t slice;
-    double distance;
-
-    if (slices_.get() != NULL &&
-        slices_->ComputeClosestSlice(slice, distance, p))
-    {
-      if (currentSlice_ != slice)
-      {
-        currentSlice_ = slice;
-        return true;
-      }
-    }
-
-    return false;
-  }
-}
--- a/Framework/Toolbox/ParallelSlicesCursor.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ParallelSlices.h"
-#include "../StoneEnumerations.h"
-
-namespace OrthancStone
-{
-  class ParallelSlicesCursor : public boost::noncopyable
-  {
-  private:
-    std::auto_ptr<ParallelSlices>  slices_;
-    size_t                         currentSlice_;
-
-    size_t GetDefaultSlice();
-
-  public:
-    ParallelSlicesCursor() :
-      currentSlice_(0)
-    {
-    }
-
-    void SetGeometry(const ParallelSlices& slices);
-
-    size_t GetSliceCount();
-
-    CoordinateSystem3D GetSlice(size_t slice);
-
-    CoordinateSystem3D GetCurrentSlice();
-
-    // Returns "true" iff. the slice has actually changed
-    bool SetDefaultSlice();
-
-    // Returns "true" iff. the slice has actually changed
-    bool ApplyOffset(SliceOffsetMode mode,
-                     int offset);
-
-    // Returns "true" iff. the slice has actually changed
-    bool ApplyWheelEvent(MouseWheelDirection direction,
-                         KeyboardModifiers modifiers);
-
-    // Returns "true" iff. the slice has actually changed
-    bool LookupSliceContainingPoint(const Vector& p);
-  };
-}
--- a/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -343,6 +343,7 @@
                                  float& maxValue,
                                  const Matrix& M_view,
                                  const ImageBuffer3D& source,
+                                 const VolumeImageGeometry& geometry,
                                  double pixelSpacing,
                                  unsigned int countSlices,
                                  ImageInterpolation shearInterpolation,
@@ -385,8 +386,8 @@
     
     // Compute the "world" matrix that maps the source volume to the
     // (0,0,0)->(1,1,1) unit cube
-    Vector origin = source.GetGeometry().GetCoordinates(0, 0, 0);
-    Vector ps = source.GetGeometry().GetVoxelDimensions(VolumeProjection_Axial);
+    Vector origin = geometry.GetCoordinates(0, 0, 0);
+    Vector ps = geometry.GetVoxelDimensions(VolumeProjection_Axial);
     Matrix world = LinearAlgebra::Product(
       GeometryToolbox::CreateScalingMatrix(1.0 / ps[0], 1.0 / ps[1], 1.0 / ps[2]),
       GeometryToolbox::CreateTranslationMatrix(-origin[0], -origin[1], -origin[2]));
@@ -598,6 +599,7 @@
                                   float& maxValue,
                                   const Matrix& M_view,
                                   const ImageBuffer3D& source,
+                                  const VolumeImageGeometry& geometry,
                                   bool mip,
                                   double pixelSpacing,
                                   unsigned int countSlices,
@@ -607,13 +609,13 @@
     if (mip)
     {
       ApplyAxialInternal<SourceFormat, TargetFormat, true>
-        (target, maxValue, M_view, source, pixelSpacing,
+        (target, maxValue, M_view, source, geometry, pixelSpacing,
          countSlices, shearInterpolation, warpInterpolation);
     }
     else
     {
       ApplyAxialInternal<SourceFormat, TargetFormat, false>
-        (target, maxValue, M_view, source, pixelSpacing,
+        (target, maxValue, M_view, source, geometry, pixelSpacing,
          countSlices, shearInterpolation, warpInterpolation);
     } 
   }
@@ -623,6 +625,7 @@
   ShearWarpProjectiveTransform::ApplyAxial(float& maxValue,
                                            const Matrix& M_view,
                                            const ImageBuffer3D& source,
+                                           const VolumeImageGeometry& geometry,
                                            Orthanc::PixelFormat targetFormat,
                                            unsigned int targetWidth,
                                            unsigned int targetHeight,
@@ -640,7 +643,7 @@
     {
       ApplyAxialInternal2<Orthanc::PixelFormat_Grayscale16,
                           Orthanc::PixelFormat_Grayscale16>
-        (*target, maxValue, M_view, source, mip, pixelSpacing,
+        (*target, maxValue, M_view, source, geometry, mip, pixelSpacing,
          countSlices, shearInterpolation, warpInterpolation);
     }
     else if (source.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16 &&
@@ -648,7 +651,7 @@
     {
       ApplyAxialInternal2<Orthanc::PixelFormat_SignedGrayscale16,
                           Orthanc::PixelFormat_SignedGrayscale16>
-        (*target, maxValue, M_view, source, mip, pixelSpacing,
+        (*target, maxValue, M_view, source, geometry, mip, pixelSpacing,
          countSlices, shearInterpolation, warpInterpolation);
     }
     else
--- a/Framework/Toolbox/ShearWarpProjectiveTransform.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/ShearWarpProjectiveTransform.h	Mon Jun 24 14:35:00 2019 +0200
@@ -92,6 +92,7 @@
     static Orthanc::ImageAccessor* ApplyAxial(float& maxValue,
                                               const Matrix& M_view,  // cf. "CalibrateView()"
                                               const ImageBuffer3D& source,
+                                              const VolumeImageGeometry& geometry,
                                               Orthanc::PixelFormat targetFormat,
                                               unsigned int targetWidth,
                                               unsigned int targetHeight,
--- a/Framework/Toolbox/Slice.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,367 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Slice.h"
-
-#include "../StoneEnumerations.h"
-#include "GeometryToolbox.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-
-#include <boost/lexical_cast.hpp>
-
-namespace Deprecated
-{
-  static bool ParseDouble(double& target,
-                          const std::string& source)
-  {
-    try
-    {
-      target = boost::lexical_cast<double>(source);
-      return true;
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      return false;
-    }
-  }
-
-  Slice* Slice::Clone() const
-  {
-    std::auto_ptr<Slice> target(new Slice());
-
-    target->type_ = type_;
-    target->orthancInstanceId_ = orthancInstanceId_;
-    target->sopClassUid_ = sopClassUid_;
-    target->frame_ = frame_;
-    target->frameCount_ = frameCount_;
-    target->geometry_ = geometry_;
-    target->pixelSpacingX_ = pixelSpacingX_;
-    target->pixelSpacingY_ = pixelSpacingY_;
-    target->thickness_ = thickness_;
-    target->width_ = width_;
-    target->height_ = height_;
-    target->converter_ = converter_;
-    if (imageInformation_.get() != NULL)
-      target->imageInformation_.reset(imageInformation_->Clone());
-
-    return target.release();
-  }
-  
-  bool Slice::ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset,
-                                    unsigned int frame)
-  {
-    // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
-
-    {
-      std::string increment;
-
-      if (dataset.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
-      {
-        Orthanc::Toolbox::ToUpperCase(increment);
-        if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
-        {
-          LOG(ERROR) << "Bad value for the \"FrameIncrementPointer\" tag";
-          return false;
-        }
-      }
-    }
-
-    std::string offsetTag;
-
-    if (!dataset.CopyToString(offsetTag, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, false) ||
-        offsetTag.empty())
-    {
-      LOG(ERROR) << "Cannot read the \"GridFrameOffsetVector\" tag, check you are using Orthanc >= 1.3.1";
-      return false;
-    }
-
-    std::vector<std::string> offsets;
-    Orthanc::Toolbox::TokenizeString(offsets, offsetTag, '\\');
-
-    if (frameCount_ <= 1 ||
-        offsets.size() < frameCount_ ||
-        offsets.size() < 2 ||
-        frame >= frameCount_)
-    {
-      LOG(ERROR) << "No information about the 3D location of some slice(s) in a RT DOSE";
-      return false;
-    }
-
-    double offset0, offset1, z;
-
-    if (!ParseDouble(offset0, offsets[0]) ||
-        !ParseDouble(offset1, offsets[1]) ||
-        !ParseDouble(z, offsets[frame]))
-    {
-      LOG(ERROR) << "Invalid syntax";
-      return false;
-    }
-
-    if (!OrthancStone::LinearAlgebra::IsCloseToZero(offset0))
-    {
-      LOG(ERROR) << "Invalid syntax";
-      return false;
-    }
-
-    geometry_ = OrthancStone::CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(),
-                                                 //+ 650 * geometry_.GetAxisX(),
-                                                 geometry_.GetAxisX(),
-                                                 geometry_.GetAxisY());
-
-    thickness_ = offset1 - offset0;
-    if (thickness_ < 0)
-    {
-      thickness_ = -thickness_;
-    }
-
-    return true;
-  }
-
-  
-  bool Slice::ParseOrthancFrame(const Orthanc::DicomMap& dataset,
-                                const std::string& instanceId,
-                                unsigned int frame)
-  {
-    orthancInstanceId_ = instanceId;
-    frame_ = frame;
-    type_ = Type_OrthancDecodableFrame;
-    imageInformation_.reset(new Orthanc::DicomImageInformation(dataset));
-
-    if (!dataset.CopyToString(sopClassUid_, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) ||
-        sopClassUid_.empty())
-    {
-      LOG(ERROR) << "Instance without a SOP class UID";
-      return false; 
-    }
-
-    if (!dataset.ParseUnsignedInteger32(frameCount_, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
-    {
-      frameCount_ = 1;   // Assume instance with one frame
-    }
-
-    if (frame >= frameCount_)
-    {
-      return false;
-    }
-
-    if (!dataset.ParseUnsignedInteger32(width_, Orthanc::DICOM_TAG_COLUMNS) ||
-        !dataset.ParseUnsignedInteger32(height_, Orthanc::DICOM_TAG_ROWS))
-    {
-      return false;
-    }
-
-    thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
-
-    std::string tmp;
-    if (dataset.CopyToString(tmp, Orthanc::DICOM_TAG_SLICE_THICKNESS, false))
-    {
-      if (!tmp.empty() &&
-          !ParseDouble(thickness_, tmp))
-      {
-        return false;  // Syntax error
-      }
-    }
-    
-    converter_.ReadParameters(dataset);
-
-    OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset);
-
-    std::string position, orientation;
-    if (dataset.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
-        dataset.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
-    {
-      geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
-
-      bool ok = true;
-
-      switch (OrthancStone::StringToSopClassUid(sopClassUid_))
-      {
-        case OrthancStone::SopClassUid_RTDose:
-          type_ = Type_OrthancRawFrame;
-          ok = ComputeRTDoseGeometry(dataset, frame);
-          break;
-            
-        default:
-          break;
-      }
-
-      if (!ok)
-      {
-        LOG(ERROR) << "Cannot deduce the 3D location of frame " << frame
-                   << " in instance " << instanceId << ", whose SOP class UID is: " << sopClassUid_;
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  
-  const std::string Slice::GetOrthancInstanceId() const
-  {
-    if (type_ == Type_OrthancDecodableFrame ||
-        type_ == Type_OrthancRawFrame)
-    {
-      return orthancInstanceId_;
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }   
-  }
-
-  
-  unsigned int Slice::GetFrame() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return frame_;
-  }
-
-  
-  const OrthancStone::CoordinateSystem3D& Slice::GetGeometry() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return geometry_;
-  }
-
-  
-  double Slice::GetThickness() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return thickness_;
-  }
-
-  
-  double Slice::GetPixelSpacingX() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return pixelSpacingX_;
-  }
-
-  
-  double Slice::GetPixelSpacingY() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return pixelSpacingY_;
-  }
-
-  
-  unsigned int Slice::GetWidth() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return width_;
-  }
-
-  
-  unsigned int Slice::GetHeight() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return height_;
-  }
-
-
-  const DicomFrameConverter& Slice::GetConverter() const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-        
-    return converter_;
-  }
-
-
-  bool Slice::ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const
-  {
-    if (type_ == Type_Invalid)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    bool opposite;
-    return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite,
-                                                                GetGeometry().GetNormal(),
-                                                                plane.GetNormal()) &&
-            OrthancStone::LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
-                                                GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
-                                                thickness_ / 2.0));
-  }
-
-  
-  void Slice::GetExtent(std::vector<OrthancStone::Vector>& points) const
-  {
-    double sx = GetPixelSpacingX();
-    double sy = GetPixelSpacingY();
-    double w = static_cast<double>(GetWidth());
-    double h = static_cast<double>(GetHeight());
-
-    points.clear();
-    points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5      * sx, -0.5      * sy));
-    points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, -0.5      * sy));
-    points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5      * sx, (h - 0.5) * sy));
-    points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy));
-  }
-
-
-  const Orthanc::DicomImageInformation& Slice::GetImageInformation() const
-  {
-    if (imageInformation_.get() == NULL)
-    {
-      // Only available if constructing the "Slice" object with a DICOM map
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return *imageInformation_;
-    }
-  }
-}
--- a/Framework/Toolbox/Slice.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CoordinateSystem3D.h"
-#include "DicomFrameConverter.h"
-
-#include <Core/DicomFormat/DicomImageInformation.h>
-#include <Core/IDynamicObject.h>
-
-namespace Deprecated
-{
-  // TODO - Remove this class
-  class Slice :
-    public Orthanc::IDynamicObject  /* to be used as a payload of SlicesSorter */
-  {
-  private:
-    enum Type
-    {
-      Type_Invalid,
-      Type_Standalone,
-      Type_OrthancDecodableFrame,
-      Type_OrthancRawFrame
-      // TODO A slice could come from some DICOM file (URL)
-    };
-
-    bool ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset,
-                               unsigned int frame);
-
-    Type                 type_;
-    std::string          orthancInstanceId_;
-    std::string          sopClassUid_;
-    unsigned int         frame_;
-    unsigned int         frameCount_;   // TODO : Redundant with "imageInformation_"
-    OrthancStone::CoordinateSystem3D   geometry_;
-    double               pixelSpacingX_;
-    double               pixelSpacingY_;
-    double               thickness_;
-    unsigned int         width_;   // TODO : Redundant with "imageInformation_"
-    unsigned int         height_;   // TODO : Redundant with "imageInformation_"
-    DicomFrameConverter  converter_;   // TODO : Partially redundant with "imageInformation_"
-
-    std::auto_ptr<Orthanc::DicomImageInformation>  imageInformation_;
-
-  public:
-    Slice() :
-      type_(Type_Invalid)
-    {
-    }
-
-
-    // this constructor is used to reference, i.e, a slice that is being loaded
-    Slice(const std::string& orthancInstanceId,
-          unsigned int frame) :
-      type_(Type_Invalid),
-      orthancInstanceId_(orthancInstanceId),
-      frame_(frame)
-    {        
-    }
-
-    // TODO Is this constructor the best way to go to tackle missing
-    // layers within SliceViewerWidget?
-    Slice(const OrthancStone::CoordinateSystem3D& plane,
-          double thickness) :
-      type_(Type_Standalone),
-      frame_(0),
-      frameCount_(0),
-      geometry_(plane),
-      pixelSpacingX_(1),
-      pixelSpacingY_(1),
-      thickness_(thickness),
-      width_(0),
-      height_(0)
-    {      
-    }
-
-    Slice(const OrthancStone::CoordinateSystem3D& plane,
-          double pixelSpacingX,
-          double pixelSpacingY,
-          double thickness,
-          unsigned int width,
-          unsigned int height,
-          const DicomFrameConverter& converter) :
-      type_(Type_Standalone),
-      frameCount_(1),
-      geometry_(plane),
-      pixelSpacingX_(pixelSpacingX),
-      pixelSpacingY_(pixelSpacingY),
-      thickness_(thickness),
-      width_(width),
-      height_(height),
-      converter_(converter)
-    {
-    }
-
-    bool IsValid() const
-    {
-      return type_ != Type_Invalid;
-    } 
-
-    bool ParseOrthancFrame(const Orthanc::DicomMap& dataset,
-                           const std::string& instanceId,
-                           unsigned int frame);
-
-    bool HasOrthancDecoding() const
-    {
-      return type_ == Type_OrthancDecodableFrame;
-    }
-
-    const std::string GetOrthancInstanceId() const;
-
-    unsigned int GetFrame() const;
-
-    const OrthancStone::CoordinateSystem3D& GetGeometry() const;
-
-    double GetThickness() const;
-
-    double GetPixelSpacingX() const;
-
-    double GetPixelSpacingY() const;
-
-    unsigned int GetWidth() const;
-
-    unsigned int GetHeight() const;
-
-    const DicomFrameConverter& GetConverter() const;
-
-    bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const;
-
-    void GetExtent(std::vector<OrthancStone::Vector>& points) const;
-
-    const Orthanc::DicomImageInformation& GetImageInformation() const;
-
-    Slice* Clone() const;
-  };
-}
--- a/Framework/Toolbox/SlicesSorter.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Toolbox/SlicesSorter.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -271,7 +271,7 @@
       assert(slices_[i] != NULL);
 
       double tmp;
-      if (CoordinateSystem3D::GetDistance(tmp, slices_[i]->GetGeometry(), slice))
+      if (CoordinateSystem3D::ComputeDistance(tmp, slices_[i]->GetGeometry(), slice))
       {
         if (!found ||
             tmp < distance)
--- a/Framework/Toolbox/ViewportGeometry.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ViewportGeometry.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-#include <boost/math/special_functions/round.hpp>
-
-namespace Deprecated
-{
-  void ViewportGeometry::ComputeTransform()
-  {
-    // The following lines must be read in reverse order!
-    cairo_matrix_t tmp;
-    
-    // Bring the center of the scene to the center of the view
-    cairo_matrix_init_translate(&transform_, 
-                                panX_ + static_cast<double>(width_) / 2.0, 
-                                panY_ + static_cast<double>(height_) / 2.0);
-
-    // Apply the zoom around (0,0)
-    cairo_matrix_init_scale(&tmp, zoom_, zoom_);
-    cairo_matrix_multiply(&transform_, &tmp, &transform_);
-
-    // Bring the center of the scene to (0,0)
-    cairo_matrix_init_translate(&tmp,
-                                -(sceneExtent_.GetX1() + sceneExtent_.GetX2()) / 2.0,
-                                -(sceneExtent_.GetY1() + sceneExtent_.GetY2()) / 2.0);
-    cairo_matrix_multiply(&transform_, &tmp, &transform_);
-  }
-
-
-  ViewportGeometry::ViewportGeometry()
-  {
-    width_ = 0;
-    height_ = 0;
-
-    zoom_ = 1;
-    panX_ = 0;
-    panY_ = 0;
-
-    ComputeTransform();
-  }
-
-
-  void ViewportGeometry::SetDisplaySize(unsigned int width,
-                                        unsigned int height)
-  {
-    if (width_ != width ||
-        height_ != height)
-    {
-      LOG(INFO) << "New display size: " << width << "x" << height;
-
-      width_ = width;
-      height_ = height;
-
-      ComputeTransform();
-    }
-  }
-
-
-  void ViewportGeometry::SetSceneExtent(const OrthancStone::Extent2D& extent)
-  {
-    LOG(INFO) << "New scene extent: ("
-              << extent.GetX1() << "," << extent.GetY1() << ") => ("
-              << extent.GetX2() << "," << extent.GetY2() << ")";
-
-    sceneExtent_ = extent;
-    ComputeTransform();
-  }
-
-
-  void ViewportGeometry::MapDisplayToScene(double& sceneX /* out */,
-                                           double& sceneY /* out */,
-                                           double x,
-                                           double y) const
-  {
-    cairo_matrix_t transform = transform_;
-
-    if (cairo_matrix_invert(&transform) != CAIRO_STATUS_SUCCESS)
-    {
-      LOG(ERROR) << "Cannot invert singular matrix";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    sceneX = x;
-    sceneY = y;
-    cairo_matrix_transform_point(&transform, &sceneX, &sceneY);
-  }
-
-
-  void ViewportGeometry::MapSceneToDisplay(int& displayX /* out */,
-                                           int& displayY /* out */,
-                                           double x,
-                                           double y) const
-  {
-    cairo_matrix_transform_point(&transform_, &x, &y);
-
-    displayX = static_cast<int>(boost::math::iround(x));
-    displayY = static_cast<int>(boost::math::iround(y));
-  }
-
-
-  void ViewportGeometry::MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
-                                               const std::vector<Touch>& displayTouches) const
-  {
-    double sceneX, sceneY;
-    sceneTouches.clear();
-    for (size_t t = 0; t < displayTouches.size(); t++)
-    {
-      MapPixelCenterToScene(
-        sceneX,
-        sceneY, 
-        static_cast<int>(displayTouches[t].x), 
-        static_cast<int>(displayTouches[t].y));
-      
-      sceneTouches.push_back(Touch((float)sceneX, (float)sceneY));
-    }
-  }
-
-  void ViewportGeometry::MapPixelCenterToScene(double& sceneX,
-                                               double& sceneY,
-                                               int x,
-                                               int y) const
-  {
-    // Take the center of the pixel
-    MapDisplayToScene(sceneX, sceneY,
-                      static_cast<double>(x) + 0.5,
-                      static_cast<double>(y) + 0.5);
-  }
-
-
-  void ViewportGeometry::FitContent()
-  {
-    if (width_ > 0 &&
-        height_ > 0 &&
-        !sceneExtent_.IsEmpty())
-    {
-      double zoomX = static_cast<double>(width_) / (sceneExtent_.GetX2() - sceneExtent_.GetX1());
-      double zoomY = static_cast<double>(height_) / (sceneExtent_.GetY2() - sceneExtent_.GetY1());
-      zoom_ = zoomX < zoomY ? zoomX : zoomY;
-
-      panX_ = 0;
-      panY_ = 0;
-
-      ComputeTransform();
-    }
-  }
-
-
-  void ViewportGeometry::ApplyTransform(OrthancStone::CairoContext& context) const
-  {
-    cairo_set_matrix(context.GetObject(), &transform_);
-  }
-
-
-  void ViewportGeometry::GetPan(double& x,
-                                double& y) const
-  {
-    x = panX_;
-    y = panY_;
-  }
-
-
-  void ViewportGeometry::SetPan(double x,
-                                double y)
-  {
-    panX_ = x;
-    panY_ = y;
-    ComputeTransform();
-  }
-
-
-  void ViewportGeometry::SetZoom(double zoom)
-  {
-    zoom_ = zoom;
-    ComputeTransform();
-  }
-
-
-  OrthancStone::Matrix ViewportGeometry::GetMatrix() const
-  {
-    OrthancStone::Matrix m(3, 3);
-
-    m(0, 0) = transform_.xx;
-    m(0, 1) = transform_.xy;
-    m(0, 2) = transform_.x0;
-    m(1, 0) = transform_.yx;
-    m(1, 1) = transform_.yy;
-    m(1, 2) = transform_.y0;
-    m(2, 0) = 0;
-    m(2, 1) = 0;
-    m(2, 2) = 1;
-    
-    return m;
-  }
-}
--- a/Framework/Toolbox/ViewportGeometry.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Viewport/CairoContext.h"
-#include "Extent2D.h"
-#include "LinearAlgebra.h"
-#include "../Viewport/IMouseTracker.h"  // to include "Touch" definition
-
-namespace Deprecated
-{
-  class ViewportGeometry
-  {
-  private:
-    // Extent of the scene (in world units)
-    OrthancStone::Extent2D   sceneExtent_;
-
-    // Size of the display (in pixels)
-    unsigned int  width_;
-    unsigned int  height_;
-
-    // Zoom/pan
-    double   zoom_;
-    double   panX_;  // In pixels (display units)
-    double   panY_;
-
-    cairo_matrix_t  transform_;  // Scene-to-display transformation
-
-    void ComputeTransform();
-
-  public:
-    ViewportGeometry();
-
-    void SetDisplaySize(unsigned int width,
-                        unsigned int height);
-
-    void SetSceneExtent(const OrthancStone::Extent2D& extent);
-
-    const OrthancStone::Extent2D& GetSceneExtent() const
-    {
-      return sceneExtent_;
-    }
-
-    void MapDisplayToScene(double& sceneX /* out */,
-                           double& sceneY /* out */,
-                           double x,
-                           double y) const;
-
-    void MapPixelCenterToScene(double& sceneX /* out */,
-                               double& sceneY /* out */,
-                               int x,
-                               int y) const;
-
-    void MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
-                               const std::vector<Touch>& displayTouches) const;
-
-    void MapSceneToDisplay(int& displayX /* out */,
-                           int& displayY /* out */,
-                           double x,
-                           double y) const;
-
-    unsigned int GetDisplayWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetDisplayHeight() const
-    {
-      return height_;
-    }
-
-    double GetZoom() const
-    {
-      return zoom_;
-    }
-
-    void FitContent();
-
-    void ApplyTransform(OrthancStone::CairoContext& context) const;
-
-    void GetPan(double& x,
-                double& y) const;
-
-    void SetPan(double x,
-                double y);
-
-    void SetZoom(double zoom);
-
-    OrthancStone::Matrix GetMatrix() const;
-  };
-}
--- a/Framework/Toolbox/VolumeImageGeometry.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,309 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "VolumeImageGeometry.h"
-
-#include "../Toolbox/GeometryToolbox.h"
-
-#include <Core/OrthancException.h>
-
-
-namespace OrthancStone
-{
-  void VolumeImageGeometry::Invalidate()
-  {
-    Vector p = (axialGeometry_.GetOrigin() +
-                static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal());
-        
-    coronalGeometry_ = CoordinateSystem3D(p,
-                                          axialGeometry_.GetAxisX(),
-                                          -axialGeometry_.GetNormal());
-    
-    sagittalGeometry_ = CoordinateSystem3D(p,
-                                           axialGeometry_.GetAxisY(),
-                                           axialGeometry_.GetNormal());
-
-    Vector origin = (
-      axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0],
-                                                -0.5 * voxelDimensions_[1]) -
-      0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal());
-
-    Vector scaling;
-    
-    if (width_ == 0 ||
-        height_ == 0 ||
-        depth_ == 0)
-    {
-      LinearAlgebra::AssignVector(scaling, 1, 1, 1);
-    }
-    else
-    {
-      scaling = (
-        axialGeometry_.GetAxisX() * voxelDimensions_[0] * static_cast<double>(width_) +
-        axialGeometry_.GetAxisY() * voxelDimensions_[1] * static_cast<double>(height_) +
-        axialGeometry_.GetNormal() * voxelDimensions_[2] * static_cast<double>(depth_));
-    }
-
-    transform_ = LinearAlgebra::Product(
-      GeometryToolbox::CreateTranslationMatrix(origin[0], origin[1], origin[2]),
-      GeometryToolbox::CreateScalingMatrix(scaling[0], scaling[1], scaling[2]));
-
-    LinearAlgebra::InvertMatrix(transformInverse_, transform_);
-  }
-
-  
-  VolumeImageGeometry::VolumeImageGeometry() :
-    width_(0),
-    height_(0),
-    depth_(0)
-  {
-    LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
-    Invalidate();
-  }
-
-
-  void VolumeImageGeometry::SetSize(unsigned int width,
-                                    unsigned int height,
-                                    unsigned int depth)
-  {
-    width_ = width;
-    height_ = height;
-    depth_ = depth;
-    Invalidate();
-  }
-
-  
-  void VolumeImageGeometry::SetAxialGeometry(const CoordinateSystem3D& geometry)
-  {
-    axialGeometry_ = geometry;
-    Invalidate();
-  }
-
-
-  void VolumeImageGeometry::SetVoxelDimensions(double x,
-                                               double y,
-                                               double z)
-  {
-    if (x <= 0 ||
-        y <= 0 ||
-        z <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      LinearAlgebra::AssignVector(voxelDimensions_, x, y, z);
-      Invalidate();
-    }
-  }
-
-
-  const CoordinateSystem3D& VolumeImageGeometry::GetProjectionGeometry(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return axialGeometry_;
-
-      case VolumeProjection_Coronal:
-        return coronalGeometry_;
-
-      case VolumeProjection_Sagittal:
-        return sagittalGeometry_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-  
-  Vector VolumeImageGeometry::GetVoxelDimensions(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return voxelDimensions_;
-
-      case VolumeProjection_Coronal:
-        return LinearAlgebra::CreateVector(voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
-
-      case VolumeProjection_Sagittal:
-        return LinearAlgebra::CreateVector(voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  unsigned int VolumeImageGeometry::GetProjectionWidth(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return width_;
-
-      case VolumeProjection_Coronal:
-        return width_;
-
-      case VolumeProjection_Sagittal:
-        return height_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  unsigned int VolumeImageGeometry::GetProjectionHeight(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return height_;
-
-      case VolumeProjection_Coronal:
-        return depth_;
-
-      case VolumeProjection_Sagittal:
-        return depth_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  unsigned int VolumeImageGeometry::GetProjectionDepth(VolumeProjection projection) const
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return depth_;
-
-      case VolumeProjection_Coronal:
-        return height_;
-
-      case VolumeProjection_Sagittal:
-        return width_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }    
-  }
-
-
-  Vector VolumeImageGeometry::GetCoordinates(float x,
-                                             float y,
-                                             float z) const
-  {
-    Vector p = LinearAlgebra::Product(transform_, LinearAlgebra::CreateVector(x, y, z, 1));
-
-    assert(LinearAlgebra::IsNear(p[3], 1));  // Affine transform, no perspective effect
-
-    // Back to non-homogeneous coordinates
-    return LinearAlgebra::CreateVector(p[0], p[1], p[2]);
-  }
-
-
-  bool VolumeImageGeometry::DetectProjection(VolumeProjection& projection,
-                                             const Vector& planeNormal) const
-  {
-    if (GeometryToolbox::IsParallel(planeNormal, axialGeometry_.GetNormal()))
-    {
-      projection = VolumeProjection_Axial;
-      return true;
-    }
-    else if (GeometryToolbox::IsParallel(planeNormal, coronalGeometry_.GetNormal()))
-    {
-      projection = VolumeProjection_Coronal;
-      return true;
-    }
-    else if (GeometryToolbox::IsParallel(planeNormal, sagittalGeometry_.GetNormal()))
-    {
-      projection = VolumeProjection_Sagittal;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  bool VolumeImageGeometry::DetectSlice(VolumeProjection& projection,
-                                        unsigned int& slice,
-                                        const CoordinateSystem3D& plane) const
-  {
-    if (!DetectProjection(projection, plane.GetNormal()))
-    {
-      return false;
-    }
-
-    // Transforms the coordinates of the origin of the plane, into the
-    // coordinates of the axial geometry
-    const Vector& origin = plane.GetOrigin();
-    Vector p = LinearAlgebra::Product(
-      transformInverse_,
-      LinearAlgebra::CreateVector(origin[0], origin[1], origin[2], 1));
-
-    assert(LinearAlgebra::IsNear(p[3], 1));
-
-    double z;
-
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        z = p[2];
-        break;
-
-      case VolumeProjection_Coronal:
-        z = p[1];
-        break;
-
-      case VolumeProjection_Sagittal:
-        z = p[0];
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    const unsigned int projectionDepth = GetProjectionDepth(projection);
-    
-    z *= static_cast<double>(projectionDepth);
-    if (z < 0)
-    {
-      return false;
-    }
-        
-    unsigned int d = static_cast<unsigned int>(std::floor(z));
-    if (d >= projectionDepth)
-    {
-      return false;
-    }
-    else
-    {
-      slice = d;
-      return true;
-    }
-  }
-}
--- a/Framework/Toolbox/VolumeImageGeometry.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../StoneEnumerations.h"
-#include "CoordinateSystem3D.h"
-
-namespace OrthancStone
-{
-  class VolumeImageGeometry
-  {
-  private:
-    unsigned int           width_;
-    unsigned int           height_;
-    unsigned int           depth_;
-    CoordinateSystem3D     axialGeometry_;
-    CoordinateSystem3D     coronalGeometry_;
-    CoordinateSystem3D     sagittalGeometry_;
-    Vector                 voxelDimensions_;
-    Matrix                 transform_;
-    Matrix                 transformInverse_;
-
-    void Invalidate();
-
-  public:
-    VolumeImageGeometry();
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    unsigned int GetDepth() const
-    {
-      return depth_;
-    }
-
-    const CoordinateSystem3D& GetAxialGeometry() const
-    {
-      return axialGeometry_;
-    }
-
-    const CoordinateSystem3D& GetCoronalGeometry() const
-    {
-      return coronalGeometry_;
-    }
-
-    const CoordinateSystem3D& GetSagittalGeometry() const
-    {
-      return sagittalGeometry_;
-    }
-
-    const CoordinateSystem3D& GetProjectionGeometry(VolumeProjection projection) const;
-    
-    const Matrix& GetTransform() const
-    {
-      return transform_;
-    }
-
-    const Matrix& GetTransformInverse() const
-    {
-      return transformInverse_;
-    }
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 unsigned int depth);
-
-    // Set the geometry of the first axial slice (i.e. the one whose
-    // depth == 0)
-    void SetAxialGeometry(const CoordinateSystem3D& geometry);
-
-    void SetVoxelDimensions(double x,
-                            double y,
-                            double z);
-
-    Vector GetVoxelDimensions(VolumeProjection projection) const;
-
-    unsigned int GetProjectionWidth(VolumeProjection projection) const;
-
-    unsigned int GetProjectionHeight(VolumeProjection projection) const;
-
-    unsigned int GetProjectionDepth(VolumeProjection projection) const;
-
-    // Get the 3D position of a point in the volume, where x, y and z
-    // lie in the [0;1] range
-    Vector GetCoordinates(float x,
-                          float y,
-                          float z) const;
-
-    bool DetectProjection(VolumeProjection& projection,
-                          const Vector& planeNormal) const;
-
-    bool DetectSlice(VolumeProjection& projection,
-                     unsigned int& slice,
-                     const CoordinateSystem3D& plane) const;
-  };
-}
--- a/Framework/Viewport/CairoContext.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "CairoContext.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-
-namespace OrthancStone
-{
-  CairoContext::CairoContext(CairoSurface& surface) :
-    width_(surface.GetWidth()),
-    height_(surface.GetHeight())
-  {
-    context_ = cairo_create(surface.GetObject());
-    if (!context_)
-    {
-      LOG(ERROR) << "Cannot create Cairo drawing context";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  CairoContext::~CairoContext()
-  {
-    if (context_ != NULL)
-    {
-      cairo_destroy(context_);
-      context_ = NULL;
-    }
-  }
-
-
-  void CairoContext::SetSourceColor(uint8_t red,
-                                    uint8_t green,
-                                    uint8_t blue)
-  {
-    cairo_set_source_rgb(context_,
-                         static_cast<float>(red) / 255.0f,
-                         static_cast<float>(green) / 255.0f,
-                         static_cast<float>(blue) / 255.0f);
-  }
-
-
-  class CairoContext::AlphaSurface : public boost::noncopyable
-  {
-  private:
-    cairo_surface_t  *surface_;
-
-  public:
-    AlphaSurface(unsigned int width,
-                 unsigned int height)
-    {
-      surface_ = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height);
-      
-      if (!surface_)
-      {
-        // Should never occur
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
-      {
-        LOG(ERROR) << "Cannot create a Cairo surface";
-        cairo_surface_destroy(surface_);
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-    
-    ~AlphaSurface()
-    {
-      cairo_surface_destroy(surface_);
-    }
-
-    void GetAccessor(Orthanc::ImageAccessor& target)
-    {
-      target.AssignWritable(Orthanc::PixelFormat_Grayscale8,
-                            cairo_image_surface_get_width(surface_),
-                            cairo_image_surface_get_height(surface_),
-                            cairo_image_surface_get_stride(surface_),
-                            cairo_image_surface_get_data(surface_));
-    }
-
-    void Blit(cairo_t* cr,
-              double x,
-              double y)
-    {
-      cairo_surface_mark_dirty(surface_);
-      cairo_mask_surface(cr, surface_, x, y);
-      cairo_fill(cr);
-    }
-  };
-
-
-  void CairoContext::DrawText(const Orthanc::Font& font,
-                              const std::string& text,
-                              double x,
-                              double y,
-                              BitmapAnchor anchor)
-  {
-    // Render a bitmap containing the text
-    unsigned int width, height;
-    font.ComputeTextExtent(width, height, text);
-    
-    AlphaSurface surface(width, height);
-
-    Orthanc::ImageAccessor accessor;
-    surface.GetAccessor(accessor);
-    font.Draw(accessor, text, 0, 0, 255);
-
-    // Correct the text location given the anchor location
-    double deltaX, deltaY;
-    ComputeAnchorTranslation(deltaX, deltaY, anchor, width, height);
-
-    // Cancel zoom/rotation before blitting the text onto the surface
-    double pixelX = x;
-    double pixelY = y;
-    cairo_user_to_device(context_, &pixelX, &pixelY);
-
-    cairo_save(context_);
-    cairo_identity_matrix(context_);
-
-    // Blit the text bitmap
-    surface.Blit(context_, pixelX + deltaX, pixelY + deltaY);
-    cairo_restore(context_);
-  }
-}
--- a/Framework/Viewport/CairoContext.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CairoSurface.h"
-#include "../StoneEnumerations.h"
-
-#include <Core/Images/Font.h>
-
-namespace OrthancStone
-{
-  // This is a RAII wrapper around the Cairo drawing context
-  class CairoContext : public boost::noncopyable
-  {
-  private:
-    class AlphaSurface;
-    
-    cairo_t*      context_;
-    unsigned int  width_;
-    unsigned int  height_;
-
-  public:
-    CairoContext(CairoSurface& surface);
-
-    ~CairoContext();
-
-    cairo_t* GetObject()
-    {
-      return context_;
-    }
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    void SetSourceColor(uint8_t red,
-                        uint8_t green,
-                        uint8_t blue);
-
-    void SetSourceColor(const uint8_t color[3])
-    {
-      SetSourceColor(color[0], color[1], color[2]);
-    }
-
-    void DrawText(const Orthanc::Font& font,
-                  const std::string& text,
-                  double x,
-                  double y,
-                  BitmapAnchor anchor);      
-  };
-}
--- a/Framework/Viewport/CairoFont.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "CairoFont.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  CairoFont::CairoFont(const char* family,
-                       cairo_font_slant_t slant,
-                       cairo_font_weight_t weight)
-  {
-    font_ = cairo_toy_font_face_create(family, slant, weight);
-    if (font_ == NULL)
-    {
-      LOG(ERROR) << "Unknown font: " << family;
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-
-
-  CairoFont::~CairoFont()
-  {
-    if (font_ != NULL)
-    {
-      cairo_font_face_destroy(font_);
-    }
-  }
-
-
-  void CairoFont::Draw(CairoContext& context,
-                       const std::string& text,
-                       double size)
-  {
-    if (size <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    cairo_t* cr = context.GetObject();
-    cairo_set_font_face(cr, font_);
-    cairo_set_font_size(cr, size);
-    cairo_show_text(cr, text.c_str());    
-  }
-}
--- a/Framework/Viewport/CairoFont.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if !defined(ORTHANC_SANDBOXED)
-#  error The macro ORTHANC_SANDBOXED must be defined
-#endif
-
-#if ORTHANC_SANDBOXED == 1
-#  error The class CairoFont cannot be used in sandboxed environments
-#endif
-
-#include "CairoContext.h"
-
-namespace OrthancStone
-{
-  class CairoFont : public boost::noncopyable
-  {
-  private:
-    cairo_font_face_t*  font_;
-
-  public:
-    CairoFont(const char* family,
-              cairo_font_slant_t slant,
-              cairo_font_weight_t weight);
-
-    ~CairoFont();
-
-    void Draw(CairoContext& context,
-              const std::string& text,
-              double size);
-  };
-}
--- a/Framework/Viewport/CairoSurface.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "CairoSurface.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-#include <Core/Images/ImageProcessing.h>
-
-namespace OrthancStone
-{
-  void CairoSurface::Release()
-  {
-    if (surface_)
-    {
-      cairo_surface_destroy(surface_);
-      surface_ = NULL;
-    }
-  }
-
-
-  void CairoSurface::Allocate(unsigned int width,
-                              unsigned int height,
-                              bool hasAlpha)
-  {
-    Release();
-
-    hasAlpha_ = hasAlpha;
-
-    surface_ = cairo_image_surface_create
-      (hasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);
-    if (!surface_)
-    {
-      // Should never occur
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
-    {
-      LOG(ERROR) << "Cannot create a Cairo surface";
-      cairo_surface_destroy(surface_);
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    width_ = width;
-    height_ = height;
-    pitch_ = cairo_image_surface_get_stride(surface_);
-    buffer_ = cairo_image_surface_get_data(surface_);
-  }
-
-
-  CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor,
-                             bool hasAlpha) :
-    hasAlpha_(hasAlpha)
-  {
-    if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-    }      
-
-    width_ = accessor.GetWidth();
-    height_ = accessor.GetHeight();
-    pitch_ = accessor.GetPitch();
-    buffer_ = accessor.GetBuffer();
-
-    surface_ = cairo_image_surface_create_for_data
-      (reinterpret_cast<unsigned char*>(buffer_),
-       hasAlpha_ ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
-       width_, height_, pitch_);
-    if (!surface_)
-    {
-      // Should never occur
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
-    {
-      LOG(ERROR) << "Bad pitch for a Cairo surface";
-      cairo_surface_destroy(surface_);
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  void CairoSurface::SetSize(unsigned int width,
-                             unsigned int height,
-                             bool hasAlpha)
-  {
-    if (hasAlpha_ != hasAlpha ||
-        width_ != width ||
-        height_ != height)
-    {
-      Allocate(width, height, hasAlpha);
-    }
-  }
-
-
-  void CairoSurface::Copy(const CairoSurface& other)
-  {
-    SetSize(other.GetWidth(), other.GetHeight(), other.HasAlpha());
-    
-    Orthanc::ImageAccessor source, target;
-
-    other.GetReadOnlyAccessor(source);
-    GetWriteableAccessor(target);
-
-    Orthanc::ImageProcessing::Copy(target, source);
-
-    cairo_surface_mark_dirty(surface_);
-  }
-
-
-  void CairoSurface::Copy(const Orthanc::ImageAccessor& source,
-                          bool hasAlpha)
-  {
-    SetSize(source.GetWidth(), source.GetHeight(), hasAlpha);
-
-    Orthanc::ImageAccessor target;
-    GetWriteableAccessor(target);
-
-    Orthanc::ImageProcessing::Convert(target, source);
-
-    cairo_surface_mark_dirty(surface_);
-  }
-
-
-  void CairoSurface::GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const
-  {
-    target.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
-  }
-  
-
-  void CairoSurface::GetWriteableAccessor(Orthanc::ImageAccessor& target)
-  {
-    target.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
-  }
-}
--- a/Framework/Viewport/CairoSurface.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <Core/Images/ImageAccessor.h>
-
-#include <boost/noncopyable.hpp>
-#include <cairo.h>
-
-namespace OrthancStone
-{
-  class CairoSurface : public boost::noncopyable
-  {
-  private:
-    cairo_surface_t* surface_;
-    unsigned int     width_;
-    unsigned int     height_;
-    unsigned int     pitch_;
-    void*            buffer_;
-    bool             hasAlpha_;
-
-    void Release();
-
-    void Allocate(unsigned int width,
-                  unsigned int height,
-                  bool hasAlpha);
-
-  public:
-    CairoSurface() :
-      surface_(NULL)
-    {
-      Allocate(0, 0, false);
-    }
-
-    CairoSurface(unsigned int width,
-                 unsigned int height,
-                 bool hasAlpha) :
-      surface_(NULL)
-    {
-      Allocate(width, height, hasAlpha);
-    }
-
-    CairoSurface(Orthanc::ImageAccessor& accessor,
-                 bool hasAlpha);
-
-    ~CairoSurface()
-    {
-      Release();
-    }
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 bool hasAlpha);
-
-    void Copy(const CairoSurface& other);
-
-    void Copy(const Orthanc::ImageAccessor& source,
-              bool hasAlpha);
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    unsigned int GetPitch() const
-    {
-      return pitch_;
-    }
-
-    const void* GetBuffer() const
-    {
-      return buffer_;
-    }
-
-    void* GetBuffer()
-    {
-      return buffer_;
-    }
-
-    cairo_surface_t* GetObject()
-    {
-      return surface_;
-    }
-
-    bool HasAlpha() const
-    {
-      return hasAlpha_;
-    }
-
-    void GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const;
-
-    void GetWriteableAccessor(Orthanc::ImageAccessor& target);
-  };
-}
--- a/Framework/Viewport/IMouseTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CairoSurface.h"
-#include <vector>
-
-namespace Deprecated
-{
-  struct Touch
-  {
-    float x;
-    float y;
-
-    Touch(float x, float y)
-    : x(x),
-      y(y)
-    {
-    }
-    Touch()
-      : x(0.0f),
-        y(0.0f)
-    {
-    }
-  };
-
-
-  // this is tracking a mouse in screen coordinates/pixels unlike
-  // the IWorldSceneMouseTracker that is tracking a mouse
-  // in scene coordinates/mm.
-  class IMouseTracker : public boost::noncopyable
-  {
-  public:
-    virtual ~IMouseTracker()
-    {
-    }
-    
-    virtual void Render(Orthanc::ImageAccessor& surface) = 0;
-
-    virtual void MouseUp() = 0;
-
-    // Returns "true" iff. the background scene must be repainted
-    virtual void MouseMove(int x, 
-                           int y,
-                           const std::vector<Touch>& displayTouches) = 0;
-  };
-}
--- a/Framework/Viewport/IStatusBar.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <string>
-#include <boost/noncopyable.hpp>
-
-namespace Deprecated
-{
-  class IStatusBar : public boost::noncopyable
-  {
-  public:
-    virtual ~IStatusBar()
-    {
-    }
-    
-    virtual void ClearMessage() = 0;
-
-    virtual void SetMessage(const std::string& message) = 0;
-  };
-}
--- a/Framework/Viewport/IViewport.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IStatusBar.h"
-#include "../StoneEnumerations.h"
-#include "../Messages/IObservable.h"
-
-#include <Core/Images/ImageAccessor.h>
-#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
-
-namespace Deprecated
-{
-  class IWidget;   // Forward declaration
-  
-  class IViewport : public OrthancStone::IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport);
-
-    IViewport(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-    
-    virtual ~IViewport()
-    {
-    }
-
-    virtual void FitContent() = 0;
-
-    virtual void SetStatusBar(IStatusBar& statusBar) = 0;
-
-    virtual void SetSize(unsigned int width,
-                         unsigned int height) = 0;
-
-    // The function returns "true" iff. a new frame was rendered
-    virtual bool Render(Orthanc::ImageAccessor& surface) = 0;
-
-    virtual void MouseDown(OrthancStone::MouseButton button,
-                           int x,
-                           int y,
-                           OrthancStone::KeyboardModifiers modifiers,
-                           const std::vector<Touch>& touches) = 0;
-
-    virtual void MouseUp() = 0;
-
-    virtual void MouseMove(int x, 
-                           int y,
-                           const std::vector<Touch>& displayTouches) = 0;
-
-    virtual void MouseEnter() = 0;
-
-    virtual void MouseLeave() = 0;
-
-    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            OrthancStone::KeyboardModifiers modifiers) = 0;
-
-    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers) = 0;
-
-    virtual bool HasAnimation() = 0;
-
-    virtual void DoAnimation() = 0;
-
-    // Should only be called from IWidget
-    // TODO Why should this be virtual?
-    virtual void NotifyContentChanged()
-    {
-      BroadcastMessage(ViewportChangedMessage(*this));
-    }
-  };
-}
--- a/Framework/Viewport/WidgetViewport.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,289 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WidgetViewport.h"
-
-#include <Core/Images/ImageProcessing.h>
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  WidgetViewport::WidgetViewport(OrthancStone::MessageBroker& broker) :
-    IViewport(broker),
-    statusBar_(NULL),
-    isMouseOver_(false),
-    lastMouseX_(0),
-    lastMouseY_(0),
-    backgroundChanged_(false)
-  {
-  }
-
-
-  void WidgetViewport::FitContent()
-  {
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->FitContent();
-    }
-  }
-
-
-  void WidgetViewport::SetStatusBar(IStatusBar& statusBar)
-  {
-    statusBar_ = &statusBar;
-
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->SetStatusBar(statusBar);
-    }
-  }
-
-
-  IWidget& WidgetViewport::SetCentralWidget(IWidget* widget)
-  {
-    if (widget == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    mouseTracker_.reset(NULL);
-
-    centralWidget_.reset(widget);
-    centralWidget_->SetViewport(*this);
-
-    if (statusBar_ != NULL)
-    {
-      centralWidget_->SetStatusBar(*statusBar_);
-    }
-
-    NotifyBackgroundChanged();
-
-    return *widget;
-  }
-
-
-  void WidgetViewport::NotifyBackgroundChanged()
-  {
-    backgroundChanged_ = true;
-    NotifyContentChanged();
-  }
-
-
-  void WidgetViewport::SetSize(unsigned int width,
-                               unsigned int height)
-  {
-    background_.SetSize(width, height, false /* no alpha */);
-
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->SetSize(width, height);
-    }
-
-    NotifyBackgroundChanged();
-  }
-
-
-  bool WidgetViewport::Render(Orthanc::ImageAccessor& surface)
-  {
-    if (centralWidget_.get() == NULL)
-    {
-      return false;
-    }
-
-    Orthanc::ImageAccessor background;
-    background_.GetWriteableAccessor(background);
-
-    if (backgroundChanged_ &&
-        !centralWidget_->Render(background))
-    {
-      return false;
-    }
-
-    if (background.GetWidth() != surface.GetWidth() ||
-        background.GetHeight() != surface.GetHeight())
-    {
-      return false;
-    }
-
-    Orthanc::ImageProcessing::Convert(surface, background);
-    
-    if (mouseTracker_.get() != NULL)
-    {
-      mouseTracker_->Render(surface);
-    }
-    else if (isMouseOver_)
-    {
-      centralWidget_->RenderMouseOver(surface, lastMouseX_, lastMouseY_);
-    }
-
-    return true;
-  }
-
-  void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches)
-  {
-    MouseDown(OrthancStone::MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, OrthancStone::KeyboardModifiers_None, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
-  }
-      
-  void WidgetViewport::TouchMove(const std::vector<Touch>& displayTouches)
-  {
-    MouseMove((int)displayTouches[0].x, (int)displayTouches[0].y, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
-  }
-      
-  void WidgetViewport::TouchEnd(const std::vector<Touch>& displayTouches)
-  {
-    // note: TouchEnd is not triggered when a single touch gesture ends (it is only triggered when
-    // going from 2 touches to 1 touch, ...)
-    MouseUp();
-  }
-
-  void WidgetViewport::MouseDown(OrthancStone::MouseButton button,
-                                 int x,
-                                 int y,
-                                 OrthancStone::KeyboardModifiers modifiers,
-                                 const std::vector<Touch>& displayTouches
-                                 )
-  {
-    lastMouseX_ = x;
-    lastMouseY_ = y;
-
-    if (centralWidget_.get() != NULL)
-    {
-      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches));
-    }
-    else
-    {
-      mouseTracker_.reset(NULL);
-    }      
-
-    NotifyContentChanged();
-  }
-
-
-  void WidgetViewport::MouseUp()
-  {
-    if (mouseTracker_.get() != NULL)
-    {
-      mouseTracker_->MouseUp();
-      mouseTracker_.reset(NULL);
-      NotifyContentChanged();
-    }
-  }
-
-
-  void WidgetViewport::MouseMove(int x, 
-                                 int y,
-                                 const std::vector<Touch>& displayTouches)
-  {
-    if (centralWidget_.get() == NULL)
-    {
-      return;
-    }
-
-    lastMouseX_ = x;
-    lastMouseY_ = y;
-
-    bool repaint = false;
-    
-    if (mouseTracker_.get() != NULL)
-    {
-      mouseTracker_->MouseMove(x, y, displayTouches);
-      repaint = true;
-    }
-    else
-    {
-      repaint = centralWidget_->HasRenderMouseOver();
-    }
-
-    if (repaint)
-    {
-      // The scene must be repainted, notify the observers
-      NotifyContentChanged();
-    }
-  }
-
-
-  void WidgetViewport::MouseEnter()
-  {
-    isMouseOver_ = true;
-    NotifyContentChanged();
-  }
-
-
-  void WidgetViewport::MouseLeave()
-  {
-    isMouseOver_ = false;
-
-    if (mouseTracker_.get() != NULL)
-    {
-      mouseTracker_->MouseUp();
-      mouseTracker_.reset(NULL);
-    }
-
-    NotifyContentChanged();
-  }
-
-
-  void WidgetViewport::MouseWheel(OrthancStone::MouseWheelDirection direction,
-                                  int x,
-                                  int y,
-                                  OrthancStone::KeyboardModifiers modifiers)
-  {
-    if (centralWidget_.get() != NULL &&
-        mouseTracker_.get() == NULL)
-    {
-      centralWidget_->MouseWheel(direction, x, y, modifiers);
-    }
-  }
-
-
-  void WidgetViewport::KeyPressed(OrthancStone::KeyboardKeys key,
-                                  char keyChar,
-                                  OrthancStone::KeyboardModifiers modifiers)
-  {
-    if (centralWidget_.get() != NULL &&
-        mouseTracker_.get() == NULL)
-    {
-      centralWidget_->KeyPressed(key, keyChar, modifiers);
-    }
-  }
-
-
-  bool WidgetViewport::HasAnimation()
-  {
-    if (centralWidget_.get() != NULL)
-    {
-      return centralWidget_->HasAnimation();
-    }
-    else
-    {
-      return false;
-    }
-  }
-   
-
-  void WidgetViewport::DoAnimation()
-  {
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->DoAnimation();
-    }
-  }
-}
--- a/Framework/Viewport/WidgetViewport.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IViewport.h"
-#include "../Widgets/IWidget.h"
-
-#include <memory>
-
-namespace Deprecated
-{
-  class WidgetViewport : public IViewport
-  {
-  private:
-    std::auto_ptr<IWidget>        centralWidget_;
-    IStatusBar*                   statusBar_;
-    std::auto_ptr<IMouseTracker>  mouseTracker_;
-    bool                          isMouseOver_;
-    int                           lastMouseX_;
-    int                           lastMouseY_;
-    OrthancStone::CairoSurface    background_;
-    bool                          backgroundChanged_;
-
-  public:
-    WidgetViewport(OrthancStone::MessageBroker& broker);
-
-    virtual void FitContent();
-
-    virtual void SetStatusBar(IStatusBar& statusBar);
-
-    IWidget& SetCentralWidget(IWidget* widget);  // Takes ownership
-
-    virtual void NotifyBackgroundChanged();
-
-    virtual void SetSize(unsigned int width,
-                         unsigned int height);
-
-    virtual bool Render(Orthanc::ImageAccessor& surface);
-
-    virtual void MouseDown(OrthancStone::MouseButton button,
-                           int x,
-                           int y,
-                           OrthancStone::KeyboardModifiers modifiers,
-                           const std::vector<Touch>& displayTouches);
-
-    virtual void MouseUp();
-
-    virtual void MouseMove(int x, 
-                           int y,
-                           const std::vector<Touch>& displayTouches);
-
-    virtual void MouseEnter();
-
-    virtual void MouseLeave();
-
-    virtual void TouchStart(const std::vector<Touch>& touches);
-    
-    virtual void TouchMove(const std::vector<Touch>& touches);
-    
-    virtual void TouchEnd(const std::vector<Touch>& touches);
-
-    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            OrthancStone::KeyboardModifiers modifiers);
-
-    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers);
-
-    virtual bool HasAnimation();
-
-    virtual void DoAnimation();
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomVolumeImage.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,94 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomVolumeImage.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void DicomVolumeImage::CheckHasGeometry() const
+  {
+    if (!HasGeometry())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+    
+
+  void DicomVolumeImage::Initialize(const VolumeImageGeometry& geometry,
+                                    Orthanc::PixelFormat format)
+  {
+    geometry_.reset(new VolumeImageGeometry(geometry));
+    image_.reset(new ImageBuffer3D(format, geometry_->GetWidth(), geometry_->GetHeight(),
+                                   geometry_->GetDepth(), false /* don't compute range */));
+
+    revision_ ++;
+  }
+
+
+  void DicomVolumeImage::SetDicomParameters(const DicomInstanceParameters& parameters)
+  {
+    parameters_.reset(parameters.Clone());
+    revision_ ++;
+  }
+    
+
+  bool DicomVolumeImage::HasGeometry() const
+  {
+    return (geometry_.get() != NULL &&
+            image_.get() != NULL);
+  }
+
+
+  ImageBuffer3D& DicomVolumeImage::GetPixelData()
+  {
+    CheckHasGeometry();
+    return *image_;
+  }
+
+
+  const ImageBuffer3D& DicomVolumeImage::GetPixelData() const
+  {
+    CheckHasGeometry();
+    return *image_;
+  }
+
+
+  const VolumeImageGeometry& DicomVolumeImage::GetGeometry() const
+  {
+    CheckHasGeometry();
+    return *geometry_;
+  }
+
+
+  const DicomInstanceParameters& DicomVolumeImage::GetDicomParameters() const
+  {
+    if (HasDicomParameters())
+    {
+      return *parameters_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }      
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomVolumeImage.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,86 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Messages/IMessage.h"
+#include "../Toolbox/DicomInstanceParameters.h"
+#include "ImageBuffer3D.h"
+#include "VolumeImageGeometry.h"
+
+namespace OrthancStone
+{
+  /**
+  This class combines a 3D image buffer, a 3D volume geometry and
+  information about the DICOM parameters of the series.
+  (MPR means MultiPlanar Reconstruction)
+  */ 
+  class DicomVolumeImage : public boost::noncopyable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage);
+
+  private:
+    uint64_t                                revision_;
+    std::auto_ptr<VolumeImageGeometry>      geometry_;
+    std::auto_ptr<ImageBuffer3D>            image_;
+    std::auto_ptr<DicomInstanceParameters>  parameters_;
+
+    void CheckHasGeometry() const;
+    
+  public:
+    DicomVolumeImage() :
+      revision_(0)
+    {
+    }
+
+    void IncrementRevision()
+    {
+      revision_ ++;
+    }
+
+    void Initialize(const VolumeImageGeometry& geometry,
+                    Orthanc::PixelFormat format);
+
+    void SetDicomParameters(const DicomInstanceParameters& parameters);
+    
+    uint64_t GetRevision() const
+    {
+      return revision_;
+    }
+
+    bool HasGeometry() const;
+
+    ImageBuffer3D& GetPixelData();
+
+    const ImageBuffer3D& GetPixelData() const;
+
+    const VolumeImageGeometry& GetGeometry() const;
+
+    bool HasDicomParameters() const
+    {
+      return parameters_.get() != NULL;
+    }      
+
+    const DicomInstanceParameters& GetDicomParameters() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,115 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomVolumeImageMPRSlicer.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void DicomVolumeImageMPRSlicer::Slice::CheckValid() const
+  {
+    if (!valid_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  DicomVolumeImageMPRSlicer::Slice::Slice(const DicomVolumeImage& volume,
+                                          const CoordinateSystem3D& cuttingPlane) :
+    volume_(volume),
+    revision_(volume_.GetRevision())
+  {
+    valid_ = (volume_.HasDicomParameters() &&
+              volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane));
+  }
+
+
+  VolumeProjection DicomVolumeImageMPRSlicer::Slice::GetProjection() const
+  {
+    CheckValid();
+    return projection_;
+  }
+
+
+  unsigned int DicomVolumeImageMPRSlicer::Slice::GetSliceIndex() const
+  {
+    CheckValid();
+    return sliceIndex_;
+  }
+  
+
+  ISceneLayer* DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                                                  const CoordinateSystem3D& cuttingPlane)
+  {
+    CheckValid();
+
+    if (configurator == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer,
+                                      "A style configurator is mandatory for textures");
+    }
+
+    std::auto_ptr<TextureBaseSceneLayer> texture;
+        
+    {
+      const DicomInstanceParameters& parameters = volume_.GetDicomParameters();
+      ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_);
+      texture.reset(dynamic_cast<TextureBaseSceneLayer*>
+                    (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters)));
+    }
+
+    const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_);
+      
+    double x0, y0, x1, y1;
+    cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin());
+    cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX());
+    texture->SetOrigin(x0, y0);
+
+    double dx = x1 - x0;
+    double dy = y1 - y0;
+    if (!LinearAlgebra::IsCloseToZero(dx) ||
+        !LinearAlgebra::IsCloseToZero(dy))
+    {
+      texture->SetAngle(atan2(dy, dx));
+    }
+        
+    Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_);
+    texture->SetPixelSpacing(tmp[0], tmp[1]);
+
+    return texture.release();
+  }
+
+
+  IVolumeSlicer::IExtractedSlice* 
+  DicomVolumeImageMPRSlicer::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
+  {
+    if (volume_->HasGeometry())
+    {
+      return new Slice(*volume_, cuttingPlane);
+    }
+    else
+    {
+      return new IVolumeSlicer::InvalidSlice;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,94 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomVolumeImage.h"
+#include "IVolumeSlicer.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  /**
+     Implements the IVolumeSlicer on Dicom volume data when the cutting plane
+     that is supplied to the slicer is either axial, sagittal or coronal. 
+     Arbitrary planes are *not* supported
+  */
+  class DicomVolumeImageMPRSlicer : public IVolumeSlicer
+  {
+  public:
+    class Slice : public IExtractedSlice
+    {
+    private:
+      const DicomVolumeImage&  volume_;
+      uint64_t                 revision_;
+      bool                     valid_;
+      VolumeProjection         projection_;
+      unsigned int             sliceIndex_;
+
+      void CheckValid() const;
+
+    public:
+      /**
+         Represents a slice of a volume image that is parallel to the 
+         coordinate system axis. 
+         The constructor initializes the type of projection (axial, sagittal or
+         coronal) and the corresponding slice index, from the cutting plane.
+      */
+      Slice(const DicomVolumeImage& volume,
+            const CoordinateSystem3D& cuttingPlane);
+
+      void SetRevision(uint64_t revision)
+      {
+        revision_ = revision;
+      }
+
+      VolumeProjection GetProjection() const;
+
+      unsigned int GetSliceIndex() const;
+
+      virtual bool IsValid()
+      {
+        return valid_;
+      }
+
+      virtual uint64_t GetRevision()
+      {
+        return revision_;
+      }
+
+      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                            const CoordinateSystem3D& cuttingPlane);
+    };
+
+  private:
+    boost::shared_ptr<DicomVolumeImage>  volume_;
+
+  public:
+    DicomVolumeImageMPRSlicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
+      volume_(volume)
+    {
+    }
+
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomVolumeImageReslicer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,116 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomVolumeImageReslicer.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class DicomVolumeImageReslicer::Slice : public IVolumeSlicer::IExtractedSlice
+  {
+  private:
+    DicomVolumeImageReslicer&  that_;
+    CoordinateSystem3D         cuttingPlane_;
+      
+  public:
+    Slice(DicomVolumeImageReslicer& that,
+          const CoordinateSystem3D& cuttingPlane) :
+      that_(that),
+      cuttingPlane_(cuttingPlane)
+    {
+    }
+      
+    virtual bool IsValid()
+    {
+      return true;
+    }
+
+    virtual uint64_t GetRevision()
+    {
+      return that_.volume_->GetRevision();
+    }
+
+    virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                          const CoordinateSystem3D& cuttingPlane)
+    {
+      VolumeReslicer& reslicer = that_.reslicer_;
+        
+      if (configurator == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                        "Must provide a layer style configurator");
+      }
+        
+      reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat());
+      reslicer.Apply(that_.volume_->GetPixelData(),
+                     that_.volume_->GetGeometry(),
+                     cuttingPlane);
+
+      if (reslicer.IsSuccess())
+      {
+        std::auto_ptr<TextureBaseSceneLayer> layer
+          (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(),
+                                                that_.volume_->GetDicomParameters()));
+        if (layer.get() == NULL)
+        {
+          return NULL;
+        }
+
+        double s = reslicer.GetPixelSpacing();
+        layer->SetPixelSpacing(s, s);
+        layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s,
+                         reslicer.GetOutputExtent().GetY1() + 0.5 * s);
+
+        // TODO - Angle!!
+                           
+        return layer.release();
+      }
+      else
+      {
+        return NULL;
+      }          
+    }
+  };
+    
+
+  DicomVolumeImageReslicer::DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
+    volume_(volume)
+  {
+    if (volume.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+    
+  IVolumeSlicer::IExtractedSlice* DicomVolumeImageReslicer::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
+  {
+    if (volume_->HasGeometry())
+    {
+      return new Slice(*this, cuttingPlane);
+    }
+    else
+    {
+      return new IVolumeSlicer::InvalidSlice;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/DicomVolumeImageReslicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,69 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomVolumeImage.h"
+#include "IVolumeSlicer.h"
+#include "VolumeReslicer.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  /**
+  This class is able to supply an extract slice for an arbitrary cutting
+  plane through a volume image
+  */
+  class DicomVolumeImageReslicer : public IVolumeSlicer
+  {
+  private:
+    class Slice;
+    
+    boost::shared_ptr<DicomVolumeImage>  volume_;
+    VolumeReslicer                       reslicer_;
+
+  public:
+    DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume);
+
+    ImageInterpolation GetInterpolation() const
+    {
+      return reslicer_.GetInterpolation();
+    }
+
+    void SetInterpolation(ImageInterpolation interpolation)
+    {
+      reslicer_.SetInterpolation(interpolation);
+    }
+
+    bool IsFastMode() const
+    {
+      return reslicer_.IsFastMode();
+    }
+
+    void SetFastMode(bool fast)
+    {
+      reslicer_.EnableFastMode(fast);
+    }
+    
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane);
+  };
+}
--- a/Framework/Volumes/ISlicedVolume.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Messages/IObservable.h"
-#include "../Toolbox/Slice.h"
-
-namespace Deprecated
-{
-  class ISlicedVolume : public OrthancStone::IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, ISlicedVolume);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, ISlicedVolume);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, ISlicedVolume);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeReadyMessage, ISlicedVolume);
-
-
-    class SliceContentChangedMessage : public OrthancStone::OriginMessage<ISlicedVolume>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      size_t        sliceIndex_;
-      const Slice&  slice_;
-      
-    public:
-      SliceContentChangedMessage(ISlicedVolume& origin,
-                                 size_t sliceIndex,
-                                 const Slice& slice) :
-        OriginMessage(origin),
-        sliceIndex_(sliceIndex),
-        slice_(slice)
-      {
-      }
-
-      size_t GetSliceIndex() const
-      {
-        return sliceIndex_;
-      }
-
-      const Slice& GetSlice() const
-      {
-        return slice_;
-      }
-    };
-
-
-    ISlicedVolume(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-    
-    virtual size_t GetSliceCount() const = 0;
-
-    virtual const Slice& GetSlice(size_t slice) const = 0;
-  };
-}
--- a/Framework/Volumes/IVolumeLoader.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Messages/IObservable.h"
-
-namespace Deprecated
-{
-  class IVolumeLoader : public OrthancStone::IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeLoader);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeLoader);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeLoader);
-
-    IVolumeLoader(OrthancStone::MessageBroker& broker) :
-      IObservable(broker)
-    {
-    }
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/IVolumeSlicer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,38 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "IVolumeSlicer.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  uint64_t IVolumeSlicer::InvalidSlice::GetRevision()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+  }
+
+  ISceneLayer* IVolumeSlicer::InvalidSlice::CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                                             const CoordinateSystem3D& cuttingPlane)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/IVolumeSlicer.cpp~	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,113 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+namespace OrthancStone
+{
+  /**
+  This interface is implemented by objects representing 3D volume data and 
+  that are able to return an object that:
+  - represent a slice of their data 
+  - are able to create the corresponding slice visual representation.
+  */
+  class IVolumeSlicer : public boost::noncopyable
+  {
+  public:
+    /**
+    This interface is implemented by objects representing a slice of 
+    volume data and that are able to create a 2D layer to display a this 
+    slice.
+
+    The CreateSceneLayer factory method is called with an optional
+    configurator that possibly impacts the ISceneLayer subclass that is 
+    created (for instance, if a LUT must be applied on the texture when
+    displaying it)
+    */
+    class IExtractedSlice : public boost::noncopyable
+    {
+    public:
+      virtual ~IExtractedSlice()
+      {
+      }
+
+      /**
+      Invalid slices are created when the data is not ready yet or if the
+      cut is outside of the available geometry.
+      */
+      virtual bool IsValid() = 0;
+
+      /**
+      This retrieves the *revision* that gets incremented every time the 
+      underlying object undergoes a mutable operation (that it, changes its 
+      state).
+      This **must** be a cheap call.
+      */
+      virtual uint64_t GetRevision() = 0;
+
+      /** Creates the slice visual representation */
+      virtual ISceneLayer* CreateSceneLayer(
+        const ILayerStyleConfigurator* configurator,  // possibly absent
+        const CoordinateSystem3D& cuttingPlane) = 0;
+    };
+
+    /**
+    See IExtractedSlice.IsValid()
+    */
+    class InvalidSlice : public IExtractedSlice
+    {
+    public:
+      virtual bool IsValid()
+      {
+        return false;
+      }
+
+      virtual uint64_t GetRevision()
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                            const CoordinateSystem3D& cuttingPlane)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    };
+
+
+    virtual ~IVolumeSlicer()
+    {
+    }
+
+    /**
+    This method is implemented by the objects representing volumetric data
+    and must returns an IExtractedSlice subclass that contains all the data
+    needed to, later on, create its visual representation through
+    CreateSceneLayer.
+    Subclasses a.o.: 
+    - InvalidSlice, 
+    - DicomVolumeImageMPRSlicer::Slice, 
+    - DicomVolumeImageReslicer::Slice
+    - DicomStructureSetLoader::Slice 
+    */
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/IVolumeSlicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,110 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Scene2D/ILayerStyleConfigurator.h"
+#include "../Toolbox/CoordinateSystem3D.h"
+
+namespace OrthancStone
+{
+  /**
+  This interface is implemented by objects representing 3D volume data and 
+  that are able to return an object that:
+  - represent a slice of their data 
+  - are able to create the corresponding slice visual representation.
+  */
+  class IVolumeSlicer : public boost::noncopyable
+  {
+  public:
+    /**
+    This interface is implemented by objects representing a slice of 
+    volume data and that are able to create a 2D layer to display a this 
+    slice.
+
+    The CreateSceneLayer factory method is called with an optional
+    configurator that possibly impacts the ISceneLayer subclass that is 
+    created (for instance, if a LUT must be applied on the texture when
+    displaying it)
+    */
+    class IExtractedSlice : public boost::noncopyable
+    {
+    public:
+      virtual ~IExtractedSlice()
+      {
+      }
+
+      /**
+      Invalid slices are created when the data is not ready yet or if the
+      cut is outside of the available geometry.
+      */
+      virtual bool IsValid() = 0;
+
+      /**
+      This retrieves the *revision* that gets incremented every time the 
+      underlying object undergoes a mutable operation (that it, changes its 
+      state).
+      This **must** be a cheap call.
+      */
+      virtual uint64_t GetRevision() = 0;
+
+      /** Creates the slice visual representation */
+      virtual ISceneLayer* CreateSceneLayer(
+        const ILayerStyleConfigurator* configurator,  // possibly absent
+        const CoordinateSystem3D& cuttingPlane) = 0;
+    };
+
+    /**
+    See IExtractedSlice.IsValid()
+    */
+    class InvalidSlice : public IExtractedSlice
+    {
+    public:
+      virtual bool IsValid()
+      {
+        return false;
+      }
+
+      virtual uint64_t GetRevision();
+
+      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                            const CoordinateSystem3D& cuttingPlane);
+    };
+
+
+    virtual ~IVolumeSlicer()
+    {
+    }
+
+    /**
+    This method is implemented by the objects representing volumetric data
+    and must returns an IExtractedSlice subclass that contains all the data
+    needed to, later on, create its visual representation through
+    CreateSceneLayer.
+    Subclasses a.o.: 
+    - InvalidSlice, 
+    - DicomVolumeImageMPRSlicer::Slice, 
+    - DicomVolumeImageReslicer::Slice
+    - DicomStructureSetLoader::Slice 
+    */
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/IVolumeSlicer.h~	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,113 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+namespace OrthancStone
+{
+  /**
+  This interface is implemented by objects representing 3D volume data and 
+  that are able to return an object that:
+  - represent a slice of their data 
+  - are able to create the corresponding slice visual representation.
+  */
+  class IVolumeSlicer : public boost::noncopyable
+  {
+  public:
+    /**
+    This interface is implemented by objects representing a slice of 
+    volume data and that are able to create a 2D layer to display a this 
+    slice.
+
+    The CreateSceneLayer factory method is called with an optional
+    configurator that possibly impacts the ISceneLayer subclass that is 
+    created (for instance, if a LUT must be applied on the texture when
+    displaying it)
+    */
+    class IExtractedSlice : public boost::noncopyable
+    {
+    public:
+      virtual ~IExtractedSlice()
+      {
+      }
+
+      /**
+      Invalid slices are created when the data is not ready yet or if the
+      cut is outside of the available geometry.
+      */
+      virtual bool IsValid() = 0;
+
+      /**
+      This retrieves the *revision* that gets incremented every time the 
+      underlying object undergoes a mutable operation (that it, changes its 
+      state).
+      This **must** be a cheap call.
+      */
+      virtual uint64_t GetRevision() = 0;
+
+      /** Creates the slice visual representation */
+      virtual ISceneLayer* CreateSceneLayer(
+        const ILayerStyleConfigurator* configurator,  // possibly absent
+        const CoordinateSystem3D& cuttingPlane) = 0;
+    };
+
+    /**
+    See IExtractedSlice.IsValid()
+    */
+    class InvalidSlice : public IExtractedSlice
+    {
+    public:
+      virtual bool IsValid()
+      {
+        return false;
+      }
+
+      virtual uint64_t GetRevision()
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                            const CoordinateSystem3D& cuttingPlane)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    };
+
+
+    virtual ~IVolumeSlicer()
+    {
+    }
+
+    /**
+    This method is implemented by the objects representing volumetric data
+    and must returns an IExtractedSlice subclass that contains all the data
+    needed to, later on, create its visual representation through
+    CreateSceneLayer.
+    Subclasses a.o.: 
+    - InvalidSlice, 
+    - DicomVolumeImageMPRSlicer::Slice, 
+    - DicomVolumeImageReslicer::Slice
+    - DicomStructureSetLoader::Slice 
+    */
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
+  };
+}
--- a/Framework/Volumes/ImageBuffer3D.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -21,8 +21,6 @@
 
 #include "ImageBuffer3D.h"
 
-#include "../Toolbox/GeometryToolbox.h"
-
 #include <Core/Images/ImageProcessing.h>
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -118,8 +116,6 @@
     computeRange_(computeRange),
     hasRange_(false)
   {
-    geometry_.SetSize(width, height, depth);
-
     LOG(INFO) << "Created a 3D image of size " << width << "x" << height
               << "x" << depth << " in " << Orthanc::EnumerationToString(format)
               << " (" << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB)";
@@ -132,63 +128,6 @@
   }
 
 
-
-
-  ParallelSlices* ImageBuffer3D::GetGeometry(VolumeProjection projection) const
-  {
-    const Vector dimensions = geometry_.GetVoxelDimensions(VolumeProjection_Axial);
-    const CoordinateSystem3D& axial = geometry_.GetAxialGeometry();
-    
-    std::auto_ptr<ParallelSlices> result(new ParallelSlices);
-
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        for (unsigned int z = 0; z < depth_; z++)
-        {
-          Vector origin = axial.GetOrigin();
-          origin += static_cast<double>(z) * dimensions[2] * axial.GetNormal();
-
-          result->AddSlice(origin,
-                           axial.GetAxisX(),
-                           axial.GetAxisY());
-        }
-        break;
-
-      case VolumeProjection_Coronal:
-        for (unsigned int y = 0; y < height_; y++)
-        {
-          Vector origin = axial.GetOrigin();
-          origin += static_cast<double>(y) * dimensions[1] * axial.GetAxisY();
-          origin += static_cast<double>(depth_ - 1) * dimensions[2] * axial.GetNormal();
-
-          result->AddSlice(origin,
-                           axial.GetAxisX(),
-                           -axial.GetNormal());
-        }
-        break;
-
-      case VolumeProjection_Sagittal:
-        for (unsigned int x = 0; x < width_; x++)
-        {
-          Vector origin = axial.GetOrigin();
-          origin += static_cast<double>(x) * dimensions[0] * axial.GetAxisX();
-          origin += static_cast<double>(depth_ - 1) * dimensions[2] * axial.GetNormal();
-
-          result->AddSlice(origin,
-                           axial.GetAxisY(),
-                           -axial.GetNormal());
-        }
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    return result.release();
-  }
-
-
   uint64_t ImageBuffer3D::GetEstimatedMemorySize() const
   {
     return image_.GetPitch() * image_.GetHeight() * Orthanc::GetBytesPerPixel(format_);
@@ -258,35 +197,6 @@
   }
 
 
-  bool ImageBuffer3D::FitWindowingToRange(Deprecated::RenderStyle& style,
-                                          const Deprecated::DicomFrameConverter& converter) const
-  {
-    if (hasRange_)
-    {
-      style.windowing_ = ImageWindowing_Custom;
-      
-      // casting the narrower type to wider before calling the + operator
-      // will prevent overflowing (this is why the cast to double is only 
-      // done on the first operand)
-      style.customWindowCenter_ = static_cast<float>(
-        converter.Apply((static_cast<double>(minValue_) + maxValue_) / 2.0));
-      
-      style.customWindowWidth_ = static_cast<float>(
-        converter.Apply(static_cast<double>(maxValue_) - minValue_));
-      
-      if (style.customWindowWidth_ > 1)
-      {
-        return true;
-      }
-    }
-
-    style.windowing_ = ImageWindowing_Custom;
-    style.customWindowCenter_ = 128.0;
-    style.customWindowWidth_ = 256.0;
-    return false;
-  }
-
-
   ImageBuffer3D::SliceReader::SliceReader(const ImageBuffer3D& that,
                                           VolumeProjection projection,
                                           unsigned int slice)
--- a/Framework/Volumes/ImageBuffer3D.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Volumes/ImageBuffer3D.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,10 +22,7 @@
 #pragma once
 
 #include "../StoneEnumerations.h"
-#include "../Layers/RenderStyle.h"
-#include "../Toolbox/VolumeImageGeometry.h"
-#include "../Toolbox/DicomFrameConverter.h"
-#include "../Toolbox/ParallelSlices.h"
+#include "../Toolbox/LinearAlgebra.h"
 
 #include <Core/Images/Image.h>
 
@@ -34,7 +31,6 @@
   class ImageBuffer3D : public boost::noncopyable
   {
   private:
-    VolumeImageGeometry    geometry_;  // TODO => Move this out of this class
     Orthanc::Image         image_;
     Orthanc::PixelFormat   format_;
     unsigned int           width_;
@@ -78,16 +74,6 @@
 
     void Clear();
 
-    VolumeImageGeometry& GetGeometry()
-    {
-      return geometry_;
-    }
-
-    const VolumeImageGeometry& GetGeometry() const
-    {
-      return geometry_;
-    }
-
     const Orthanc::ImageAccessor& GetInternalImage() const
     {
       return image_;
@@ -113,17 +99,16 @@
       return format_;
     }
 
-    // TODO - Remove
-    ParallelSlices* GetGeometry(VolumeProjection projection) const;
-    
+    unsigned int GetBytesPerPixel() const
+    {
+      return Orthanc::GetBytesPerPixel(format_);
+    }
+
     uint64_t GetEstimatedMemorySize() const;
 
     bool GetRange(float& minValue,
                   float& maxValue) const;
 
-    bool FitWindowingToRange(Deprecated::RenderStyle& style,
-                             const Deprecated::DicomFrameConverter& converter) const;
-
     uint8_t GetVoxelGrayscale8Unchecked(unsigned int x,
                                         unsigned int y,
                                         unsigned int z) const
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/OrientedVolumeBoundingBox.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,268 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrientedVolumeBoundingBox.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+#include "ImageBuffer3D.h"
+
+#include <Core/OrthancException.h>
+
+#include <cassert>
+
+namespace OrthancStone
+{
+  OrientedVolumeBoundingBox::OrientedVolumeBoundingBox(const VolumeImageGeometry& geometry)
+  {
+    unsigned int n = geometry.GetDepth();
+    if (n < 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);      
+    }
+
+    Vector dim = geometry.GetVoxelDimensions(VolumeProjection_Axial);
+
+    u_ = geometry.GetAxialGeometry().GetAxisX();
+    v_ = geometry.GetAxialGeometry().GetAxisY();
+    w_ = geometry.GetAxialGeometry().GetNormal();
+
+    hu_ = static_cast<double>(geometry.GetWidth() * dim[0] / 2.0);
+    hv_ = static_cast<double>(geometry.GetHeight() * dim[1] / 2.0);
+    hw_ = static_cast<double>(geometry.GetDepth() * dim[2] / 2.0);
+      
+    c_ = (geometry.GetAxialGeometry().GetOrigin() + 
+          (hu_ - dim[0] / 2.0) * u_ +
+          (hv_ - dim[1] / 2.0) * v_ +
+          (hw_ - dim[2] / 2.0) * w_);
+  }
+
+
+  bool OrientedVolumeBoundingBox::HasIntersectionWithPlane(std::vector<Vector>& points,
+                                                           const Vector& normal,
+                                                           double d) const
+  {
+    assert(normal.size() == 3);
+
+    double r = (hu_ * fabs(boost::numeric::ublas::inner_prod(normal, u_)) +
+                hv_ * fabs(boost::numeric::ublas::inner_prod(normal, v_)) +
+                hw_ * fabs(boost::numeric::ublas::inner_prod(normal, w_)));
+
+    double s = boost::numeric::ublas::inner_prod(normal, c_) + d;
+
+    if (fabs(s) >= r)
+    {
+      // No intersection, or intersection is reduced to a single point
+      return false;
+    }
+    else
+    {
+      Vector p;
+
+      // Loop over all the 12 edges (segments) of the oriented
+      // bounding box, and check whether they intersect the plane
+        
+      // X-aligned edges
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
+           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      // Y-aligned edges
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_,
+           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      // Z-aligned edges
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ - u_ * hu_ - v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ - v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ - v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ - u_ * hu_ + v_ * hv_ - w_ * hw_,
+           c_ - u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      if (GeometryToolbox::IntersectPlaneAndSegment
+          (p, normal, d,
+           c_ + u_ * hu_ + v_ * hv_ - w_ * hw_,
+           c_ + u_ * hu_ + v_ * hv_ + w_ * hw_))
+      {
+        points.push_back(p);
+      }
+
+      return true;
+    }
+  }
+
+
+  bool OrientedVolumeBoundingBox::HasIntersection(std::vector<Vector>& points,
+                                                  const CoordinateSystem3D& plane) const
+  {
+    // From the vector equation of a 3D plane (specified by origin
+    // and normal), to the general equation of a 3D plane (which
+    // looses information about the origin of the coordinate system)
+    const Vector& normal = plane.GetNormal();
+    const Vector& origin = plane.GetOrigin();
+    double d = -(normal[0] * origin[0] + normal[1] * origin[1] + normal[2] * origin[2]);
+
+    return HasIntersectionWithPlane(points, normal, d);
+  }
+  
+
+  bool OrientedVolumeBoundingBox::Contains(const Vector& p) const
+  {
+    assert(p.size() == 3);
+
+    const Vector q = p - c_;
+
+    return (fabs(boost::numeric::ublas::inner_prod(q, u_)) <= hu_ &&
+            fabs(boost::numeric::ublas::inner_prod(q, v_)) <= hv_ &&
+            fabs(boost::numeric::ublas::inner_prod(q, w_)) <= hw_);
+  }
+
+  
+  void OrientedVolumeBoundingBox::FromInternalCoordinates(Vector& target,
+                                                          double x,
+                                                          double y,
+                                                          double z) const
+  {
+    target = (c_ +
+              u_ * 2.0 * hu_ * (x - 0.5) +
+              v_ * 2.0 * hv_ * (y - 0.5) +
+              w_ * 2.0 * hw_ * (z - 0.5));
+  }
+
+  
+  void OrientedVolumeBoundingBox::FromInternalCoordinates(Vector& target,
+                                                          const Vector& source) const
+  {
+    assert(source.size() == 3);
+    FromInternalCoordinates(target, source[0], source[1], source[2]);
+  }
+
+  
+  void OrientedVolumeBoundingBox::ToInternalCoordinates(Vector& target,
+                                                        const Vector& source) const
+  {
+    assert(source.size() == 3);
+    const Vector q = source - c_;
+
+    double x = boost::numeric::ublas::inner_prod(q, u_) / (2.0 * hu_) + 0.5;
+    double y = boost::numeric::ublas::inner_prod(q, v_) / (2.0 * hv_) + 0.5;
+    double z = boost::numeric::ublas::inner_prod(q, w_) / (2.0 * hw_) + 0.5;
+
+    LinearAlgebra::AssignVector(target, x, y, z);
+  }
+
+
+  bool OrientedVolumeBoundingBox::ComputeExtent(Extent2D& extent,
+                                                const CoordinateSystem3D& plane) const
+  {
+    extent.Reset();
+    
+    std::vector<Vector> points;
+    if (HasIntersection(points, plane))
+    {
+      for (size_t i = 0; i < points.size(); i++)
+      {
+        double x, y;
+        plane.ProjectPoint(x, y, points[i]);
+        extent.AddPoint(x, y);
+      }
+      
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/OrientedVolumeBoundingBox.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox/CoordinateSystem3D.h"
+#include "../Toolbox/Extent2D.h"
+#include "../Toolbox/LinearAlgebra.h"
+#include "VolumeImageGeometry.h"
+
+namespace OrthancStone
+{
+  class OrientedVolumeBoundingBox : public boost::noncopyable
+  {
+  private:
+    Vector  c_;   // center
+    Vector  u_;   // normalized width vector
+    Vector  v_;   // normalized height vector
+    Vector  w_;   // normalized depth vector
+    double  hu_;  // half width
+    double  hv_;  // half height
+    double  hw_;  // half depth
+
+  public:
+    OrientedVolumeBoundingBox(const VolumeImageGeometry& geometry);
+
+    const Vector& GetCenter() const
+    {
+      return c_;
+    }
+
+    bool HasIntersectionWithPlane(std::vector<Vector>& points,
+                                  const Vector& normal,
+                                  double d) const;
+
+    bool HasIntersection(std::vector<Vector>& points,
+                         const CoordinateSystem3D& plane) const;
+
+    bool Contains(const Vector& p) const;
+
+    void FromInternalCoordinates(Vector& target,
+                                 double x,
+                                 double y,
+                                 double z) const;
+
+    void FromInternalCoordinates(Vector& target,
+                                 const Vector& source) const;
+
+    void ToInternalCoordinates(Vector& target,
+                               const Vector& source) const;
+
+    bool ComputeExtent(Extent2D& extent,
+                       const CoordinateSystem3D& plane) const;
+  };
+}
+
--- a/Framework/Volumes/StructureSetLoader.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "StructureSetLoader.h"
-
-#include "../Toolbox/MessagingToolbox.h"
-
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  StructureSetLoader::StructureSetLoader(OrthancStone::MessageBroker& broker,
-                                         OrthancApiClient& orthanc) :
-    IVolumeLoader(broker),
-    IObserver(broker),
-    orthanc_(orthanc)
-  {
-  }
-  
-
-  void StructureSetLoader::OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    OrthancPlugins::FullOrthancDataset dataset(message.GetJson());
-
-    Orthanc::DicomMap slice;
-    OrthancStone::MessagingToolbox::ConvertDataset(slice, dataset);
-    structureSet_->AddReferencedSlice(slice);
-
-    BroadcastMessage(ContentChangedMessage(*this));
-  }
-  
-
-  void StructureSetLoader::OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    OrthancPlugins::FullOrthancDataset dataset(message.GetJson());
-    structureSet_.reset(new OrthancStone::DicomStructureSet(dataset));
-
-    std::set<std::string> instances;
-    structureSet_->GetReferencedInstances(instances);
-
-    for (std::set<std::string>::const_iterator it = instances.begin();
-         it != instances.end(); ++it)
-    {
-      orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it,
-                                         new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted));
-    }
-
-    BroadcastMessage(GeometryReadyMessage(*this));
-  }
-
-  
-  void StructureSetLoader::OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message)
-  {
-    const Json::Value& lookup = message.GetJson();
-
-    if (lookup.type() != Json::arrayValue ||
-        lookup.size() != 1 ||
-        !lookup[0].isMember("Type") ||
-        !lookup[0].isMember("Path") ||
-        lookup[0]["Type"].type() != Json::stringValue ||
-        lookup[0]["ID"].type() != Json::stringValue ||
-        lookup[0]["Type"].asString() != "Instance")
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-
-    const std::string& instance = lookup[0]["ID"].asString();
-    orthanc_.GetJsonAsync("/instances/" + instance + "/tags",
-                          new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded));
-  }
-
-  
-  void StructureSetLoader::ScheduleLoadInstance(const std::string& instance)
-  {
-    if (structureSet_.get() != NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050",
-                            new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded));
-    }
-  }
-
-
-  OrthancStone::DicomStructureSet& StructureSetLoader::GetStructureSet()
-  {
-    if (structureSet_.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return *structureSet_;
-    }
-  }
-}
--- a/Framework/Volumes/StructureSetLoader.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Toolbox/DicomStructureSet.h"
-#include "../Toolbox/OrthancApiClient.h"
-#include "IVolumeLoader.h"
-
-namespace Deprecated
-{
-  class StructureSetLoader :
-    public IVolumeLoader,
-    public OrthancStone::IObserver
-  {
-  private:
-    OrthancApiClient&                 orthanc_;
-    std::auto_ptr<OrthancStone::DicomStructureSet>  structureSet_;
-
-    void OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-    void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message);
-
-  public:
-    StructureSetLoader(OrthancStone::MessageBroker& broker,
-                       OrthancApiClient& orthanc);
-
-    void ScheduleLoadInstance(const std::string& instance);
-
-    bool HasStructureSet() const
-    {
-      return structureSet_.get() != NULL;
-    }
-
-    OrthancStone::DicomStructureSet& GetStructureSet();
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageGeometry.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,326 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "VolumeImageGeometry.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  void VolumeImageGeometry::Invalidate()
+  {
+    Vector p = (axialGeometry_.GetOrigin() +
+                static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal());
+        
+    coronalGeometry_ = CoordinateSystem3D(p,
+                                          axialGeometry_.GetAxisX(),
+                                          -axialGeometry_.GetNormal());
+    
+    sagittalGeometry_ = CoordinateSystem3D(p,
+                                           axialGeometry_.GetAxisY(),
+                                           axialGeometry_.GetNormal());
+
+    Vector origin = (
+      axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0],
+                                                -0.5 * voxelDimensions_[1]) -
+      0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal());
+
+    Vector scaling;
+    
+    if (width_ == 0 ||
+        height_ == 0 ||
+        depth_ == 0)
+    {
+      LinearAlgebra::AssignVector(scaling, 1, 1, 1);
+    }
+    else
+    {
+      scaling = (
+        axialGeometry_.GetAxisX() * voxelDimensions_[0] * static_cast<double>(width_) +
+        axialGeometry_.GetAxisY() * voxelDimensions_[1] * static_cast<double>(height_) +
+        axialGeometry_.GetNormal() * voxelDimensions_[2] * static_cast<double>(depth_));
+    }
+
+    transform_ = LinearAlgebra::Product(
+      GeometryToolbox::CreateTranslationMatrix(origin[0], origin[1], origin[2]),
+      GeometryToolbox::CreateScalingMatrix(scaling[0], scaling[1], scaling[2]));
+
+    LinearAlgebra::InvertMatrix(transformInverse_, transform_);
+  }
+
+  
+  VolumeImageGeometry::VolumeImageGeometry() :
+    width_(0),
+    height_(0),
+    depth_(0)
+  {
+    LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
+    Invalidate();
+  }
+
+
+  void VolumeImageGeometry::SetSize(unsigned int width,
+                                    unsigned int height,
+                                    unsigned int depth)
+  {
+    width_ = width;
+    height_ = height;
+    depth_ = depth;
+    Invalidate();
+  }
+
+  
+  void VolumeImageGeometry::SetAxialGeometry(const CoordinateSystem3D& geometry)
+  {
+    axialGeometry_ = geometry;
+    Invalidate();
+  }
+
+
+  void VolumeImageGeometry::SetVoxelDimensions(double x,
+                                               double y,
+                                               double z)
+  {
+    if (x <= 0 ||
+        y <= 0 ||
+        z <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      LinearAlgebra::AssignVector(voxelDimensions_, x, y, z);
+      Invalidate();
+    }
+  }
+
+
+  const CoordinateSystem3D& VolumeImageGeometry::GetProjectionGeometry(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return axialGeometry_;
+
+      case VolumeProjection_Coronal:
+        return coronalGeometry_;
+
+      case VolumeProjection_Sagittal:
+        return sagittalGeometry_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  Vector VolumeImageGeometry::GetVoxelDimensions(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return voxelDimensions_;
+
+      case VolumeProjection_Coronal:
+        return LinearAlgebra::CreateVector(voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
+
+      case VolumeProjection_Sagittal:
+        return LinearAlgebra::CreateVector(voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int VolumeImageGeometry::GetProjectionWidth(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return width_;
+
+      case VolumeProjection_Coronal:
+        return width_;
+
+      case VolumeProjection_Sagittal:
+        return height_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int VolumeImageGeometry::GetProjectionHeight(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return height_;
+
+      case VolumeProjection_Coronal:
+        return depth_;
+
+      case VolumeProjection_Sagittal:
+        return depth_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int VolumeImageGeometry::GetProjectionDepth(VolumeProjection projection) const
+  {
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        return depth_;
+
+      case VolumeProjection_Coronal:
+        return height_;
+
+      case VolumeProjection_Sagittal:
+        return width_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }    
+  }
+
+
+  Vector VolumeImageGeometry::GetCoordinates(float x,
+                                             float y,
+                                             float z) const
+  {
+    Vector p = LinearAlgebra::Product(transform_, LinearAlgebra::CreateVector(x, y, z, 1));
+
+    assert(LinearAlgebra::IsNear(p[3], 1));  // Affine transform, no perspective effect
+
+    // Back to non-homogeneous coordinates
+    return LinearAlgebra::CreateVector(p[0], p[1], p[2]);
+  }
+
+
+  bool VolumeImageGeometry::DetectProjection(VolumeProjection& projection,
+                                             const Vector& planeNormal) const
+  {
+    if (GeometryToolbox::IsParallel(planeNormal, axialGeometry_.GetNormal()))
+    {
+      projection = VolumeProjection_Axial;
+      return true;
+    }
+    else if (GeometryToolbox::IsParallel(planeNormal, coronalGeometry_.GetNormal()))
+    {
+      projection = VolumeProjection_Coronal;
+      return true;
+    }
+    else if (GeometryToolbox::IsParallel(planeNormal, sagittalGeometry_.GetNormal()))
+    {
+      projection = VolumeProjection_Sagittal;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  bool VolumeImageGeometry::DetectSlice(VolumeProjection& projection,
+                                        unsigned int& slice,
+                                        const CoordinateSystem3D& plane) const
+  {
+    if (!DetectProjection(projection, plane.GetNormal()))
+    {
+      return false;
+    }
+
+    // Transforms the coordinates of the origin of the plane, into the
+    // coordinates of the axial geometry
+    const Vector& origin = plane.GetOrigin();
+    Vector p = LinearAlgebra::Product(
+      transformInverse_,
+      LinearAlgebra::CreateVector(origin[0], origin[1], origin[2], 1));
+
+    assert(LinearAlgebra::IsNear(p[3], 1));
+
+    double z;
+
+    switch (projection)
+    {
+      case VolumeProjection_Axial:
+        z = p[2];
+        break;
+
+      case VolumeProjection_Coronal:
+        z = p[1];
+        break;
+
+      case VolumeProjection_Sagittal:
+        z = p[0];
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const unsigned int projectionDepth = GetProjectionDepth(projection);
+    
+    z *= static_cast<double>(projectionDepth);
+    if (z < 0)
+    {
+      return false;
+    }
+        
+    unsigned int d = static_cast<unsigned int>(std::floor(z));
+    if (d >= projectionDepth)
+    {
+      return false;
+    }
+    else
+    {
+      slice = d;
+      return true;
+    }
+  }
+
+
+  CoordinateSystem3D VolumeImageGeometry::GetProjectionSlice(VolumeProjection projection,
+                                                             unsigned int z) const
+  {
+    if (z >= GetProjectionDepth(projection))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Vector dim = GetVoxelDimensions(projection);
+    CoordinateSystem3D plane = GetProjectionGeometry(projection);
+
+    plane.SetOrigin(plane.GetOrigin() + static_cast<double>(z) * plane.GetNormal() * dim[2]);
+
+    return plane;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeImageGeometry.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,134 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../StoneEnumerations.h"
+#include "../Toolbox/CoordinateSystem3D.h"
+
+namespace OrthancStone
+{
+  class VolumeImageGeometry
+  {
+  private:
+    unsigned int           width_;
+    unsigned int           height_;
+    unsigned int           depth_;
+    CoordinateSystem3D     axialGeometry_;
+    CoordinateSystem3D     coronalGeometry_;
+    CoordinateSystem3D     sagittalGeometry_;
+    Vector                 voxelDimensions_;
+    Matrix                 transform_;
+    Matrix                 transformInverse_;
+
+    void Invalidate();
+
+  public:
+    VolumeImageGeometry();
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetDepth() const
+    {
+      return depth_;
+    }
+
+    const CoordinateSystem3D& GetAxialGeometry() const
+    {
+      return axialGeometry_;
+    }
+
+    const CoordinateSystem3D& GetCoronalGeometry() const
+    {
+      return coronalGeometry_;
+    }
+
+    const CoordinateSystem3D& GetSagittalGeometry() const
+    {
+      return sagittalGeometry_;
+    }
+
+    const CoordinateSystem3D& GetProjectionGeometry(VolumeProjection projection) const;
+    
+    const Matrix& GetTransform() const
+    {
+      return transform_;
+    }
+
+    const Matrix& GetTransformInverse() const
+    {
+      return transformInverse_;
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 unsigned int depth);
+
+    // Set the geometry of the first axial slice (i.e. the one whose
+    // depth == 0)
+    void SetAxialGeometry(const CoordinateSystem3D& geometry);
+
+    void SetVoxelDimensions(double x,
+                            double y,
+                            double z);
+
+    Vector GetVoxelDimensions(VolumeProjection projection) const;
+
+    unsigned int GetProjectionWidth(VolumeProjection projection) const;
+
+    unsigned int GetProjectionHeight(VolumeProjection projection) const;
+
+    unsigned int GetProjectionDepth(VolumeProjection projection) const;
+
+    // Get the 3D position of a point in the volume, where x, y and z
+    // lie in the [0;1] range
+    Vector GetCoordinates(float x,
+                          float y,
+                          float z) const;
+
+    bool DetectProjection(VolumeProjection& projection,
+                          const Vector& planeNormal) const;
+
+    /**
+    Being given a cutting plane, this method will determine if it is an
+    axial, sagittal or coronal cut and returns 
+    the slice number corresponding to this cut.
+
+    If the cutting plane is not parallel to the tree x = 0, y = 0 or z = 0
+    planes, it is considered as arbitrary and the method returns false. 
+    Otherwise, it returns true.
+    */
+    bool DetectSlice(VolumeProjection& projection,
+                     unsigned int& slice,
+                     const CoordinateSystem3D& plane) const;
+
+    CoordinateSystem3D GetProjectionSlice(VolumeProjection projection,
+                                          unsigned int z) const;
+  };
+}
--- a/Framework/Volumes/VolumeReslicer.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Volumes/VolumeReslicer.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -230,7 +230,7 @@
       FastRowIterator(const Orthanc::ImageAccessor& slice,
                       const Extent2D& extent,
                       const CoordinateSystem3D& plane,
-                      const OrientedBoundingBox& box,
+                      const OrientedVolumeBoundingBox& box,
                       unsigned int y)
       {
         const double width = static_cast<double>(slice.GetWidth());
@@ -285,7 +285,7 @@
       const Orthanc::ImageAccessor&  slice_;
       const Extent2D&                extent_;
       const CoordinateSystem3D&      plane_;
-      const OrientedBoundingBox&     box_;
+      const OrientedVolumeBoundingBox&     box_;
       unsigned int                   x_;
       unsigned int                   y_;
       
@@ -293,7 +293,7 @@
       SlowRowIterator(const Orthanc::ImageAccessor& slice,
                       const Extent2D& extent,
                       const CoordinateSystem3D& plane,
-                      const OrientedBoundingBox& box,
+                      const OrientedVolumeBoundingBox& box,
                       unsigned int y) :
         slice_(slice),
         extent_(extent),
@@ -342,7 +342,7 @@
                              const Extent2D& extent,
                              const ImageBuffer3D& source,
                              const CoordinateSystem3D& plane,
-                             const OrientedBoundingBox& box,
+                             const OrientedVolumeBoundingBox& box,
                              float scaling,
                              float offset)
     {
@@ -386,7 +386,7 @@
                              const Extent2D& extent,
                              const ImageBuffer3D& source,
                              const CoordinateSystem3D& plane,
-                             const OrientedBoundingBox& box,
+                             const OrientedVolumeBoundingBox& box,
                              ImageInterpolation interpolation,
                              bool hasLinearFunction,
                              float scaling,
@@ -452,7 +452,7 @@
                              const Extent2D& extent,
                              const ImageBuffer3D& source,
                              const CoordinateSystem3D& plane,
-                             const OrientedBoundingBox& box,
+                             const OrientedVolumeBoundingBox& box,
                              ImageInterpolation interpolation,
                              bool hasLinearFunction,
                              float scaling,
@@ -501,7 +501,7 @@
 
   void VolumeReslicer::CheckIterators(const ImageBuffer3D& source,
                                       const CoordinateSystem3D& plane,
-                                      const OrientedBoundingBox& box) const
+                                      const OrientedVolumeBoundingBox& box) const
   {
     for (unsigned int y = 0; y < slice_->GetHeight(); y++)
     {
@@ -745,12 +745,13 @@
 
   
   void VolumeReslicer::Apply(const ImageBuffer3D& source,
+                             const VolumeImageGeometry& geometry,
                              const CoordinateSystem3D& plane)
   {
     // Choose the default voxel size as the finest voxel dimension
     // of the source volumetric image
     const OrthancStone::Vector dim =
-      source.GetGeometry().GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
+      geometry.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
     double voxelSize = dim[0];
     
     if (dim[1] < voxelSize)
@@ -768,22 +769,24 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
 
-    Apply(source, plane, voxelSize);
+    Apply(source, geometry, plane, voxelSize);
   }
 
   
   void VolumeReslicer::Apply(const ImageBuffer3D& source,
+                             const VolumeImageGeometry& geometry,
                              const CoordinateSystem3D& plane,
                              double voxelSize)
   {
     Reset();
+    pixelSpacing_ = voxelSize;
 
     // Firstly, compute the intersection of the source volumetric
     // image with the reslicing plane. This leads to a polygon with 3
     // to 6 vertices. We compute the extent of the intersection
     // polygon, with respect to the coordinate system of the reslicing
     // plane.
-    OrientedBoundingBox box(source);
+    OrientedVolumeBoundingBox box(geometry);
 
     if (!box.ComputeExtent(extent_, plane))
     {
@@ -815,4 +818,17 @@
 
     success_ = true;
   }
+
+
+  double VolumeReslicer::GetPixelSpacing() const
+  {
+    if (success_)
+    {
+      return pixelSpacing_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
 }
--- a/Framework/Volumes/VolumeReslicer.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Framework/Volumes/VolumeReslicer.h	Mon Jun 24 14:35:00 2019 +0200
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "../Toolbox/Extent2D.h"
-#include "../Toolbox/OrientedBoundingBox.h"
+#include "OrientedVolumeBoundingBox.h"
 #include "ImageBuffer3D.h"
 
 namespace OrthancStone
@@ -43,10 +43,11 @@
     bool                           success_;
     Extent2D                       extent_;
     std::auto_ptr<Orthanc::Image>  slice_;
+    double                         pixelSpacing_;
 
     void CheckIterators(const ImageBuffer3D& source,
                         const CoordinateSystem3D& plane,
-                        const OrientedBoundingBox& box) const;
+                        const OrientedVolumeBoundingBox& box) const;
 
     void Reset();
 
@@ -111,10 +112,14 @@
     Orthanc::ImageAccessor* ReleaseOutputSlice();
 
     void Apply(const ImageBuffer3D& source,
+               const VolumeImageGeometry& geometry,
                const CoordinateSystem3D& plane);
 
     void Apply(const ImageBuffer3D& source,
+               const VolumeImageGeometry& geometry,
                const CoordinateSystem3D& plane,
                double voxelSize);
+
+    double GetPixelSpacing() const;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeSceneLayerSource.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,143 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "VolumeSceneLayerSource.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  static bool IsSameCuttingPlane(const CoordinateSystem3D& a,
+                                 const CoordinateSystem3D& b)
+  {
+    // TODO - What if the normal is reversed?
+    double distance;
+    return (CoordinateSystem3D::ComputeDistance(distance, a, b) &&
+            LinearAlgebra::IsCloseToZero(distance));
+  }
+
+
+  void VolumeSceneLayerSource::ClearLayer()
+  {
+    scene_.DeleteLayer(layerDepth_);
+    lastPlane_.reset(NULL);
+  }
+
+
+  VolumeSceneLayerSource::VolumeSceneLayerSource(Scene2D& scene,
+                                                 int layerDepth,
+                                                 const boost::shared_ptr<IVolumeSlicer>& slicer) :
+    scene_(scene),
+    layerDepth_(layerDepth),
+    slicer_(slicer)
+  {
+    if (slicer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  void VolumeSceneLayerSource::RemoveConfigurator()
+  {
+    configurator_.reset();
+    lastPlane_.reset();
+  }
+
+
+  void VolumeSceneLayerSource::SetConfigurator(ILayerStyleConfigurator* configurator)  // Takes ownership
+  {
+    if (configurator == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    configurator_.reset(configurator);
+
+    // Invalidate the layer
+    lastPlane_.reset(NULL);
+  }
+
+
+  ILayerStyleConfigurator& VolumeSceneLayerSource::GetConfigurator() const
+  {
+    if (configurator_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+      
+    return *configurator_;
+  }
+
+
+  void VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane)
+  {
+    assert(slicer_.get() != NULL);
+    std::auto_ptr<IVolumeSlicer::IExtractedSlice> slice(slicer_->ExtractSlice(plane));
+
+    if (slice.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);        
+    }
+
+    if (!slice->IsValid())
+    {
+      // The slicer cannot handle this cutting plane: Clear the layer
+      ClearLayer();
+    }
+    else if (lastPlane_.get() != NULL &&
+             IsSameCuttingPlane(*lastPlane_, plane) &&
+             lastRevision_ == slice->GetRevision())
+    {
+      // The content of the slice has not changed: Don't update the
+      // layer content, but possibly update its style
+
+      if (configurator_.get() != NULL &&
+          configurator_->GetRevision() != lastConfiguratorRevision_ &&
+          scene_.HasLayer(layerDepth_))
+      {
+        configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));
+      }
+    }
+    else
+    {
+      // Content has changed: An update is needed
+      lastPlane_.reset(new CoordinateSystem3D(plane));
+      lastRevision_ = slice->GetRevision();
+
+      std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane));
+      if (layer.get() == NULL)
+      {
+        ClearLayer();
+      }
+      else
+      {
+        if (configurator_.get() != NULL)
+        {
+          lastConfiguratorRevision_ = configurator_->GetRevision();
+          configurator_->ApplyStyle(*layer);
+        }
+
+        scene_.SetLayer(layerDepth_, layer.release());
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeSceneLayerSource.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Scene2D/Scene2D.h"
+#include "IVolumeSlicer.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  /**
+     This class applies one "volume slicer" to a "3D volume", in order
+     to create one "2D scene layer" that will be set onto the "2D
+     scene". The style of the layer can be fine-tuned using a "layer
+     style configurator". The class only changes the layer if the
+     cutting plane has been modified since the last call to "Update()".
+   **/
+  class VolumeSceneLayerSource : public boost::noncopyable
+  {
+  private:
+    Scene2D&                                scene_;
+    int                                     layerDepth_;
+    boost::shared_ptr<IVolumeSlicer>        slicer_;
+    std::auto_ptr<ILayerStyleConfigurator>  configurator_;
+    std::auto_ptr<CoordinateSystem3D>       lastPlane_;
+    uint64_t                                lastRevision_;
+    uint64_t                                lastConfiguratorRevision_;
+
+    void ClearLayer();
+
+  public:
+    VolumeSceneLayerSource(Scene2D& scene,
+                           int layerDepth,
+                           const boost::shared_ptr<IVolumeSlicer>& slicer);
+
+    const IVolumeSlicer& GetSlicer() const
+    {
+      return *slicer_;
+    }
+
+    void RemoveConfigurator();
+
+    void SetConfigurator(ILayerStyleConfigurator* configurator);
+
+    bool HasConfigurator() const
+    {
+      return configurator_.get() != NULL;
+    }
+
+    ILayerStyleConfigurator& GetConfigurator() const;
+
+    void Update(const CoordinateSystem3D& plane);  
+  };
+}
--- a/Framework/Widgets/CairoWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "CairoWidget.h"
-
-#include <Core/Images/ImageProcessing.h>
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  static bool IsAligned(const Orthanc::ImageAccessor& target)
-  {
-    // TODO
-    return true;
-  }
-
-  CairoWidget::CairoWidget(const std::string& name) :
-    WidgetBase(name)
-  {
-  }
-
-  void CairoWidget::SetSize(unsigned int width,
-                            unsigned int height)
-  {
-    surface_.SetSize(width, height, false /* no alpha */);
-  }
-  
-
-  bool CairoWidget::Render(Orthanc::ImageAccessor& target)
-  {
-    // Don't call the base class here, as
-    // "ClearBackgroundCairo()" is a faster alternative
-
-    if (IsAligned(target))
-    {
-      OrthancStone::CairoSurface surface(target, false /* no alpha */);
-      OrthancStone::CairoContext context(surface);
-      ClearBackgroundCairo(context);
-      return RenderCairo(context);
-    }
-    else
-    {
-      OrthancStone::CairoContext context(surface_);
-      ClearBackgroundCairo(context);
-
-      if (RenderCairo(context))
-      {
-        Orthanc::ImageAccessor surface;
-        surface_.GetReadOnlyAccessor(surface);
-        Orthanc::ImageProcessing::Copy(target, surface);
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  }
-
-
-  void CairoWidget::RenderMouseOver(Orthanc::ImageAccessor& target,
-                                    int x,
-                                    int y)
-  {
-    if (IsAligned(target))
-    {
-      OrthancStone::CairoSurface surface(target, false /* no alpha */);
-      OrthancStone::CairoContext context(surface);
-      RenderMouseOverCairo(context, x, y);
-    }
-    else
-    {
-      Orthanc::ImageAccessor accessor;
-      surface_.GetWriteableAccessor(accessor);
-      Orthanc::ImageProcessing::Copy(accessor, target);
-
-      OrthancStone::CairoContext context(surface_);
-      RenderMouseOverCairo(context, x, y);
-
-      Orthanc::ImageProcessing::Copy(target, accessor);
-    }
-  }
-}
--- a/Framework/Widgets/CairoWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WidgetBase.h"
-
-namespace Deprecated
-{
-  class CairoWidget : public WidgetBase
-  {
-  private:
-    OrthancStone::CairoSurface   surface_;
-
-  protected:
-    virtual bool RenderCairo(OrthancStone::CairoContext& context) = 0;
-    
-    virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context,
-                                      int x,
-                                      int y) = 0;
-    
-  public:
-    CairoWidget(const std::string& name);
-
-    virtual void SetSize(unsigned int width,
-                         unsigned int height);
-
-    virtual bool Render(Orthanc::ImageAccessor& target);
-
-    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                 int x,
-                                 int y);  
-  };
-}
--- a/Framework/Widgets/EmptyWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "EmptyWidget.h"
-
-#include <Core/Images/ImageProcessing.h>
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
-  {
-    // Note: This call is slow
-    Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
-    return true;
-  }
-
-
-  void EmptyWidget::DoAnimation()
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-}
--- a/Framework/Widgets/EmptyWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IWidget.h"
-
-namespace Deprecated
-{
-  /**
-   * This is a test widget that simply fills its surface with an
-   * uniform color.
-   **/
-  class EmptyWidget : public IWidget
-  {
-  private:
-    uint8_t  red_;
-    uint8_t  green_;
-    uint8_t  blue_;
-
-  public:
-    EmptyWidget(uint8_t red,
-                uint8_t green,
-                uint8_t blue) :
-      red_(red),
-      green_(green),
-      blue_(blue)
-    {
-    }
-
-    virtual void FitContent()
-    {
-    }
-
-    virtual void SetParent(IWidget& widget)
-    {
-    }
-
-    virtual void SetViewport(WidgetViewport& viewport)
-    {
-    }
-
-    virtual void NotifyContentChanged()
-    {
-    }
-
-    virtual void SetStatusBar(IStatusBar& statusBar)
-    {
-    }
-
-    virtual void SetSize(unsigned int width,
-                         unsigned int height)
-    {
-    }
-
-    virtual bool Render(Orthanc::ImageAccessor& surface);
-
-    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
-                                              int x,
-                                              int y,
-                                              OrthancStone::KeyboardModifiers modifiers,
-                                              const std::vector<Touch>& touches)
-    {
-      return NULL;
-    }
-
-    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                 int x,
-                                 int y)
-    {
-    }
-
-    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            OrthancStone::KeyboardModifiers modifiers)
-    {
-    }
-
-    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers)
-    {
-    }
-
-    virtual bool HasAnimation() const
-    {
-      return false;
-    }
-
-    virtual void DoAnimation();
-
-    virtual bool HasRenderMouseOver()
-    {
-      return false;
-    }
-  };
-}
--- a/Framework/Widgets/IWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../StoneEnumerations.h"
-#include "../Viewport/IMouseTracker.h"
-#include "../Viewport/IStatusBar.h"
-
-namespace Deprecated
-{
-  class WidgetViewport;  // Forward declaration
-  
-  class IWidget : public boost::noncopyable
-  {
-  public:
-    virtual ~IWidget()
-    {
-    }
-
-    virtual void FitContent() = 0;
-
-    virtual void SetParent(IWidget& parent) = 0;
-    
-    virtual void SetViewport(WidgetViewport& viewport) = 0;
-
-    virtual void SetStatusBar(IStatusBar& statusBar) = 0;
-
-    virtual void SetSize(unsigned int width, 
-                         unsigned int height) = 0;
- 
-    virtual bool Render(Orthanc::ImageAccessor& surface) = 0;
-
-    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
-                                              int x,
-                                              int y,
-                                              OrthancStone::KeyboardModifiers modifiers,
-                                              const std::vector<Touch>& touches) = 0;
-
-    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                 int x,
-                                 int y) = 0;
-
-    virtual bool HasRenderMouseOver() = 0;
-
-    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            OrthancStone::KeyboardModifiers modifiers) = 0;
-
-    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers) = 0;
-
-    virtual bool HasAnimation() const = 0;
-
-    virtual void DoAnimation() = 0;
-
-    // Subclasses can call this method to signal the display of the
-    // widget must be refreshed
-    virtual void NotifyContentChanged() = 0;
-  };
-}
--- a/Framework/Widgets/IWorldSceneInteractor.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IWorldSceneMouseTracker.h"
-
-#include "../Toolbox/ViewportGeometry.h"
-#include "../StoneEnumerations.h"
-#include "../Viewport/IStatusBar.h"
-
-namespace Deprecated
-{
-    class WorldSceneWidget;
-
-    class IWorldSceneInteractor : public boost::noncopyable
-    {
-    public:
-        virtual ~IWorldSceneInteractor()
-        {
-        }
-
-        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                            const ViewportGeometry& view,
-                                                            OrthancStone::MouseButton button,
-                                                            OrthancStone::KeyboardModifiers modifiers,
-                                                            int viewportX,
-                                                            int viewportY,
-                                                            double x,
-                                                            double y,
-                                                            IStatusBar* statusBar,
-                                                            const std::vector<Touch>& touches) = 0;
-
-        virtual void MouseOver(OrthancStone::CairoContext& context,
-                               WorldSceneWidget& widget,
-                               const ViewportGeometry& view,
-                               double x,
-                               double y,
-                               IStatusBar* statusBar) = 0;
-
-        virtual void MouseWheel(WorldSceneWidget& widget,
-                                OrthancStone::MouseWheelDirection direction,
-                                OrthancStone::KeyboardModifiers modifiers,
-                                IStatusBar* statusBar) = 0;
-
-        virtual void KeyPressed(WorldSceneWidget& widget,
-                                OrthancStone::KeyboardKeys key,
-                                char keyChar,
-                                OrthancStone::KeyboardModifiers modifiers,
-                                IStatusBar* statusBar) = 0;
-    };
-}
--- a/Framework/Widgets/IWorldSceneMouseTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Viewport/CairoContext.h"
-#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
-
-namespace Deprecated
-{
-
-  // this is tracking a mouse in scene coordinates/mm unlike
-  // the IMouseTracker that is tracking a mouse
-  // in screen coordinates/pixels.
-  class IWorldSceneMouseTracker : public boost::noncopyable
-  {
-  public:
-    virtual ~IWorldSceneMouseTracker()
-    {
-    }
-
-    virtual bool HasRender() const = 0;
-
-    virtual void Render(OrthancStone::CairoContext& context,
-                        double zoom) = 0;
-
-    virtual void MouseUp() = 0;
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double sceneX,
-                           double sceneY,
-                           const std::vector<Touch>& displayTouches,
-                           const std::vector<Touch>& sceneTouches) = 0;
-  };
-}
--- a/Framework/Widgets/LayoutWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,503 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "LayoutWidget.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-#include <boost/math/special_functions/round.hpp>
-
-namespace Deprecated
-{
-  class LayoutWidget::LayoutMouseTracker : public IMouseTracker
-  {
-  private:
-    std::auto_ptr<IMouseTracker>   tracker_;
-    int                            left_;
-    int                            top_;
-    unsigned int                   width_;
-    unsigned int                   height_;
-
-  public:
-    LayoutMouseTracker(IMouseTracker* tracker,
-                       int left,
-                       int top,
-                       unsigned int width,
-                       unsigned int height) :
-      tracker_(tracker),
-      left_(left),
-      top_(top),
-      width_(width),
-      height_(height)
-    {
-      if (tracker == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-    virtual void Render(Orthanc::ImageAccessor& surface)
-    {
-      Orthanc::ImageAccessor accessor;
-      surface.GetRegion(accessor, left_, top_, width_, height_);
-      tracker_->Render(accessor);
-    }
-
-    virtual void MouseUp()
-    {
-      tracker_->MouseUp();
-    }
-
-    virtual void MouseMove(int x, 
-                           int y,
-                           const std::vector<Touch>& displayTouches)
-    {
-      std::vector<Touch> relativeTouches;
-      for (size_t t = 0; t < displayTouches.size(); t++)
-      {
-        relativeTouches.push_back(Touch(displayTouches[t].x - left_, displayTouches[t].y - top_));
-      }
-
-      tracker_->MouseMove(x - left_, y - top_, relativeTouches);
-    }
-  };
-
-
-  class LayoutWidget::ChildWidget : public boost::noncopyable
-  {
-  private:
-    std::auto_ptr<IWidget>  widget_;
-    int                     left_;
-    int                     top_;
-    unsigned int            width_;
-    unsigned int            height_;
-
-  public:
-    ChildWidget(IWidget* widget) :
-      widget_(widget)
-    {
-      assert(widget != NULL);
-      SetEmpty();
-    }
-
-    void DoAnimation()
-    {
-      if (widget_->HasAnimation())
-      {
-        widget_->DoAnimation();
-      }
-    }
-
-    IWidget& GetWidget() const
-    {
-      return *widget_;
-    }
-
-    void SetRectangle(unsigned int left, 
-                      unsigned int top,
-                      unsigned int width,
-                      unsigned int height)
-    {
-      left_ = left;
-      top_ = top;
-      width_ = width;
-      height_ = height;
-
-      widget_->SetSize(width, height);
-    }
-
-    void SetEmpty()
-    {
-      SetRectangle(0, 0, 0, 0);
-    }
-
-    bool Contains(int x, 
-                  int y) const
-    {
-      return (x >= left_ && 
-              y >= top_ &&
-              x < left_ + static_cast<int>(width_) &&
-              y < top_ + static_cast<int>(height_));
-    }      
-
-    bool Render(Orthanc::ImageAccessor& target)
-    {
-      if (width_ == 0 ||
-          height_ == 0)
-      {
-        return true;
-      }
-      else 
-      {
-        Orthanc::ImageAccessor accessor;
-        target.GetRegion(accessor, left_, top_, width_, height_);
-        return widget_->Render(accessor);
-      }
-    }
-
-    IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
-                                      int x,
-                                      int y,
-                                      OrthancStone::KeyboardModifiers modifiers,
-                                      const std::vector<Touch>& touches)
-    {
-      if (Contains(x, y))
-      {
-        IMouseTracker* tracker = widget_->CreateMouseTracker(button, 
-                                                             x - left_, 
-                                                             y - top_, 
-                                                             modifiers,
-                                                             touches);
-        if (tracker)
-        {
-          return new LayoutMouseTracker(tracker, left_, top_, width_, height_);
-        }
-      }
-
-      return NULL;
-    }
-
-    void RenderMouseOver(Orthanc::ImageAccessor& target,
-                         int x,
-                         int y)
-    {
-      if (Contains(x, y))
-      {
-        Orthanc::ImageAccessor accessor;
-        target.GetRegion(accessor, left_, top_, width_, height_);
-
-        widget_->RenderMouseOver(accessor, x - left_, y - top_);
-      }
-    }
-
-    void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                    int x,
-                    int y,
-                    OrthancStone::KeyboardModifiers modifiers)
-    {
-      if (Contains(x, y))
-      {
-        widget_->MouseWheel(direction, x - left_, y - top_, modifiers);
-      }
-    }
-    
-    bool HasRenderMouseOver()
-    {
-      return widget_->HasRenderMouseOver();
-    }
-  };
-
-
-  void LayoutWidget::ComputeChildrenExtents()
-  {
-    if (children_.size() == 0)
-    {
-      return;
-    }
-
-    float internal = static_cast<float>(paddingInternal_);
-
-    if (width_ <= paddingLeft_ + paddingRight_ ||
-        height_ <= paddingTop_ + paddingBottom_)
-    {
-      for (size_t i = 0; i < children_.size(); i++)
-      {
-        children_[i]->SetEmpty();          
-      }
-    }
-    else if (isHorizontal_)
-    {
-      unsigned int padding = paddingLeft_ + paddingRight_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_;
-      float childWidth = ((static_cast<float>(width_) - static_cast<float>(padding)) / 
-                          static_cast<float>(children_.size()));
-        
-      for (size_t i = 0; i < children_.size(); i++)
-      {
-        float left = static_cast<float>(paddingLeft_) + static_cast<float>(i) * (childWidth + internal);
-        float right = left + childWidth;
-
-        if (left >= right)
-        {
-          children_[i]->SetEmpty();
-        }
-        else
-        {
-          children_[i]->SetRectangle(static_cast<unsigned int>(left), 
-                                     paddingTop_, 
-                                     boost::math::iround(right - left),
-                                     height_ - paddingTop_ - paddingBottom_);
-        }
-      }
-    }
-    else
-    {
-      unsigned int padding = paddingTop_ + paddingBottom_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_;
-      float childHeight = ((static_cast<float>(height_) - static_cast<float>(padding)) / 
-                           static_cast<float>(children_.size()));
-        
-      for (size_t i = 0; i < children_.size(); i++)
-      {
-        float top = static_cast<float>(paddingTop_) + static_cast<float>(i) * (childHeight + internal);
-        float bottom = top + childHeight;
-
-        if (top >= bottom)
-        {
-          children_[i]->SetEmpty();
-        }
-        else
-        {
-          children_[i]->SetRectangle(paddingTop_, 
-                                     static_cast<unsigned int>(top), 
-                                     width_ - paddingLeft_ - paddingRight_,
-                                     boost::math::iround(bottom - top));
-        }
-      }
-    }
-
-    NotifyContentChanged(*this);
-  }
-
-
-  LayoutWidget::LayoutWidget(const std::string& name) :
-    WidgetBase(name),
-    isHorizontal_(true),
-    width_(0),
-    height_(0),
-    paddingLeft_(0),
-    paddingTop_(0),
-    paddingRight_(0),
-    paddingBottom_(0),
-    paddingInternal_(0)
-  {
-  }
-
-
-  LayoutWidget::~LayoutWidget()
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      delete children_[i];
-    }
-  }
-
-
-  void LayoutWidget::FitContent()
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->GetWidget().FitContent();
-    }
-  }
-  
-
-  void LayoutWidget::NotifyContentChanged(const IWidget& widget)
-  {
-    // One of the children has changed
-    WidgetBase::NotifyContentChanged();
-  }
-
-
-  void LayoutWidget::SetHorizontal()
-  {
-    isHorizontal_ = true;
-    ComputeChildrenExtents();
-  }
-
-
-  void LayoutWidget::SetVertical()
-  {
-    isHorizontal_ = false;
-    ComputeChildrenExtents();
-  }
-
-
-  void LayoutWidget::SetPadding(unsigned int left,
-                                unsigned int top,
-                                unsigned int right,
-                                unsigned int bottom,
-                                unsigned int spacing)
-  {
-    paddingLeft_ = left;
-    paddingTop_ = top;
-    paddingRight_ = right;
-    paddingBottom_ = bottom;
-    paddingInternal_ = spacing;
-  }
-    
-
-  void LayoutWidget::SetPadding(unsigned int padding)
-  {
-    paddingLeft_ = padding;
-    paddingTop_ = padding;
-    paddingRight_ = padding;
-    paddingBottom_ = padding;
-    paddingInternal_ = padding;
-  }
-
-
-  IWidget& LayoutWidget::AddWidget(IWidget* widget)  // Takes ownership
-  {
-    if (widget == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    if (GetStatusBar() != NULL)
-    {
-      widget->SetStatusBar(*GetStatusBar());
-    }
-
-    children_.push_back(new ChildWidget(widget));
-    widget->SetParent(*this);
-
-    ComputeChildrenExtents();
-
-    if (widget->HasAnimation())
-    {
-      hasAnimation_ = true;
-    }
-
-    return *widget;
-  }
-
-
-  void LayoutWidget::SetStatusBar(IStatusBar& statusBar)
-  {
-    WidgetBase::SetStatusBar(statusBar);
-
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->GetWidget().SetStatusBar(statusBar);
-    }
-  }
-
-
-  void LayoutWidget::SetSize(unsigned int width,
-                             unsigned int height)
-  {
-    width_ = width;
-    height_ = height;
-    ComputeChildrenExtents();
-  }
-
-
-  bool LayoutWidget::Render(Orthanc::ImageAccessor& surface)
-  {
-    if (!WidgetBase::Render(surface))
-    {
-      return false;
-    }
-
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      if (!children_[i]->Render(surface))
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-    
-  IMouseTracker* LayoutWidget::CreateMouseTracker(OrthancStone::MouseButton button,
-                                                  int x,
-                                                  int y,
-                                                  OrthancStone::KeyboardModifiers modifiers,
-                                                  const std::vector<Touch>& touches)
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers, touches);
-      if (tracker != NULL)
-      {
-        return tracker;
-      }
-    }
-
-    return NULL;
-  }
-
-
-  void LayoutWidget::RenderMouseOver(Orthanc::ImageAccessor& target,
-                                     int x,
-                                     int y)
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->RenderMouseOver(target, x, y);
-    }
-  }
-
-
-  void LayoutWidget::MouseWheel(OrthancStone::MouseWheelDirection direction,
-                                int x,
-                                int y,
-                                OrthancStone::KeyboardModifiers modifiers)
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->MouseWheel(direction, x, y, modifiers);
-    }
-  }
-
-
-  void LayoutWidget::KeyPressed(OrthancStone::KeyboardKeys key,
-                                char keyChar,
-                                OrthancStone::KeyboardModifiers modifiers)
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->GetWidget().KeyPressed(key, keyChar, modifiers);
-    }
-  }
-
-  
-  void LayoutWidget::DoAnimation()
-  {
-    if (hasAnimation_)
-    {
-      for (size_t i = 0; i < children_.size(); i++)
-      {
-        children_[i]->DoAnimation();
-      }
-    }
-    else
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  bool LayoutWidget::HasRenderMouseOver()
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      if (children_[i]->HasRenderMouseOver())
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-}
--- a/Framework/Widgets/LayoutWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WidgetBase.h"
-
-#include <vector>
-#include <memory>
-
-namespace Deprecated
-{
-  class LayoutWidget : public WidgetBase
-  {
-  private:
-    class LayoutMouseTracker;
-    class ChildWidget;
-
-    std::vector<ChildWidget*>     children_;
-    bool                          isHorizontal_;
-    unsigned int                  width_;
-    unsigned int                  height_;
-    std::auto_ptr<IMouseTracker>  mouseTracker_;
-    unsigned int                  paddingLeft_;
-    unsigned int                  paddingTop_;
-    unsigned int                  paddingRight_;
-    unsigned int                  paddingBottom_;
-    unsigned int                  paddingInternal_;
-    bool                          hasAnimation_;
-
-    void ComputeChildrenExtents();
-
-  public:
-    LayoutWidget(const std::string& name);
-
-    virtual ~LayoutWidget();
-
-    virtual void FitContent();
-
-    virtual void NotifyContentChanged(const IWidget& widget);
-
-    void SetHorizontal();
-
-    void SetVertical();
-
-    void SetPadding(unsigned int left,
-                    unsigned int top,
-                    unsigned int right,
-                    unsigned int bottom,
-                    unsigned int spacing);
-    
-    void SetPadding(unsigned int padding);
-
-    unsigned int GetPaddingLeft() const
-    {
-      return paddingLeft_;
-    }
-
-    unsigned int GetPaddingTop() const
-    {
-      return paddingTop_;
-    }
-
-    unsigned int GetPaddingRight() const
-    {
-      return paddingRight_;
-    }
-
-    unsigned int GetPaddingBottom() const
-    {
-      return paddingBottom_;
-    }
-
-    unsigned int GetPaddingInternal() const
-    {
-      return paddingInternal_;
-    }
-
-    IWidget& AddWidget(IWidget* widget);  // Takes ownership
-
-    virtual void SetStatusBar(IStatusBar& statusBar);
-
-    virtual void SetSize(unsigned int width,
-                         unsigned int height);
-
-    virtual bool Render(Orthanc::ImageAccessor& surface);
-    
-    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
-                                              int x,
-                                              int y,
-                                              OrthancStone::KeyboardModifiers modifiers,
-                                              const std::vector<Touch>& touches);
-
-    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                 int x,
-                                 int y);
-
-    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            OrthancStone::KeyboardModifiers modifiers);
-
-    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers);
-
-    virtual bool HasAnimation() const
-    {
-      return hasAnimation_;
-    }
-
-    virtual void DoAnimation();
-
-    virtual bool HasRenderMouseOver();
-  };
-}
--- a/Framework/Widgets/PanMouseTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PanMouseTracker.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  PanMouseTracker::PanMouseTracker(WorldSceneWidget& that,
-                                   int x,
-                                   int y) :
-    that_(that)
-  {
-    that.GetView().GetPan(originalPanX_, originalPanY_);
-    that.GetView().MapPixelCenterToScene(downX_, downY_, x, y);
-  }
-    
-
-  void PanMouseTracker::Render(OrthancStone::CairoContext& context,
-                               double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void PanMouseTracker::MouseMove(int displayX,
-                                  int displayY,
-                                  double x,
-                                  double y,
-                                  const std::vector<Touch>& displayTouches,
-                                  const std::vector<Touch>& sceneTouches)
-  {
-    ViewportGeometry view = that_.GetView();
-    view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(),
-                originalPanY_ + (y - downY_) * view.GetZoom());
-    that_.SetView(view);
-  }
-}
--- a/Framework/Widgets/PanMouseTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WorldSceneWidget.h"
-
-namespace Deprecated
-{
-  class PanMouseTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    WorldSceneWidget&  that_;
-    double             originalPanX_;
-    double             originalPanY_;
-    double             downX_;
-    double             downY_;
-    
-  public:
-    PanMouseTracker(WorldSceneWidget& that,
-                    int x,
-                    int y);
-    
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void MouseUp()
-    {
-    }
-
-    virtual void Render(OrthancStone::CairoContext& context,
-                        double zoom);
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double x,
-                           double y,
-                           const std::vector<Touch>& displayTouches,
-                           const std::vector<Touch>& sceneTouches);
-  };
-}
--- a/Framework/Widgets/PanZoomMouseTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PanZoomMouseTracker.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-#include <math.h>
-
-namespace Deprecated
-{
-  Touch GetCenter(const std::vector<Touch>& touches)
-  {
-    return Touch((touches[0].x + touches[1].x) / 2.0f, (touches[0].y + touches[1].y) / 2.0f);
-  }
-
-  double GetDistance(const std::vector<Touch>& touches)
-  {
-    float dx = touches[0].x - touches[1].x;
-    float dy = touches[0].y - touches[1].y;
-    return sqrt((double)(dx * dx) + (double)(dy * dy));
-  }
-
-
-  PanZoomMouseTracker::PanZoomMouseTracker(WorldSceneWidget& that,
-                                           const std::vector<Touch>& startTouches)
-    : that_(that),
-      originalZoom_(that.GetView().GetZoom())
-  {
-    that.GetView().GetPan(originalPanX_, originalPanY_);
-    that.GetView().MapPixelCenterToScene(originalSceneTouches_, startTouches);
-
-    originalDisplayCenter_ = GetCenter(startTouches);
-    originalSceneCenter_ = GetCenter(originalSceneTouches_);
-    originalDisplayDistanceBetweenTouches_ = GetDistance(startTouches);
-
-//    printf("original Pan %f %f\n", originalPanX_, originalPanY_);
-//    printf("original Zoom %f \n", originalZoom_);
-//    printf("original distance %f \n", (float)originalDisplayDistanceBetweenTouches_);
-//    printf("original display touches 0 %f %f\n", startTouches[0].x, startTouches[0].y);
-//    printf("original display touches 1 %f %f\n", startTouches[1].x, startTouches[1].y);
-//    printf("original Scene center %f %f\n", originalSceneCenter_.x, originalSceneCenter_.y);
-
-    unsigned int height = that.GetView().GetDisplayHeight();
-
-    if (height <= 3)
-    {
-      idle_ = true;
-      LOG(WARNING) << "image is too small to zoom (current height = " << height << ")";
-    }
-    else
-    {
-      idle_ = false;
-      normalization_ = 1.0 / static_cast<double>(height - 1);
-    }
-
-  }
-
-
-  void PanZoomMouseTracker::Render(OrthancStone::CairoContext& context,
-                                   double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void PanZoomMouseTracker::MouseMove(int displayX,
-                                      int displayY,
-                                      double x,
-                                      double y,
-                                      const std::vector<Touch>& displayTouches,
-                                      const std::vector<Touch>& sceneTouches)
-  {
-    ViewportGeometry view = that_.GetView();
-
-//    printf("Display touches 0 %f %f\n", displayTouches[0].x, displayTouches[0].y);
-//    printf("Display touches 1 %f %f\n", displayTouches[1].x, displayTouches[1].y);
-//    printf("Scene touches 0 %f %f\n", sceneTouches[0].x, sceneTouches[0].y);
-//    printf("Scene touches 1 %f %f\n", sceneTouches[1].x, sceneTouches[1].y);
-
-//    printf("zoom = %f\n", view.GetZoom());
-    Touch currentSceneCenter = GetCenter(sceneTouches);
-    double panX = originalPanX_ + (currentSceneCenter.x - originalSceneCenter_.x) * view.GetZoom();
-    double panY = originalPanY_ + (currentSceneCenter.y - originalSceneCenter_.y) * view.GetZoom();
-
-    view.SetPan(panX, panY);
-
-    static const double MIN_ZOOM = -4;
-    static const double MAX_ZOOM = 4;
-
-    if (!idle_)
-    {
-      double currentDistanceBetweenTouches = GetDistance(displayTouches);
-
-      double dy = static_cast<double>(currentDistanceBetweenTouches - originalDisplayDistanceBetweenTouches_) * normalization_;  // In the range [-1,1]
-      double z;
-
-      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
-      if (dy < -1.0)
-      {
-        z = MIN_ZOOM;
-      }
-      else if (dy > 1.0)
-      {
-        z = MAX_ZOOM;
-      }
-      else
-      {
-        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
-      }
-
-      z = pow(2.0, z);
-
-      view.SetZoom(z * originalZoom_);
-    }
-
-    that_.SetView(view);
-  }
-}
--- a/Framework/Widgets/PanZoomMouseTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WorldSceneWidget.h"
-
-namespace Deprecated
-{
-  class PanZoomMouseTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    WorldSceneWidget&  that_;
-    std::vector<Touch> originalSceneTouches_;
-    Touch              originalSceneCenter_;
-    Touch              originalDisplayCenter_;
-    double             originalPanX_;
-    double             originalPanY_;
-    double             originalZoom_;
-    double             originalDisplayDistanceBetweenTouches_;
-    bool               idle_;
-    double             normalization_;
-
-  public:
-    PanZoomMouseTracker(WorldSceneWidget& that,
-                        const std::vector<Touch>& startTouches);
-    
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void MouseUp()
-    {
-    }
-
-    virtual void Render(OrthancStone::CairoContext& context,
-                        double zoom);
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double x,
-                           double y,
-                           const std::vector<Touch>& displayTouches,
-                           const std::vector<Touch>& sceneTouches);
-  };
-}
--- a/Framework/Widgets/SliceViewerWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,654 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SliceViewerWidget.h"
-
-#include "../Layers/SliceOutlineRenderer.h"
-#include "../Toolbox/GeometryToolbox.h"
-#include "Framework/Layers/FrameRenderer.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-#include <boost/math/constants/constants.hpp>
-
-
-static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon();
-
-namespace Deprecated
-{
-  class SliceViewerWidget::Scene : public boost::noncopyable
-  {
-  private:
-    OrthancStone::CoordinateSystem3D            plane_;
-    double                        thickness_;
-    size_t                        countMissing_;
-    std::vector<ILayerRenderer*>  renderers_;
-
-  public:
-    void DeleteLayer(size_t index)
-    {
-      if (index >= renderers_.size())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      assert(countMissing_ <= renderers_.size());
-
-      if (renderers_[index] != NULL)
-      {
-        assert(countMissing_ < renderers_.size());
-        delete renderers_[index];
-        renderers_[index] = NULL;
-        countMissing_++;
-      }
-    }
-
-    Scene(const OrthancStone::CoordinateSystem3D& plane,
-          double thickness,
-          size_t countLayers) :
-      plane_(plane),
-      thickness_(thickness),
-      countMissing_(countLayers),
-      renderers_(countLayers, NULL)
-    {
-      if (thickness <= 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    ~Scene()
-    {
-      for (size_t i = 0; i < renderers_.size(); i++)
-      {
-        DeleteLayer(i);
-      }
-    }
-
-    void SetLayer(size_t index,
-                  ILayerRenderer* renderer)  // Takes ownership
-    {
-      if (renderer == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-
-      DeleteLayer(index);
-
-      renderers_[index] = renderer;
-      countMissing_--;
-    }
-
-    const OrthancStone::CoordinateSystem3D& GetPlane() const
-    {
-      return plane_;
-    }
-
-    bool HasRenderer(size_t index)
-    {
-      return renderers_[index] != NULL;
-    }
-
-    bool IsComplete() const
-    {
-      return countMissing_ == 0;
-    }
-
-    unsigned int GetCountMissing() const
-    {
-      return static_cast<unsigned int>(countMissing_);
-    }
-
-    bool RenderScene(OrthancStone::CairoContext& context,
-                     const ViewportGeometry& view,
-                     const OrthancStone::CoordinateSystem3D& viewportPlane)
-    {
-      bool fullQuality = true;
-      cairo_t *cr = context.GetObject();
-
-      for (size_t i = 0; i < renderers_.size(); i++)
-      {
-        if (renderers_[i] != NULL)
-        {
-          const OrthancStone::CoordinateSystem3D& framePlane = renderers_[i]->GetLayerPlane();
-          
-          double x0, y0, x1, y1, x2, y2;
-          viewportPlane.ProjectPoint(x0, y0, framePlane.GetOrigin());
-          viewportPlane.ProjectPoint(x1, y1, framePlane.GetOrigin() + framePlane.GetAxisX());
-          viewportPlane.ProjectPoint(x2, y2, framePlane.GetOrigin() + framePlane.GetAxisY());
-
-          /**
-           * Now we solve the system of linear equations Ax + b = x', given:
-           *   A [0 ; 0] + b = [x0 ; y0]
-           *   A [1 ; 0] + b = [x1 ; y1]
-           *   A [0 ; 1] + b = [x2 ; y2]
-           * <=>
-           *   b = [x0 ; y0]
-           *   A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0]
-           *   A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0]
-           * <=>
-           *   b = [x0 ; y0]
-           *   [a11 ; a21] = [x1 - x0 ; y1 - y0]
-           *   [a12 ; a22] = [x2 - x0 ; y2 - y0]
-           **/
-
-          cairo_matrix_t transform;
-          cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0);
-
-          cairo_save(cr);
-          cairo_transform(cr, &transform);
-          
-          if (!renderers_[i]->RenderLayer(context, view))
-          {
-            cairo_restore(cr);
-            return false;
-          }
-
-          cairo_restore(cr);
-        }
-
-        if (renderers_[i] != NULL &&
-            !renderers_[i]->IsFullQuality())
-        {
-          fullQuality = false;
-        }
-      }
-
-      if (!fullQuality)
-      {
-        double x, y;
-        view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10);
-
-        cairo_translate(cr, x, y);
-
-#if 1
-        double s = 5.0 / view.GetZoom();
-        cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s);
-#else
-        // TODO Drawing filled circles makes WebAssembly crash!
-        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>());
-#endif
-        
-        cairo_set_line_width(cr, 2.0 / view.GetZoom());
-        cairo_set_source_rgb(cr, 1, 1, 1);
-        cairo_stroke_preserve(cr);
-        cairo_set_source_rgb(cr, 1, 0, 0);
-        cairo_fill(cr);
-      }
-
-      return true;
-    }
-
-    void SetLayerStyle(size_t index,
-                       const RenderStyle& style)
-    {
-      if (renderers_[index] != NULL)
-      {
-        renderers_[index]->SetLayerStyle(style);
-      }
-    }
-
-    bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const
-    {
-      bool isOpposite;
-      if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
-                                                               plane.GetNormal(),
-                                                               plane_.GetNormal()))
-      {
-        return false;
-      }
-      else
-      {
-        double z = (plane_.ProjectAlongNormal(plane.GetOrigin()) -
-                    plane_.ProjectAlongNormal(plane_.GetOrigin()));
-
-        if (z < 0)
-        {
-          z = -z;
-        }
-
-        return z <= thickness_;
-      }
-    }
-
-    double GetThickness() const
-    {
-      return thickness_;
-    }
-  };
-
-  
-  bool SliceViewerWidget::LookupLayer(size_t& index /* out */,
-                                      const IVolumeSlicer& layer) const
-  {
-    LayersIndex::const_iterator found = layersIndex_.find(&layer);
-
-    if (found == layersIndex_.end())
-    {
-      return false;
-    }
-    else
-    {
-      index = found->second;
-      assert(index < layers_.size() &&
-             layers_[index] == &layer);
-      return true;
-    }
-  }
-
-
-  void SliceViewerWidget::GetLayerExtent(OrthancStone::Extent2D& extent,
-                                         IVolumeSlicer& source) const
-  {
-    extent.Reset();
-
-    std::vector<OrthancStone::Vector> points;
-    if (source.GetExtent(points, plane_))
-    {
-      for (size_t i = 0; i < points.size(); i++)
-      {
-        double x, y;
-        plane_.ProjectPoint(x, y, points[i]);
-        extent.AddPoint(x, y);
-      }
-    }
-  }
-
-
-  OrthancStone::Extent2D SliceViewerWidget::GetSceneExtent()
-  {
-    OrthancStone::Extent2D sceneExtent;
-
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      assert(layers_[i] != NULL);
-      OrthancStone::Extent2D layerExtent;
-      GetLayerExtent(layerExtent, *layers_[i]);
-
-      sceneExtent.Union(layerExtent);
-    }
-
-    return sceneExtent;
-  }
-
-  
-  bool SliceViewerWidget::RenderScene(OrthancStone::CairoContext& context,
-                                      const ViewportGeometry& view)
-  {
-    if (currentScene_.get() != NULL)
-    {
-      return currentScene_->RenderScene(context, view, plane_);
-    }
-    else
-    {
-      return true;
-    }
-  }
-
-  
-  void SliceViewerWidget::ResetPendingScene()
-  {
-    double thickness;
-    if (pendingScene_.get() == NULL)
-    {
-      thickness = 1.0;
-    }
-    else
-    {
-      thickness = pendingScene_->GetThickness();
-    }
-    
-    pendingScene_.reset(new Scene(plane_, thickness, layers_.size()));
-  }
-  
-
-  void SliceViewerWidget::UpdateLayer(size_t index,
-                                      ILayerRenderer* renderer,
-                                      const OrthancStone::CoordinateSystem3D& plane)
-  {
-    LOG(INFO) << "Updating layer " << index;
-    
-    std::auto_ptr<ILayerRenderer> tmp(renderer);
-
-    if (renderer == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    if (index >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(layers_.size() == styles_.size());
-    renderer->SetLayerStyle(styles_[index]);
-
-    if (currentScene_.get() != NULL &&
-        currentScene_->ContainsPlane(plane))
-    {
-      currentScene_->SetLayer(index, tmp.release());
-      NotifyContentChanged();
-    }
-    else if (pendingScene_.get() != NULL &&
-             pendingScene_->ContainsPlane(plane))
-    {
-      pendingScene_->SetLayer(index, tmp.release());
-
-      if (currentScene_.get() == NULL ||
-          !currentScene_->IsComplete() ||
-          pendingScene_->IsComplete())
-      {
-        currentScene_ = pendingScene_;
-        NotifyContentChanged();
-      }
-    }
-  }
-
-  
-  SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, 
-                                       const std::string& name) :
-    WorldSceneWidget(name),
-    IObserver(broker),
-    IObservable(broker),
-    started_(false)
-  {
-    SetBackgroundCleared(true);
-  }
-  
-  
-  SliceViewerWidget::~SliceViewerWidget()
-  {
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      delete layers_[i];
-    }
-  }
-  
-  void SliceViewerWidget::ObserveLayer(IVolumeSlicer& layer)
-  {
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage>
-                                   (*this, &SliceViewerWidget::OnGeometryReady));
-    // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage>
-                                   (*this, &SliceViewerWidget::OnSliceChanged));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage>
-                                   (*this, &SliceViewerWidget::OnContentChanged));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage>
-                                   (*this, &SliceViewerWidget::OnLayerReady));
-    layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage>
-                                   (*this, &SliceViewerWidget::OnLayerError));
-  }
-
-
-  size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer)  // Takes ownership
-  {
-    if (layer == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    size_t index = layers_.size();
-    layers_.push_back(layer);
-    styles_.push_back(RenderStyle());
-    layersIndex_[layer] = index;
-
-    ResetPendingScene();
-
-    ObserveLayer(*layer);
-
-    ResetChangedLayers();
-
-    return index;
-  }
-
-
-  void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer)  // Takes ownership
-  {
-    if (layer == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-
-    if (index >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    delete layers_[index];
-    layers_[index] = layer;
-    layersIndex_[layer] = index;
-
-    ResetPendingScene();
-
-    ObserveLayer(*layer);
-
-    InvalidateLayer(index);
-  }
-
-
-  void SliceViewerWidget::RemoveLayer(size_t index)
-  {
-    if (index >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    IVolumeSlicer* previousLayer = layers_[index];
-    layersIndex_.erase(layersIndex_.find(previousLayer));
-    layers_.erase(layers_.begin() + index);
-    changedLayers_.erase(changedLayers_.begin() + index);
-    styles_.erase(styles_.begin() + index);
-
-    delete layers_[index];
-
-    currentScene_->DeleteLayer(index);
-    ResetPendingScene();
-
-    NotifyContentChanged();
-  }
-
-
-  const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(layers_.size() == styles_.size());
-    return styles_[layer];
-  }
-  
-
-  void SliceViewerWidget::SetLayerStyle(size_t layer,
-                                        const RenderStyle& style)
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(layers_.size() == styles_.size());
-    styles_[layer] = style;
-
-    if (currentScene_.get() != NULL)
-    {
-      currentScene_->SetLayerStyle(layer, style);
-    }
-
-    if (pendingScene_.get() != NULL)
-    {
-      pendingScene_->SetLayerStyle(layer, style);
-    }
-
-    NotifyContentChanged();
-  }
-  
-
-  void SliceViewerWidget::SetSlice(const OrthancStone::CoordinateSystem3D& plane)
-  {
-    LOG(INFO) << "Setting slice origin: (" << plane.GetOrigin()[0]
-              << "," << plane.GetOrigin()[1]
-              << "," << plane.GetOrigin()[2] << ")";
-    
-    Deprecated::Slice displayedSlice(plane_, THIN_SLICE_THICKNESS);
-
-    //if (!displayedSlice.ContainsPlane(slice))
-    {
-      if (currentScene_.get() == NULL ||
-          (pendingScene_.get() != NULL &&
-           pendingScene_->IsComplete()))
-      {
-        currentScene_ = pendingScene_;
-      }
-
-      plane_ = plane;
-      ResetPendingScene();
-
-      InvalidateAllLayers();   // TODO Removing this line avoid loading twice the image in WASM
-    }
-
-    BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice));
-  }
-
-
-  void SliceViewerWidget::OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message)
-  {
-    size_t i;
-    if (LookupLayer(i, message.GetOrigin()))
-    {
-      LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
-
-      changedLayers_[i] = true;
-      //layers_[i]->ScheduleLayerCreation(plane_);
-    }
-    BroadcastMessage(GeometryChangedMessage(*this));
-  }
-  
-
-  void SliceViewerWidget::InvalidateAllLayers()
-  {
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      assert(layers_[i] != NULL);
-      changedLayers_[i] = true;
-      
-      //layers_[i]->ScheduleLayerCreation(plane_);
-    }
-  }
-
-
-  void SliceViewerWidget::InvalidateLayer(size_t layer)
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(layers_[layer] != NULL);
-    changedLayers_[layer] = true;
-
-    //layers_[layer]->ScheduleLayerCreation(plane_);
-  }
-
-
-  void SliceViewerWidget::OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message)
-  {
-    size_t index;
-    if (LookupLayer(index, message.GetOrigin()))
-    {
-      InvalidateLayer(index);
-    }
-    
-    BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
-  }
-  
-
-  void SliceViewerWidget::OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message)
-  {
-    if (message.GetSlice().ContainsPlane(plane_))
-    {
-      size_t index;
-      if (LookupLayer(index, message.GetOrigin()))
-      {
-        InvalidateLayer(index);
-      }
-    }
-    
-    BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
-  }
-  
-  
-  void SliceViewerWidget::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message)
-  {
-    size_t index;
-    if (LookupLayer(index, message.GetOrigin()))
-    {
-      LOG(INFO) << "Renderer ready for layer " << index;
-      UpdateLayer(index, message.CreateRenderer(), message.GetSlice());
-    }
-    
-    BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
-  }
-
-
-  void SliceViewerWidget::OnLayerError(const IVolumeSlicer::LayerErrorMessage& message)
-  {
-    size_t index;
-    if (LookupLayer(index, message.GetOrigin()))
-    {
-      LOG(ERROR) << "Using error renderer on layer " << index;
-
-      // TODO
-      //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
-
-      BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this));
-    }
-  }
-
-
-  void SliceViewerWidget::ResetChangedLayers()
-  {
-    changedLayers_.resize(layers_.size());
-
-    for (size_t i = 0; i < changedLayers_.size(); i++)
-    {
-      changedLayers_[i] = false;
-    }
-  }
-
-
-  void SliceViewerWidget::DoAnimation()
-  {
-    assert(changedLayers_.size() <= layers_.size());
-    
-    for (size_t i = 0; i < changedLayers_.size(); i++)
-    {
-      if (changedLayers_[i])
-      {
-        layers_[i]->ScheduleLayerCreation(plane_);
-      }
-    }
-    
-    ResetChangedLayers();
-  }
-}
--- a/Framework/Widgets/SliceViewerWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WorldSceneWidget.h"
-#include "../Layers/IVolumeSlicer.h"
-#include "../Toolbox/Extent2D.h"
-#include "../../Framework/Messages/IObserver.h"
-
-#include <map>
-
-namespace Deprecated
-{
-  class SliceViewerWidget :
-    public WorldSceneWidget,
-    public OrthancStone::IObserver,
-    public OrthancStone::IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryChangedMessage, SliceViewerWidget);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, SliceViewerWidget);
-
-
-    // TODO - Use this message in ReferenceLineSource
-    class DisplayedSliceMessage : public OrthancStone::OriginMessage<SliceViewerWidget>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      const Deprecated::Slice& slice_;
-
-    public:
-      DisplayedSliceMessage(SliceViewerWidget& origin,
-                            const Deprecated::Slice& slice) :
-        OriginMessage(origin),
-        slice_(slice)
-      {
-      }
-
-      const Deprecated::Slice& GetSlice() const
-      {
-        return slice_;
-      }
-    };
-
-  private:
-    SliceViewerWidget(const SliceViewerWidget&);
-    SliceViewerWidget& operator=(const SliceViewerWidget&);
-
-    class Scene;
-    
-    typedef std::map<const IVolumeSlicer*, size_t>  LayersIndex;
-
-    bool                         started_;
-    LayersIndex                  layersIndex_;
-    std::vector<IVolumeSlicer*>  layers_;
-    std::vector<RenderStyle>     styles_;
-    OrthancStone::CoordinateSystem3D           plane_;
-    std::auto_ptr<Scene>         currentScene_;
-    std::auto_ptr<Scene>         pendingScene_;
-    std::vector<bool>            changedLayers_;
-
-    bool LookupLayer(size_t& index /* out */,
-                     const IVolumeSlicer& layer) const;
-
-    void GetLayerExtent(OrthancStone::Extent2D& extent,
-                        IVolumeSlicer& source) const;
-
-    void OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message);
-
-    virtual void OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message);
-
-    virtual void OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message);
-
-    virtual void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message);
-
-    virtual void OnLayerError(const IVolumeSlicer::LayerErrorMessage& message);
-
-    void ObserveLayer(IVolumeSlicer& source);
-
-    void ResetChangedLayers();
-
-  public:
-    SliceViewerWidget(OrthancStone::MessageBroker& broker, 
-                      const std::string& name);
-
-    virtual OrthancStone::Extent2D GetSceneExtent();
-
-  protected:
-    virtual bool RenderScene(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view);
-
-    void ResetPendingScene();
-
-    void UpdateLayer(size_t index,
-                     ILayerRenderer* renderer,
-                     const OrthancStone::CoordinateSystem3D& plane);
-
-    void InvalidateAllLayers();
-
-    void InvalidateLayer(size_t layer);
-    
-  public:
-    virtual ~SliceViewerWidget();
-
-    size_t AddLayer(IVolumeSlicer* layer);  // Takes ownership
-
-    void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership
-
-    void RemoveLayer(size_t layerIndex);
-
-    size_t GetLayerCount() const
-    {
-      return layers_.size();
-    }
-
-    const RenderStyle& GetLayerStyle(size_t layer) const;
-
-    void SetLayerStyle(size_t layer,
-                       const RenderStyle& style);
-
-    void SetSlice(const OrthancStone::CoordinateSystem3D& plane);
-
-    const OrthancStone::CoordinateSystem3D& GetSlice() const
-    {
-      return plane_;
-    }
-
-    virtual bool HasAnimation() const
-    {
-      return true;
-    }
-
-    virtual void DoAnimation();
-  };
-}
--- a/Framework/Widgets/TestCairoWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "TestCairoWidget.h"
-
-#include <stdio.h>
-
-
-namespace Deprecated
-{
-  namespace Samples
-  {
-    void TestCairoWidget::DoAnimation() 
-    {
-      value_ -= 0.01f;
-      if (value_ < 0)
-      {
-        value_ = 1;
-      }
-
-      NotifyContentChanged();
-    }
-
-
-    bool TestCairoWidget::RenderCairo(OrthancStone::CairoContext& context)
-    {
-      cairo_t* cr = context.GetObject();
-
-      cairo_set_source_rgb (cr, .3, 0, 0);
-      cairo_paint(cr);
-
-      cairo_set_source_rgb(cr, 0, 1, 0);
-      cairo_rectangle(cr, width_ / 4, height_ / 4, width_ / 2, height_ / 2);
-      cairo_set_line_width(cr, 1.0);
-      cairo_fill(cr);
-
-      cairo_set_source_rgb(cr, 0, 1, value_);
-      cairo_rectangle(cr, width_ / 2 - 50, height_ / 2 - 50, 100, 100);
-      cairo_fill(cr);
-
-      return true;
-    }
-
-
-    void TestCairoWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context,
-                                               int x,
-                                               int y)
-    {
-      cairo_t* cr = context.GetObject();
-
-      cairo_set_source_rgb (cr, 1, 0, 0);
-      cairo_rectangle(cr, x - 5, y - 5, 10, 10);
-      cairo_set_line_width(cr, 1.0);
-      cairo_stroke(cr);
-
-      char buf[64];
-      sprintf(buf, "(%d,%d)", x, y);
-      UpdateStatusBar(buf);
-    }
-
-
-    TestCairoWidget::TestCairoWidget(const std::string& name, bool animate) :
-      CairoWidget(name),
-      width_(0),
-      height_(0),
-      value_(1),
-      animate_(animate)
-    {
-    }
-
-
-    void TestCairoWidget::SetSize(unsigned int width, 
-                                  unsigned int height)
-    {
-      CairoWidget::SetSize(width, height);
-      width_ = width;
-      height_ = height;
-    }
- 
-
-    IMouseTracker* TestCairoWidget::CreateMouseTracker(OrthancStone::MouseButton button,
-                                                       int x,
-                                                       int y,
-                                                       OrthancStone::KeyboardModifiers modifiers,
-                                                       const std::vector<Touch>& touches)
-    {
-      UpdateStatusBar("Click");
-      return NULL;
-    }
-
-
-    void TestCairoWidget::MouseWheel(OrthancStone::MouseWheelDirection direction,
-                                     int x,
-                                     int y,
-                                     OrthancStone::KeyboardModifiers modifiers) 
-    {
-      UpdateStatusBar(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up");
-    }
-
-    
-    void TestCairoWidget::KeyPressed(OrthancStone::KeyboardKeys key,
-                                     char keyChar,
-                                     OrthancStone::KeyboardModifiers modifiers)
-    {
-      UpdateStatusBar("Key pressed: \"" + std::string(1, keyChar) + "\"");
-    }
-  }
-}
--- a/Framework/Widgets/TestCairoWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CairoWidget.h"
-
-namespace Deprecated
-{
-  namespace Samples
-  {
-    class TestCairoWidget : public CairoWidget
-    {
-    private:
-      unsigned int  width_;
-      unsigned int  height_;
-      float         value_;
-      bool          animate_;
-
-    protected:
-      virtual bool RenderCairo(OrthancStone::CairoContext& context);
-
-      virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context,
-                                        int x,
-                                        int y);
-
-    public:
-      TestCairoWidget(const std::string& name, bool animate);
-
-      virtual void SetSize(unsigned int width, 
-                           unsigned int height);
- 
-      virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
-                                                int x,
-                                                int y,
-                                                OrthancStone::KeyboardModifiers modifiers,
-                                                const std::vector<Touch>& touches);
-
-      virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                              int x,
-                              int y,
-                              OrthancStone::KeyboardModifiers modifiers);
-    
-      virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                              char keyChar,
-                              OrthancStone::KeyboardModifiers modifiers);
-
-      virtual bool HasAnimation() const
-      {
-        return animate_;
-      }
-      
-      virtual void DoAnimation();
-
-      virtual bool HasRenderMouseOver()
-      {
-        return true;
-      }
-    };
-  }
-}
--- a/Framework/Widgets/TestWorldSceneWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "TestWorldSceneWidget.h"
-
-#include <Core/OrthancException.h>
-
-#include <math.h>
-#include <stdio.h>
-
-namespace Deprecated
-{
-  namespace Samples
-  {
-    class TestWorldSceneWidget::Interactor : public IWorldSceneInteractor
-    {
-    public:
-      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                          const ViewportGeometry& view,
-                                                          OrthancStone::MouseButton button,
-                                                          OrthancStone::KeyboardModifiers modifiers,
-                                                          int viewportX,
-                                                          int viewportY,
-                                                          double x,
-                                                          double y,
-                                                          IStatusBar* statusBar,
-                                                          const std::vector<Touch>& touches)
-      {
-        if (statusBar)
-        {
-          char buf[64];
-          sprintf(buf, "X = %0.2f, Y = %0.2f", x, y);
-          statusBar->SetMessage(buf);
-        }
-
-        return NULL;
-      }
-
-      virtual void MouseOver(OrthancStone::CairoContext& context,
-                             WorldSceneWidget& widget,
-                             const ViewportGeometry& view,
-                             double x,
-                             double y,
-                             IStatusBar* statusBar)
-      {
-        double S = 0.5;
-
-        if (fabs(x) <= S &&
-            fabs(y) <= S)
-        {
-          cairo_t* cr = context.GetObject();
-          cairo_set_source_rgb(cr, 1, 0, 0);
-          cairo_rectangle(cr, -S, -S , 2.0 * S, 2.0 * S);
-          cairo_set_line_width(cr, 1.0 / view.GetZoom());
-          cairo_stroke(cr);
-        }
-      }
-
-      virtual void MouseWheel(WorldSceneWidget& widget,
-                              OrthancStone::MouseWheelDirection direction,
-                              OrthancStone::KeyboardModifiers modifiers,
-                              IStatusBar* statusBar)
-      {
-        if (statusBar)
-        {
-          statusBar->SetMessage(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up");
-        }
-      }
-
-      virtual void KeyPressed(WorldSceneWidget& widget,
-                              OrthancStone::KeyboardKeys key,
-                              char keyChar,
-                              OrthancStone::KeyboardModifiers modifiers,
-                              IStatusBar* statusBar)
-      {
-        if (statusBar)
-        {
-          statusBar->SetMessage("Key pressed: \"" + std::string(1, keyChar) + "\"");
-        }
-      }
-    };
-
-
-    bool TestWorldSceneWidget::RenderScene(OrthancStone::CairoContext& context,
-                                           const ViewportGeometry& view)
-    {
-      cairo_t* cr = context.GetObject();
-
-      // Clear background
-      cairo_set_source_rgb(cr, 0, 0, 0);
-      cairo_paint(cr);
-
-      float color = static_cast<float>(count_ % 16) / 15.0f;
-      cairo_set_source_rgb(cr, 0, 1.0f - color, color);
-      cairo_rectangle(cr, -10, -.5, 20, 1);
-      cairo_fill(cr);
-
-      return true;
-    }
-
-
-    TestWorldSceneWidget::TestWorldSceneWidget(const std::string& name, bool animate) :
-      WorldSceneWidget(name),
-      interactor_(new Interactor),
-      animate_(animate),
-      count_(0)
-    {
-      SetInteractor(*interactor_);
-    }
-
-
-    OrthancStone::Extent2D TestWorldSceneWidget::GetSceneExtent()
-    {
-      return OrthancStone::Extent2D(-10, -.5, 10, .5);
-    }
-
-
-    void TestWorldSceneWidget::DoAnimation()
-    {
-      if (animate_)
-      {
-        count_++;
-        NotifyContentChanged();
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-  }
-}
--- a/Framework/Widgets/TestWorldSceneWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WorldSceneWidget.h"
-
-#include <memory>
-
-namespace Deprecated
-{
-  namespace Samples
-  {
-    class TestWorldSceneWidget : public WorldSceneWidget
-    {
-    private:
-      class Interactor;
-
-      std::auto_ptr<Interactor>   interactor_;
-      bool                        animate_;
-      unsigned int                count_;
-
-    protected:
-      virtual bool RenderScene(OrthancStone::CairoContext& context,
-                               const ViewportGeometry& view);
-
-    public:
-      TestWorldSceneWidget(const std::string& name, bool animate);
-
-      virtual OrthancStone::Extent2D GetSceneExtent();
-
-      virtual bool HasAnimation() const
-      {
-        return animate_;
-      }
-
-      virtual void DoAnimation();
-
-      virtual bool HasRenderMouseOver()
-      {
-        return true;
-      }
-    };
-  }
-}
--- a/Framework/Widgets/WidgetBase.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WidgetBase.h"
-
-#include <Core/OrthancException.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Logging.h>
-
-namespace Deprecated
-{
-  void WidgetBase::NotifyContentChanged()
-  {
-    if (parent_ != NULL)
-    {
-      parent_->NotifyContentChanged();
-    }
-
-    if (viewport_ != NULL)
-    {
-      viewport_->NotifyBackgroundChanged();
-    }
-  }
-
-
-  void WidgetBase::SetParent(IWidget& parent)
-  {
-    if (parent_ != NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      parent_ = &parent;
-    }
-  }    
-
-  
-  void WidgetBase::ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const 
-  {
-    // Clear the background using Orthanc
-
-    if (backgroundCleared_)
-    {
-      Orthanc::ImageProcessing::Set(target, 
-                                    backgroundColor_[0],
-                                    backgroundColor_[1],
-                                    backgroundColor_[2],
-                                    255 /* alpha */);
-    }
-  }
-
-
-  void WidgetBase::ClearBackgroundCairo(OrthancStone::CairoContext& context) const
-  {
-    // Clear the background using Cairo
-
-    if (IsBackgroundCleared())
-    {
-      uint8_t red, green, blue;
-      GetBackgroundColor(red, green, blue);
-
-      context.SetSourceColor(red, green, blue);
-      cairo_paint(context.GetObject());
-    }
-  }
-
-
-  void WidgetBase::ClearBackgroundCairo(Orthanc::ImageAccessor& target) const
-  {
-    OrthancStone::CairoSurface surface(target, false /* no alpha */);
-    OrthancStone::CairoContext context(surface);
-    ClearBackgroundCairo(context);
-  }
-
-
-  void WidgetBase::UpdateStatusBar(const std::string& message)
-  {
-    if (statusBar_ != NULL)
-    {
-      statusBar_->SetMessage(message);
-    }
-  }
-
-
-  WidgetBase::WidgetBase(const std::string& name) :
-    parent_(NULL),
-    viewport_(NULL),
-    statusBar_(NULL),
-    backgroundCleared_(false),
-    transmitMouseOver_(false),
-    name_(name)
-  {
-    backgroundColor_[0] = 0;
-    backgroundColor_[1] = 0;
-    backgroundColor_[2] = 0;
-  }
-
-
-  void WidgetBase::SetViewport(WidgetViewport& viewport)
-  {
-    if (viewport_ != NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      viewport_ = &viewport;
-    }
-  }
-
-  
-  void WidgetBase::SetBackgroundColor(uint8_t red,
-                                      uint8_t green,
-                                      uint8_t blue)
-  {
-    backgroundColor_[0] = red;
-    backgroundColor_[1] = green;
-    backgroundColor_[2] = blue;
-  }
-
-  void WidgetBase::GetBackgroundColor(uint8_t& red,
-                                      uint8_t& green,
-                                      uint8_t& blue) const
-  {
-    red = backgroundColor_[0];
-    green = backgroundColor_[1];
-    blue = backgroundColor_[2];
-  }
-
-
-  bool WidgetBase::Render(Orthanc::ImageAccessor& surface)
-  {
-#if 0
-    ClearBackgroundOrthanc(surface);
-#else
-    ClearBackgroundCairo(surface);  // Faster than Orthanc
-#endif
-
-    return true;
-  }
-
-  
-  void WidgetBase::DoAnimation()
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-}
--- a/Framework/Widgets/WidgetBase.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IWidget.h"
-
-#include "../Viewport/CairoContext.h"
-#include "../Viewport/WidgetViewport.h"
-
-namespace Deprecated
-{
-  class WidgetBase : public IWidget
-  {
-  private:
-    IWidget*         parent_;
-    WidgetViewport*  viewport_;
-    IStatusBar*      statusBar_;
-    bool             backgroundCleared_;
-    uint8_t          backgroundColor_[3];
-    bool             transmitMouseOver_;
-    std::string      name_;
-
-  protected:
-    void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const;
-
-    void ClearBackgroundCairo(OrthancStone::CairoContext& context) const;
-
-    void ClearBackgroundCairo(Orthanc::ImageAccessor& target) const;
-
-    void UpdateStatusBar(const std::string& message);
-
-    IStatusBar* GetStatusBar() const
-    {
-      return statusBar_;
-    }
-
-  public:
-    WidgetBase(const std::string& name);
-
-    virtual void FitContent()
-    {
-    }
-  
-    virtual void SetParent(IWidget& parent);
-    
-    virtual void SetViewport(WidgetViewport& viewport);
-
-    void SetBackgroundCleared(bool clear)
-    {
-      backgroundCleared_ = clear;
-    }
-
-    bool IsBackgroundCleared() const
-    {
-      return backgroundCleared_;
-    }
-
-    void SetTransmitMouseOver(bool transmit)
-    {
-      transmitMouseOver_ = transmit;
-    }
-
-    void SetBackgroundColor(uint8_t red,
-                            uint8_t green,
-                            uint8_t blue);
-
-    void GetBackgroundColor(uint8_t& red,
-                            uint8_t& green,
-                            uint8_t& blue) const;
-
-    virtual void SetStatusBar(IStatusBar& statusBar)
-    {
-      statusBar_ = &statusBar;
-    }
-
-    virtual bool Render(Orthanc::ImageAccessor& surface);
-
-    virtual bool HasAnimation() const
-    {
-      return false;
-    }
-
-    virtual void DoAnimation();
-
-    virtual bool HasRenderMouseOver()
-    {
-      return transmitMouseOver_;
-    }
-
-    virtual void NotifyContentChanged();
-
-    const std::string& GetName() const
-    {
-      return name_;
-    }
-
-  };
-}
--- a/Framework/Widgets/WorldSceneWidget.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "WorldSceneWidget.h"
-
-#include "PanMouseTracker.h"
-#include "ZoomMouseTracker.h"
-#include "PanZoomMouseTracker.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-#include <math.h>
-#include <memory>
-#include <cassert>
-
-namespace Deprecated
-{
-  // this is an adapter between a IWorldSceneMouseTracker
-  // that is tracking a mouse in scene coordinates/mm and
-  // an IMouseTracker that is tracking a mouse
-  // in screen coordinates/pixels.
-  class WorldSceneWidget::SceneMouseTracker : public IMouseTracker
-  {
-  private:
-    ViewportGeometry                        view_;
-    std::auto_ptr<IWorldSceneMouseTracker>  tracker_;
-
-  public:
-    SceneMouseTracker(const ViewportGeometry& view,
-                      IWorldSceneMouseTracker* tracker) :
-      view_(view),
-      tracker_(tracker)
-    {
-      if (tracker == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    virtual void Render(Orthanc::ImageAccessor& target)
-    {
-      if (tracker_->HasRender())
-      {
-        OrthancStone::CairoSurface surface(target, false /* no alpha */);
-        OrthancStone::CairoContext context(surface);
-        view_.ApplyTransform(context);
-        tracker_->Render(context, view_.GetZoom());
-      }
-    }
-
-    virtual void MouseUp()
-    {
-      tracker_->MouseUp();
-    }
-
-    virtual void MouseMove(int x,
-                           int y,
-                           const std::vector<Touch>& displayTouches)
-    {
-      double sceneX, sceneY;
-      view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
-
-      std::vector<Touch> sceneTouches;
-      for (size_t t = 0; t < displayTouches.size(); t++)
-      {
-        double sx, sy;
-        
-        view_.MapPixelCenterToScene(
-          sx, sy, (int)displayTouches[t].x, (int)displayTouches[t].y);
-        
-        sceneTouches.push_back(
-          Touch(static_cast<float>(sx), static_cast<float>(sy)));
-      }
-      tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches);
-    }
-  };
-
-
-  bool WorldSceneWidget::RenderCairo(OrthancStone::CairoContext& context)
-  {
-    view_.ApplyTransform(context);
-    return RenderScene(context, view_);
-  }
-
-
-  void WorldSceneWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context,
-                                              int x,
-                                              int y)
-  {
-    ViewportGeometry view = GetView();
-    view.ApplyTransform(context);
-
-    double sceneX, sceneY;
-    view.MapPixelCenterToScene(sceneX, sceneY, x, y);
-
-    if (interactor_)
-    {
-      interactor_->MouseOver(context, *this, view, sceneX, sceneY, GetStatusBar());
-    }
-  }
-
-
-  void WorldSceneWidget::SetSceneExtent(ViewportGeometry& view)
-  {
-    view.SetSceneExtent(GetSceneExtent());
-  }
-
-
-  void WorldSceneWidget::SetSize(unsigned int width,
-                                 unsigned int height)
-  {
-    CairoWidget::SetSize(width, height);
-    view_.SetDisplaySize(width, height);
-  }
-
-
-  void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor)
-  {
-    interactor_ = &interactor;
-  }
-
-
-  void WorldSceneWidget::FitContent()
-  {
-    SetSceneExtent(view_);
-    view_.FitContent();
-
-    NotifyContentChanged();
-  }
-
-
-  void WorldSceneWidget::SetView(const ViewportGeometry& view)
-  {
-    view_ = view;
-
-    NotifyContentChanged();
-  }
-
-
-  IMouseTracker* WorldSceneWidget::CreateMouseTracker(OrthancStone::MouseButton button,
-                                                      int x,
-                                                      int y,
-                                                      OrthancStone::KeyboardModifiers modifiers,
-                                                      const std::vector<Touch>& touches)
-  {
-    double sceneX, sceneY;
-    view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
-
-    // asks the Widget Interactor to provide a mouse tracker
-    std::auto_ptr<IWorldSceneMouseTracker> tracker;
-
-    if (interactor_)
-    {
-      tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches));
-    }
-    
-    if (tracker.get() != NULL)
-    {
-      return new SceneMouseTracker(view_, tracker.release());
-    }
-    else if (hasDefaultMouseEvents_)
-    {
-      printf("has default mouse events\n");
-      if (touches.size() == 2)
-      {
-        printf("2 touches !\n");
-        return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches));
-      }
-      else
-      {
-        switch (button)
-        {
-          case OrthancStone::MouseButton_Middle:
-            return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y));
-
-          case OrthancStone::MouseButton_Right:
-            return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y));
-
-          default:
-            return NULL;
-        }
-      }
-    }
-    else
-    {
-      return NULL;
-    }
-  }
-
-
-  void WorldSceneWidget::MouseWheel(OrthancStone::MouseWheelDirection direction,
-                                    int x,
-                                    int y,
-                                    OrthancStone::KeyboardModifiers modifiers)
-  {
-    if (interactor_)
-    {
-      interactor_->MouseWheel(*this, direction, modifiers, GetStatusBar());
-    }
-  }
-
-
-  void WorldSceneWidget::KeyPressed(OrthancStone::KeyboardKeys key,
-                                    char keyChar,
-                                    OrthancStone::KeyboardModifiers modifiers)
-  {
-    if (interactor_)
-    {
-      interactor_->KeyPressed(*this, key, keyChar, modifiers, GetStatusBar());
-    }
-  }
-}
--- a/Framework/Widgets/WorldSceneWidget.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "CairoWidget.h"
-#include "IWorldSceneInteractor.h"
-
-#include "../Toolbox/ViewportGeometry.h"
-
-namespace Deprecated
-{
-  class WorldSceneWidget : public CairoWidget
-  {
-  private:
-    class SceneMouseTracker;
-
-    ViewportGeometry       view_;
-    IWorldSceneInteractor* interactor_;
-    bool                   hasDefaultMouseEvents_;
-
-  protected:
-    virtual OrthancStone::Extent2D GetSceneExtent() = 0;
-
-    virtual bool RenderScene(OrthancStone::CairoContext& context,
-                             const ViewportGeometry& view) = 0;
-
-    // From CairoWidget
-    virtual bool RenderCairo(OrthancStone::CairoContext& context);
-
-    // From CairoWidget
-    virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context,
-                                      int x,
-                                      int y);
-
-    void SetSceneExtent(ViewportGeometry& geometry);
-
-  public:
-    WorldSceneWidget(const std::string& name) :
-      CairoWidget(name),
-      interactor_(NULL),
-      hasDefaultMouseEvents_(true)
-    {
-    }
-
-    void SetDefaultMouseEvents(bool value)
-    {
-      hasDefaultMouseEvents_ = value;
-    }
-
-    bool HasDefaultMouseEvents() const
-    {
-      return hasDefaultMouseEvents_;
-    }
-
-    void SetInteractor(IWorldSceneInteractor& interactor);
-
-    void SetView(const ViewportGeometry& view);
-
-    const ViewportGeometry& GetView() const
-    {
-      return view_;
-    }
-
-    virtual void SetSize(unsigned int width,
-                         unsigned int height);
-
-    virtual void FitContent();
-
-    virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button,
-                                              int x,
-                                              int y,
-                                              OrthancStone::KeyboardModifiers modifiers,
-                                              const std::vector<Touch>& touches);
-
-    virtual void MouseWheel(OrthancStone::MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            OrthancStone::KeyboardModifiers modifiers);
-
-    virtual void KeyPressed(OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers);
-  };
-}
--- a/Framework/Widgets/ZoomMouseTracker.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ZoomMouseTracker.h"
-
-#include <Core/Logging.h>
-#include <Core/OrthancException.h>
-
-namespace Deprecated
-{
-  ZoomMouseTracker::ZoomMouseTracker(WorldSceneWidget& that,
-                                     int x,
-                                     int y) :
-    that_(that),
-    originalZoom_(that.GetView().GetZoom()),
-    downX_(x),
-    downY_(y)
-  {
-    that.GetView().MapPixelCenterToScene(centerX_, centerY_, x, y);
-
-    unsigned int height = that.GetView().GetDisplayHeight();
-      
-    if (height <= 3)
-    {
-      idle_ = true;
-      LOG(WARNING) << "image is too small to zoom (current height = " << height << ")";
-    }
-    else
-    {
-      idle_ = false;
-      normalization_ = 1.0 / static_cast<double>(height - 1);
-    }
-  }
-    
-
-  void ZoomMouseTracker::Render(OrthancStone::CairoContext& context,
-                                double zoom)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
-  void ZoomMouseTracker::MouseMove(int displayX,
-                                   int displayY,
-                                   double x,
-                                   double y,
-                                   const std::vector<Touch>& displayTouches,
-                                   const std::vector<Touch>& sceneTouches)
-  {
-    static const double MIN_ZOOM = -4;
-    static const double MAX_ZOOM = 4;
-
-      
-    if (!idle_)
-    {
-      double dy = static_cast<double>(displayY - downY_) * normalization_;  // In the range [-1,1]
-      double z;
-
-      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
-      if (dy < -1.0)
-      {
-        z = MIN_ZOOM;
-      }
-      else if (dy > 1.0)
-      {
-        z = MAX_ZOOM;
-      }
-      else
-      {
-        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
-      }
-
-      z = pow(2.0, z);
-
-      ViewportGeometry view = that_.GetView();
-        
-      view.SetZoom(z * originalZoom_);
-        
-      // Correct the pan so that the original click point is kept at
-      // the same location on the display
-      double panX, panY;
-      view.GetPan(panX, panY);
-
-      int tx, ty;
-      view.MapSceneToDisplay(tx, ty, centerX_, centerY_);
-      view.SetPan(panX + static_cast<double>(downX_ - tx),
-                  panY + static_cast<double>(downY_ - ty));
-        
-      that_.SetView(view);
-    }
-  }
-}
--- a/Framework/Widgets/ZoomMouseTracker.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "WorldSceneWidget.h"
-
-namespace Deprecated
-{
-  class ZoomMouseTracker : public IWorldSceneMouseTracker
-  {
-  private:
-    WorldSceneWidget&  that_;
-    double             originalZoom_;
-    int                downX_;
-    int                downY_;
-    double             centerX_;
-    double             centerY_;
-    bool               idle_;
-    double             normalization_;
-    
-  public:
-    ZoomMouseTracker(WorldSceneWidget& that,
-                     int x,
-                     int y);
-    
-    virtual bool HasRender() const
-    {
-      return false;
-    }
-
-    virtual void MouseUp()
-    {
-    }
-
-    virtual void Render(OrthancStone::CairoContext& context,
-                        double zoom);
-
-    virtual void MouseMove(int displayX,
-                           int displayY,
-                           double x,
-                           double y,
-                           const std::vector<Touch>& displayTouches,
-                           const std::vector<Touch>& sceneTouches);
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoContext.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,146 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CairoContext.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+
+namespace OrthancStone
+{
+  CairoContext::CairoContext(CairoSurface& surface) :
+    width_(surface.GetWidth()),
+    height_(surface.GetHeight())
+  {
+    context_ = cairo_create(surface.GetObject());
+    if (!context_)
+    {
+      LOG(ERROR) << "Cannot create Cairo drawing context";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  CairoContext::~CairoContext()
+  {
+    if (context_ != NULL)
+    {
+      cairo_destroy(context_);
+      context_ = NULL;
+    }
+  }
+
+
+  void CairoContext::SetSourceColor(uint8_t red,
+                                    uint8_t green,
+                                    uint8_t blue)
+  {
+    cairo_set_source_rgb(context_,
+                         static_cast<float>(red) / 255.0f,
+                         static_cast<float>(green) / 255.0f,
+                         static_cast<float>(blue) / 255.0f);
+  }
+
+
+  class CairoContext::AlphaSurface : public boost::noncopyable
+  {
+  private:
+    cairo_surface_t  *surface_;
+
+  public:
+    AlphaSurface(unsigned int width,
+                 unsigned int height)
+    {
+      surface_ = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height);
+      
+      if (!surface_)
+      {
+        // Should never occur
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+      {
+        LOG(ERROR) << "Cannot create a Cairo surface";
+        cairo_surface_destroy(surface_);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+    
+    ~AlphaSurface()
+    {
+      cairo_surface_destroy(surface_);
+    }
+
+    void GetAccessor(Orthanc::ImageAccessor& target)
+    {
+      target.AssignWritable(Orthanc::PixelFormat_Grayscale8,
+                            cairo_image_surface_get_width(surface_),
+                            cairo_image_surface_get_height(surface_),
+                            cairo_image_surface_get_stride(surface_),
+                            cairo_image_surface_get_data(surface_));
+    }
+
+    void Blit(cairo_t* cr,
+              double x,
+              double y)
+    {
+      cairo_surface_mark_dirty(surface_);
+      cairo_mask_surface(cr, surface_, x, y);
+      cairo_fill(cr);
+    }
+  };
+
+
+  void CairoContext::DrawText(const Orthanc::Font& font,
+                              const std::string& text,
+                              double x,
+                              double y,
+                              BitmapAnchor anchor)
+  {
+    // Render a bitmap containing the text
+    unsigned int width, height;
+    font.ComputeTextExtent(width, height, text);
+    
+    AlphaSurface surface(width, height);
+
+    Orthanc::ImageAccessor accessor;
+    surface.GetAccessor(accessor);
+    font.Draw(accessor, text, 0, 0, 255);
+
+    // Correct the text location given the anchor location
+    double deltaX, deltaY;
+    ComputeAnchorTranslation(deltaX, deltaY, anchor, width, height);
+
+    // Cancel zoom/rotation before blitting the text onto the surface
+    double pixelX = x;
+    double pixelY = y;
+    cairo_user_to_device(context_, &pixelX, &pixelY);
+
+    cairo_save(context_);
+    cairo_identity_matrix(context_);
+
+    // Blit the text bitmap
+    surface.Blit(context_, pixelX + deltaX, pixelY + deltaY);
+    cairo_restore(context_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoContext.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "CairoSurface.h"
+#include "../StoneEnumerations.h"
+
+#include <Core/Images/Font.h>
+
+namespace OrthancStone
+{
+  // This is a RAII wrapper around the Cairo drawing context
+  class CairoContext : public boost::noncopyable
+  {
+  private:
+    class AlphaSurface;
+    
+    cairo_t*      context_;
+    unsigned int  width_;
+    unsigned int  height_;
+
+  public:
+    CairoContext(CairoSurface& surface);
+
+    ~CairoContext();
+
+    cairo_t* GetObject()
+    {
+      return context_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    void SetSourceColor(uint8_t red,
+                        uint8_t green,
+                        uint8_t blue);
+
+    void SetSourceColor(const uint8_t color[3])
+    {
+      SetSourceColor(color[0], color[1], color[2]);
+    }
+
+    void DrawText(const Orthanc::Font& font,
+                  const std::string& text,
+                  double x,
+                  double y,
+                  BitmapAnchor anchor);      
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoSurface.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,155 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CairoSurface.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageProcessing.h>
+
+namespace OrthancStone
+{
+  void CairoSurface::Release()
+  {
+    if (surface_)
+    {
+      cairo_surface_destroy(surface_);
+      surface_ = NULL;
+    }
+  }
+
+
+  void CairoSurface::Allocate(unsigned int width,
+                              unsigned int height,
+                              bool hasAlpha)
+  {
+    Release();
+
+    hasAlpha_ = hasAlpha;
+
+    surface_ = cairo_image_surface_create
+      (hasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);
+    if (!surface_)
+    {
+      // Should never occur
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Cannot create a Cairo surface";
+      cairo_surface_destroy(surface_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    width_ = width;
+    height_ = height;
+    pitch_ = cairo_image_surface_get_stride(surface_);
+    buffer_ = cairo_image_surface_get_data(surface_);
+  }
+
+
+  CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor,
+                             bool hasAlpha) :
+    hasAlpha_(hasAlpha)
+  {
+    if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }      
+
+    width_ = accessor.GetWidth();
+    height_ = accessor.GetHeight();
+    pitch_ = accessor.GetPitch();
+    buffer_ = accessor.GetBuffer();
+
+    surface_ = cairo_image_surface_create_for_data
+      (reinterpret_cast<unsigned char*>(buffer_),
+       hasAlpha_ ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
+       width_, height_, pitch_);
+    if (!surface_)
+    {
+      // Should never occur
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS)
+    {
+      LOG(ERROR) << "Bad pitch for a Cairo surface";
+      cairo_surface_destroy(surface_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void CairoSurface::SetSize(unsigned int width,
+                             unsigned int height,
+                             bool hasAlpha)
+  {
+    if (hasAlpha_ != hasAlpha ||
+        width_ != width ||
+        height_ != height)
+    {
+      Allocate(width, height, hasAlpha);
+    }
+  }
+
+
+  void CairoSurface::Copy(const CairoSurface& other)
+  {
+    SetSize(other.GetWidth(), other.GetHeight(), other.HasAlpha());
+    
+    Orthanc::ImageAccessor source, target;
+
+    other.GetReadOnlyAccessor(source);
+    GetWriteableAccessor(target);
+
+    Orthanc::ImageProcessing::Copy(target, source);
+
+    cairo_surface_mark_dirty(surface_);
+  }
+
+
+  void CairoSurface::Copy(const Orthanc::ImageAccessor& source,
+                          bool hasAlpha)
+  {
+    SetSize(source.GetWidth(), source.GetHeight(), hasAlpha);
+
+    Orthanc::ImageAccessor target;
+    GetWriteableAccessor(target);
+
+    Orthanc::ImageProcessing::Convert(target, source);
+
+    cairo_surface_mark_dirty(surface_);
+  }
+
+
+  void CairoSurface::GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const
+  {
+    target.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
+  }
+  
+
+  void CairoSurface::GetWriteableAccessor(Orthanc::ImageAccessor& target)
+  {
+    target.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Wrappers/CairoSurface.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,118 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <Core/Images/ImageAccessor.h>
+
+#include <boost/noncopyable.hpp>
+#include <cairo.h>
+
+namespace OrthancStone
+{
+  class CairoSurface : public boost::noncopyable
+  {
+  private:
+    cairo_surface_t* surface_;
+    unsigned int     width_;
+    unsigned int     height_;
+    unsigned int     pitch_;
+    void*            buffer_;
+    bool             hasAlpha_;
+
+    void Release();
+
+    void Allocate(unsigned int width,
+                  unsigned int height,
+                  bool hasAlpha);
+
+  public:
+    CairoSurface() :
+      surface_(NULL)
+    {
+      Allocate(0, 0, false);
+    }
+
+    CairoSurface(unsigned int width,
+                 unsigned int height,
+                 bool hasAlpha) :
+      surface_(NULL)
+    {
+      Allocate(width, height, hasAlpha);
+    }
+
+    CairoSurface(Orthanc::ImageAccessor& accessor,
+                 bool hasAlpha);
+
+    ~CairoSurface()
+    {
+      Release();
+    }
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 bool hasAlpha);
+
+    void Copy(const CairoSurface& other);
+
+    void Copy(const Orthanc::ImageAccessor& source,
+              bool hasAlpha);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    const void* GetBuffer() const
+    {
+      return buffer_;
+    }
+
+    void* GetBuffer()
+    {
+      return buffer_;
+    }
+
+    cairo_surface_t* GetObject()
+    {
+      return surface_;
+    }
+
+    bool HasAlpha() const
+    {
+      return hasAlpha_;
+    }
+
+    void GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const;
+
+    void GetWriteableAccessor(Orthanc::ImageAccessor& target);
+  };
+}
--- a/Framework/dev.h	Wed Jun 19 17:36:33 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,958 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Layers/FrameRenderer.h"
-#include "Layers/LineLayerRenderer.h"
-#include "Layers/SliceOutlineRenderer.h"
-#include "Toolbox/DownloadStack.h"
-#include "Toolbox/GeometryToolbox.h"
-#include "Toolbox/OrthancSlicesLoader.h"
-#include "Volumes/ImageBuffer3D.h"
-#include "Volumes/ISlicedVolume.h"
-#include "Widgets/SliceViewerWidget.h"
-
-#include <Core/Logging.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/OrthancException.h>
-
-#include <boost/math/special_functions/round.hpp>
-
-
-namespace Deprecated
-{
-  // TODO: Handle errors while loading
-  class OrthancVolumeImage :
-    public ISlicedVolume,
-    public OrthancStone::IObserver
-  {
-  private:
-    OrthancSlicesLoader           loader_;
-    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;
-    std::auto_ptr<DownloadStack>  downloadStack_;
-    bool                          computeRange_;
-    size_t                        pendingSlices_;
-
-    void ScheduleSliceDownload()
-    {
-      assert(downloadStack_.get() != NULL);
-
-      unsigned int slice;
-      if (downloadStack_->Pop(slice))
-      {
-        loader_.ScheduleLoadSliceImage(slice, OrthancStone::SliceImageQuality_Jpeg90);
-      }
-    }
-
-
-    static bool IsCompatible(const Slice& a,
-                             const Slice& b)
-    {
-      if (!OrthancStone::GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(),
-                                                     b.GetGeometry().GetNormal()))
-      {
-        LOG(ERROR) << "A slice in the volume image is not parallel to the others.";
-        return false;
-      }
-
-      if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat())
-      {
-        LOG(ERROR) << "The pixel format changes across the slices of the volume image.";
-        return false;
-      }
-
-      if (a.GetWidth() != b.GetWidth() ||
-          a.GetHeight() != b.GetHeight())
-      {
-        LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image";
-        return false;
-      }
-
-      if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
-          !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY()))
-      {
-        LOG(ERROR) << "The pixel spacing of the slices change across the volume image";
-        return false;
-      }
-
-      return true;
-    }
-
-
-    static double GetDistance(const Slice& a,
-                              const Slice& b)
-    {
-      return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) -
-                  a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin()));
-    }
-
-
-    void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message)
-    {
-      assert(&message.GetOrigin() == &loader_);
-
-      if (loader_.GetSlicesCount() == 0)
-      {
-        LOG(ERROR) << "Empty volume image";
-        BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
-        return;
-      }
-
-      for (size_t i = 1; i < loader_.GetSlicesCount(); i++)
-      {
-        if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i)))
-        {
-          BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
-          return;
-        }
-      }
-
-      double spacingZ;
-
-      if (loader_.GetSlicesCount() > 1)
-      {
-        spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1));
-      }
-      else
-      {
-        // This is a volume with one single slice: Choose a dummy
-        // z-dimension for voxels
-        spacingZ = 1;
-      }
-
-      for (size_t i = 1; i < loader_.GetSlicesCount(); i++)
-      {
-        if (!OrthancStone::LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)),
-                                                 0.001 /* this is expressed in mm */))
-        {
-          LOG(ERROR) << "The distance between successive slices is not constant in a volume image";
-          BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
-          return;
-        }
-      }
-
-      unsigned int width = loader_.GetSlice(0).GetWidth();
-      unsigned int height = loader_.GetSlice(0).GetHeight();
-      Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat();
-      LOG(INFO) << "Creating a volume image of size " << width << "x" << height
-                << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format);
-
-      image_.reset(new OrthancStone::ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSlicesCount()), computeRange_));
-      image_->GetGeometry().SetAxialGeometry(loader_.GetSlice(0).GetGeometry());
-      image_->GetGeometry().SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(),
-                                               loader_.GetSlice(0).GetPixelSpacingY(), spacingZ);
-      image_->Clear();
-
-      downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSlicesCount())));
-      pendingSlices_ = loader_.GetSlicesCount();
-
-      for (unsigned int i = 0; i < 4; i++)  // Limit to 4 simultaneous downloads
-      {
-        ScheduleSliceDownload();
-      }
-
-      // TODO Check the DicomFrameConverter are constant
-
-      BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this));
-    }
-
-
-    void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message)
-    {
-      assert(&message.GetOrigin() == &loader_);
-
-      LOG(ERROR) << "Unable to download a volume image";
-      BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this));
-    }
-
-
-    void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message)
-    {
-      assert(&message.GetOrigin() == &loader_);
-
-      {
-        OrthancStone::ImageBuffer3D::SliceWriter writer(*image_, OrthancStone::VolumeProjection_Axial, message.GetSliceIndex());
-        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage());
-      }
-
-      BroadcastMessage(ISlicedVolume::SliceContentChangedMessage
-                       (*this, message.GetSliceIndex(), message.GetSlice()));
-
-      if (pendingSlices_ == 1)
-      {
-        BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this));
-        pendingSlices_ = 0;
-      }
-      else if (pendingSlices_ > 1)
-      {
-        pendingSlices_ -= 1;
-      }
-
-      ScheduleSliceDownload();
-    }
-
-
-    void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message)
-    {
-      assert(&message.GetOrigin() == &loader_);
-
-      LOG(ERROR) << "Cannot download slice " << message.GetSliceIndex() << " in a volume image";
-      ScheduleSliceDownload();
-    }
-
-
-  public:
-    OrthancVolumeImage(OrthancStone::MessageBroker& broker,
-                       OrthancApiClient& orthanc,
-                       bool computeRange) :
-      ISlicedVolume(broker),
-      IObserver(broker),
-      loader_(broker, orthanc),
-      computeRange_(computeRange),
-      pendingSlices_(0)
-    {
-      loader_.RegisterObserverCallback(
-        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryReadyMessage>
-        (*this, &OrthancVolumeImage::OnSliceGeometryReady));
-
-      loader_.RegisterObserverCallback(
-        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryErrorMessage>
-        (*this, &OrthancVolumeImage::OnSliceGeometryError));
-
-      loader_.RegisterObserverCallback(
-        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageReadyMessage>
-        (*this, &OrthancVolumeImage::OnSliceImageReady));
-
-      loader_.RegisterObserverCallback(
-        new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageErrorMessage>
-        (*this, &OrthancVolumeImage::OnSliceImageError));
-    }
-
-    void ScheduleLoadSeries(const std::string& seriesId)
-    {
-      loader_.ScheduleLoadSeries(seriesId);
-    }
-
-    void ScheduleLoadInstance(const std::string& instanceId)
-    {
-      loader_.ScheduleLoadInstance(instanceId);
-    }
-
-    void ScheduleLoadFrame(const std::string& instanceId,
-                           unsigned int frame)
-    {
-      loader_.ScheduleLoadFrame(instanceId, frame);
-    }
-
-    virtual size_t GetSlicesCount() const
-    {
-      return loader_.GetSlicesCount();
-    }
-
-    virtual const Slice& GetSlice(size_t index) const
-    {
-      return loader_.GetSlice(index);
-    }
-
-    OrthancStone::ImageBuffer3D& GetImage() const
-    {
-      if (image_.get() == NULL)
-      {
-        // The geometry is not ready yet
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        return *image_;
-      }
-    }
-
-    bool FitWindowingToRange(RenderStyle& style,
-                             const DicomFrameConverter& converter) const
-    {
-      if (image_.get() == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        return image_->FitWindowingToRange(style, converter);
-      }
-    }
-  };
-
-
-  class VolumeImageGeometry
-  {
-  private:
-    unsigned int         width_;
-    unsigned int         height_;
-    size_t               depth_;
-    double               pixelSpacingX_;
-    double               pixelSpacingY_;
-    double               sliceThickness_;
-    OrthancStone::CoordinateSystem3D   reference_;
-    DicomFrameConverter  converter_;
-
-    double ComputeAxialThickness(const OrthancVolumeImage& volume) const
-    {
-      double thickness;
-
-      size_t n = volume.GetSlicesCount();
-      if (n > 1)
-      {
-        const Slice& a = volume.GetSlice(0);
-        const Slice& b = volume.GetSlice(n - 1);
-        thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) -
-                      reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) /
-                     (static_cast<double>(n) - 1.0));
-      }
-      else
-      {
-        thickness = volume.GetSlice(0).GetThickness();
-      }
-
-      if (thickness <= 0)
-      {
-        // The slices should have been sorted with increasing Z
-        // (along the normal) by the OrthancSlicesLoader
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-      else
-      {
-        return thickness;
-      }
-    }
-
-    void SetupAxial(const OrthancVolumeImage& volume)
-    {
-      const Slice& axial = volume.GetSlice(0);
-
-      width_ = axial.GetWidth();
-      height_ = axial.GetHeight();
-      depth_ = volume.GetSlicesCount();
-
-      pixelSpacingX_ = axial.GetPixelSpacingX();
-      pixelSpacingY_ = axial.GetPixelSpacingY();
-      sliceThickness_ = ComputeAxialThickness(volume);
-
-      reference_ = axial.GetGeometry();
-    }
-
-    void SetupCoronal(const OrthancVolumeImage& volume)
-    {
-      const Slice& axial = volume.GetSlice(0);
-      double axialThickness = ComputeAxialThickness(volume);
-
-      width_ = axial.GetWidth();
-      height_ = static_cast<unsigned int>(volume.GetSlicesCount());
-      depth_ = axial.GetHeight();
-
-      pixelSpacingX_ = axial.GetPixelSpacingX();
-      pixelSpacingY_ = axialThickness;
-      sliceThickness_ = axial.GetPixelSpacingY();
-
-      OrthancStone::Vector origin = axial.GetGeometry().GetOrigin();
-      origin += (static_cast<double>(volume.GetSlicesCount() - 1) *
-                 axialThickness * axial.GetGeometry().GetNormal());
-
-      reference_ = OrthancStone::CoordinateSystem3D(origin,
-                                                    axial.GetGeometry().GetAxisX(),
-                                                    - axial.GetGeometry().GetNormal());
-    }
-
-    void SetupSagittal(const OrthancVolumeImage& volume)
-    {
-      const Slice& axial = volume.GetSlice(0);
-      double axialThickness = ComputeAxialThickness(volume);
-
-      width_ = axial.GetHeight();
-      height_ = static_cast<unsigned int>(volume.GetSlicesCount());
-      depth_ = axial.GetWidth();
-
-      pixelSpacingX_ = axial.GetPixelSpacingY();
-      pixelSpacingY_ = axialThickness;
-      sliceThickness_ = axial.GetPixelSpacingX();
-
-      OrthancStone::Vector origin = axial.GetGeometry().GetOrigin();
-      origin += (static_cast<double>(volume.GetSlicesCount() - 1) *
-                 axialThickness * axial.GetGeometry().GetNormal());
-
-      reference_ = OrthancStone::CoordinateSystem3D(origin,
-                                                    axial.GetGeometry().GetAxisY(),
-                                                    axial.GetGeometry().GetNormal());
-    }
-
-  public:
-    VolumeImageGeometry(const OrthancVolumeImage& volume,
-                        OrthancStone::VolumeProjection projection)
-    {
-      if (volume.GetSlicesCount() == 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      converter_ = volume.GetSlice(0).GetConverter();
-
-      switch (projection)
-      {
-        case OrthancStone::VolumeProjection_Axial:
-          SetupAxial(volume);
-          break;
-
-        case OrthancStone::VolumeProjection_Coronal:
-          SetupCoronal(volume);
-          break;
-
-        case OrthancStone::VolumeProjection_Sagittal:
-          SetupSagittal(volume);
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    size_t GetSlicesCount() const
-    {
-      return depth_;
-    }
-
-    const OrthancStone::Vector& GetNormal() const
-    {
-      return reference_.GetNormal();
-    }
-
-    bool LookupSlice(size_t& index,
-                     const OrthancStone::CoordinateSystem3D& slice) const
-    {
-      bool opposite;
-      if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite,
-                                                               reference_.GetNormal(),
-                                                               slice.GetNormal()))
-      {
-        return false;
-      }
-
-      double z = (reference_.ProjectAlongNormal(slice.GetOrigin()) -
-                  reference_.ProjectAlongNormal(reference_.GetOrigin())) / sliceThickness_;
-
-      int s = static_cast<int>(boost::math::iround(z));
-
-      if (s < 0 ||
-          s >= static_cast<int>(depth_))
-      {
-        return false;
-      }
-      else
-      {
-        index = static_cast<size_t>(s);
-        return true;
-      }
-    }
-
-    Slice* GetSlice(size_t slice) const
-    {
-      if (slice >= depth_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        OrthancStone::CoordinateSystem3D origin(reference_.GetOrigin() +
-                                                static_cast<double>(slice) * sliceThickness_ * reference_.GetNormal(),
-                                                reference_.GetAxisX(),
-                                                reference_.GetAxisY());
-
-        return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_,
-                         width_, height_, converter_);
-      }
-    }
-  };
-
-
-
-  class VolumeImageMPRSlicer :
-    public IVolumeSlicer,
-    public OrthancStone::IObserver
-  {
-  private:
-    class RendererFactory : public LayerReadyMessage::IRendererFactory
-    {
-    private:
-      const Orthanc::ImageAccessor&  frame_;
-      const Slice&                   slice_;
-      bool                           isFullQuality_;
-
-    public:
-      RendererFactory(const Orthanc::ImageAccessor& frame,
-                      const Slice& slice,
-                      bool isFullQuality) :
-        frame_(frame),
-        slice_(slice),
-        isFullQuality_(isFullQuality)
-      {
-      }
-
-      virtual ILayerRenderer* CreateRenderer() const
-      {
-        return FrameRenderer::CreateRenderer(frame_, slice_, isFullQuality_);
-      }
-    };
-
-
-    OrthancVolumeImage&                 volume_;
-    std::auto_ptr<VolumeImageGeometry>  axialGeometry_;
-    std::auto_ptr<VolumeImageGeometry>  coronalGeometry_;
-    std::auto_ptr<VolumeImageGeometry>  sagittalGeometry_;
-
-
-    bool IsGeometryReady() const
-    {
-      return axialGeometry_.get() != NULL;
-    }
-
-    void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message)
-    {
-      assert(&message.GetOrigin() == &volume_);
-
-      // These 3 values are only used to speed up the IVolumeSlicer
-      axialGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Axial));
-      coronalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Coronal));
-      sagittalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Sagittal));
-
-      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this));
-    }
-
-    void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message)
-    {
-      assert(&message.GetOrigin() == &volume_);
-
-      BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this));
-    }
-
-    void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message)
-    {
-      assert(&message.GetOrigin() == &volume_);
-
-      BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this));
-    }
-
-    void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message)
-    {
-      assert(&message.GetOrigin() == &volume_);
-
-      //IVolumeSlicer::OnSliceContentChange(slice);
-
-      // TODO Improve this?
-      BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this));
-    }
-
-    const VolumeImageGeometry& GetProjectionGeometry(OrthancStone::VolumeProjection projection)
-    {
-      if (!IsGeometryReady())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      switch (projection)
-      {
-        case OrthancStone::VolumeProjection_Axial:
-          return *axialGeometry_;
-
-        case OrthancStone::VolumeProjection_Sagittal:
-          return *sagittalGeometry_;
-
-        case OrthancStone::VolumeProjection_Coronal:
-          return *coronalGeometry_;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-
-    bool DetectProjection(OrthancStone::VolumeProjection& projection,
-                          const OrthancStone::CoordinateSystem3D& viewportSlice)
-    {
-      bool isOpposite;  // Ignored
-
-      if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
-                                                              viewportSlice.GetNormal(),
-                                                              axialGeometry_->GetNormal()))
-      {
-        projection = OrthancStone::VolumeProjection_Axial;
-        return true;
-      }
-      else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
-                                                                   viewportSlice.GetNormal(),
-                                                                   sagittalGeometry_->GetNormal()))
-      {
-        projection = OrthancStone::VolumeProjection_Sagittal;
-        return true;
-      }
-      else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite,
-                                                                   viewportSlice.GetNormal(),
-                                                                   coronalGeometry_->GetNormal()))
-      {
-        projection = OrthancStone::VolumeProjection_Coronal;
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-
-
-  public:
-    VolumeImageMPRSlicer(OrthancStone::MessageBroker& broker,
-                         OrthancVolumeImage&  volume) :
-      IVolumeSlicer(broker),
-      IObserver(broker),
-      volume_(volume)
-    {
-      volume_.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage>
-        (*this, &VolumeImageMPRSlicer::OnGeometryReady));
-
-      volume_.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryErrorMessage>
-        (*this, &VolumeImageMPRSlicer::OnGeometryError));
-
-      volume_.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::ContentChangedMessage>
-        (*this, &VolumeImageMPRSlicer::OnContentChanged));
-
-      volume_.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::SliceContentChangedMessage>
-        (*this, &VolumeImageMPRSlicer::OnSliceContentChanged));
-    }
-
-    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
-                           const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE
-    {
-      OrthancStone::VolumeProjection projection;
-
-      if (!IsGeometryReady() ||
-          !DetectProjection(projection, viewportSlice))
-      {
-        return false;
-      }
-      else
-      {
-        // As the slices of the volumic image are arranged in a box,
-        // we only consider one single reference slice (the one with index 0).
-        std::auto_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0));
-        slice->GetExtent(points);
-
-        return true;
-      }
-    }
-
-    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE
-    {
-      OrthancStone::VolumeProjection projection;
-
-      if (IsGeometryReady() &&
-          DetectProjection(projection, viewportSlice))
-      {
-        const VolumeImageGeometry& geometry = GetProjectionGeometry(projection);
-
-        size_t closest;
-
-        if (geometry.LookupSlice(closest, viewportSlice))
-        {
-          bool isFullQuality = true;  // TODO
-
-          std::auto_ptr<Orthanc::Image> frame;
-
-          {
-            OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, static_cast<unsigned int>(closest));
-
-            // TODO Transfer ownership if non-axial, to avoid memcpy
-            frame.reset(Orthanc::Image::Clone(reader.GetAccessor()));
-          }
-
-          std::auto_ptr<Slice> slice(geometry.GetSlice(closest));
-
-          RendererFactory factory(*frame, *slice, isFullQuality);
-
-          BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry()));
-          return;
-        }
-      }
-
-      // Error
-      OrthancStone::CoordinateSystem3D slice;
-      BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice));
-    }
-  };
-
-
-  class VolumeImageInteractor :
-    public IWorldSceneInteractor,
-    public OrthancStone::IObserver
-  {
-  private:
-    SliceViewerWidget&                  widget_;
-    OrthancStone::VolumeProjection      projection_;
-    std::auto_ptr<VolumeImageGeometry>  slices_;
-    size_t                              slice_;
-
-  protected:
-    void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message)
-    {
-      if (slices_.get() == NULL)
-      {
-        const OrthancVolumeImage& image =
-          dynamic_cast<const OrthancVolumeImage&>(message.GetOrigin());
-
-        slices_.reset(new VolumeImageGeometry(image, projection_));
-        SetSlice(slices_->GetSlicesCount() / 2);
-
-        widget_.FitContent();
-      }
-    }
-
-    virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                        const ViewportGeometry& view,
-                                                        OrthancStone::MouseButton button,
-                                                        OrthancStone::KeyboardModifiers modifiers,
-                                                        int viewportX,
-                                                        int viewportY,
-                                                        double x,
-                                                        double y,
-                                                        IStatusBar* statusBar,
-                                                        const std::vector<Touch>& touches) ORTHANC_OVERRIDE
-    {
-      return  NULL;
-    }
-
-    virtual void MouseOver(OrthancStone::CairoContext& context,
-                           WorldSceneWidget& widget,
-                           const ViewportGeometry& view,
-                           double x,
-                           double y,
-                           IStatusBar* statusBar) ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual void MouseWheel(WorldSceneWidget& widget,
-                            OrthancStone::MouseWheelDirection direction,
-                            OrthancStone::KeyboardModifiers modifiers,
-                            IStatusBar* statusBar) ORTHANC_OVERRIDE
-    {
-      int scale = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1);
-
-      switch (direction)
-      {
-        case OrthancStone::MouseWheelDirection_Up:
-          OffsetSlice(-scale);
-          break;
-
-        case OrthancStone::MouseWheelDirection_Down:
-          OffsetSlice(scale);
-          break;
-
-        default:
-          break;
-      }
-    }
-
-    virtual void KeyPressed(WorldSceneWidget& widget,
-                            OrthancStone::KeyboardKeys key,
-                            char keyChar,
-                            OrthancStone::KeyboardModifiers modifiers,
-                            IStatusBar* statusBar) ORTHANC_OVERRIDE
-    {
-      switch (keyChar)
-      {
-        case 's':
-          widget.FitContent();
-          break;
-
-        default:
-          break;
-      }
-    }
-
-  public:
-    VolumeImageInteractor(OrthancStone::MessageBroker& broker,
-                          OrthancVolumeImage& volume,
-                          SliceViewerWidget& widget,
-                          OrthancStone::VolumeProjection projection) :
-      IObserver(broker),
-      widget_(widget),
-      projection_(projection)
-    {
-      widget.SetInteractor(*this);
-
-      volume.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeImageInteractor, ISlicedVolume::GeometryReadyMessage>
-        (*this, &VolumeImageInteractor::OnGeometryReady));
-    }
-
-    bool IsGeometryReady() const
-    {
-      return slices_.get() != NULL;
-    }
-
-    size_t GetSlicesCount() const
-    {
-      if (slices_.get() == NULL)
-      {
-        return 0;
-      }
-      else
-      {
-        return slices_->GetSlicesCount();
-      }
-    }
-
-    void OffsetSlice(int offset)
-    {
-      if (slices_.get() != NULL)
-      {
-        int slice = static_cast<int>(slice_) + offset;
-
-        if (slice < 0)
-        {
-          slice = 0;
-        }
-
-        if (slice >= static_cast<int>(slices_->GetSlicesCount()))
-        {
-          slice = static_cast<unsigned int>(slices_->GetSlicesCount()) - 1;
-        }
-
-        if (slice != static_cast<int>(slice_))
-        {
-          SetSlice(slice);
-        }
-      }
-    }
-
-    void SetSlice(size_t slice)
-    {
-      if (slices_.get() != NULL)
-      {
-        slice_ = slice;
-
-        std::auto_ptr<Slice> tmp(slices_->GetSlice(slice_));
-        widget_.SetSlice(tmp->GetGeometry());
-      }
-    }
-  };
-
-
-
-  class ReferenceLineSource : public IVolumeSlicer
-  {
-  private:
-    class RendererFactory : public LayerReadyMessage::IRendererFactory
-    {
-    private:
-      double                     x1_;
-      double                     y1_;
-      double                     x2_;
-      double                     y2_;
-      const OrthancStone::CoordinateSystem3D&  slice_;
-
-    public:
-      RendererFactory(double x1,
-                      double y1,
-                      double x2,
-                      double y2,
-                      const OrthancStone::CoordinateSystem3D& slice) :
-        x1_(x1),
-        y1_(y1),
-        x2_(x2),
-        y2_(y2),
-        slice_(slice)
-      {
-      }
-
-      virtual ILayerRenderer* CreateRenderer() const
-      {
-        return new LineLayerRenderer(x1_, y1_, x2_, y2_, slice_);
-      }
-    };
-
-    SliceViewerWidget&  otherPlane_;
-
-  public:
-    ReferenceLineSource(OrthancStone::MessageBroker& broker,
-                        SliceViewerWidget&  otherPlane) :
-      IVolumeSlicer(broker),
-      otherPlane_(otherPlane)
-    {
-      BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this));
-    }
-
-    virtual bool GetExtent(std::vector<OrthancStone::Vector>& points,
-                           const OrthancStone::CoordinateSystem3D& viewportSlice)
-    {
-      return false;
-    }
-
-    virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice)
-    {
-      Slice reference(viewportSlice, 0.001);
-
-      OrthancStone::Vector p, d;
-
-      const OrthancStone::CoordinateSystem3D& slice = otherPlane_.GetSlice();
-
-      // Compute the line of intersection between the two slices
-      if (!OrthancStone::GeometryToolbox::IntersectTwoPlanes(p, d,
-                                                             slice.GetOrigin(), slice.GetNormal(),
-                                                             viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
-      {
-        // The two slice are parallel, don't try and display the intersection
-        BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry()));
-      }
-      else
-      {
-        double x1, y1, x2, y2;
-        viewportSlice.ProjectPoint(x1, y1, p);
-        viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
-
-        const OrthancStone::Extent2D extent = otherPlane_.GetSceneExtent();
-
-        if (OrthancStone::GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2,
-                                                               x1, y1, x2, y2,
-                                                               extent.GetX1(), extent.GetY1(),
-                                                               extent.GetX2(), extent.GetY2()))
-        {
-          RendererFactory factory(x1, y1, x2, y2, slice);
-          BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry()));
-        }
-        else
-        {
-          // Error: Parallel slices
-          BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry()));
-        }
-      }
-    }
-  };
-}
--- a/Platforms/Generic/DelayedCallCommand.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Generic/DelayedCallCommand.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,7 +23,7 @@
 
 #include "IOracleCommand.h"
 
-#include "../../Framework/Toolbox/IDelayedCallExecutor.h"
+#include "../../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h"
 #include "../../Framework/Messages/IObservable.h"
 #include "../../Framework/Messages/ICallable.h"
 #include "../../Applications/Generic/NativeStoneApplicationContext.h"
--- a/Platforms/Generic/OracleDelayedCallExecutor.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Generic/OracleDelayedCallExecutor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Framework/Toolbox/IDelayedCallExecutor.h"
+#include "../../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h"
 #include "Oracle.h"
 #include "../../Applications/Generic/NativeStoneApplicationContext.h"
 #include "DelayedCallCommand.h"
--- a/Platforms/Generic/OracleWebService.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Generic/OracleWebService.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -20,7 +20,7 @@
 
 
 #include "OracleWebService.h"
-#include "../../Framework/Toolbox/IWebService.h"
+#include "../../Framework/Deprecated/Toolbox/IWebService.h"
 
 namespace Deprecated
 {
--- a/Platforms/Generic/OracleWebService.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Generic/OracleWebService.h	Mon Jun 24 14:35:00 2019 +0200
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Framework/Toolbox/BaseWebService.h"
+#include "../../Framework/Deprecated/Toolbox/BaseWebService.h"
 #include "Oracle.h"
 #include "WebServiceGetCommand.h"
 #include "WebServicePostCommand.h"
--- a/Platforms/Generic/WebServiceCommandBase.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.h	Mon Jun 24 14:35:00 2019 +0200
@@ -23,7 +23,7 @@
 
 #include "IOracleCommand.h"
 
-#include "../../Framework/Toolbox/IWebService.h"
+#include "../../Framework/Deprecated/Toolbox/IWebService.h"
 #include "../../Framework/Messages/IObservable.h"
 #include "../../Framework/Messages/ICallable.h"
 #include "../../Applications/Generic/NativeStoneApplicationContext.h"
--- a/Platforms/Wasm/Defaults.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Wasm/Defaults.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -2,9 +2,8 @@
 
 #include "WasmWebService.h"
 #include "WasmDelayedCallExecutor.h"
-#include <Framework/dev.h>
-#include "Framework/Widgets/TestCairoWidget.h"
-#include <Framework/Viewport/WidgetViewport.h>
+#include "../../Framework/Deprecated/Widgets/TestCairoWidget.h"
+#include <Framework/Deprecated/Viewport/WidgetViewport.h>
 #include <Applications/Wasm/StartupParametersBuilder.h>
 #include <Platforms/Wasm/WasmPlatformApplicationAdapter.h>
 #include <Core/Logging.h>
--- a/Platforms/Wasm/Defaults.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Wasm/Defaults.h	Mon Jun 24 14:35:00 2019 +0200
@@ -2,9 +2,8 @@
 
 #include <emscripten/emscripten.h>
 
-#include <Framework/dev.h>
-#include <Framework/Viewport/WidgetViewport.h>
-#include <Framework/Widgets/LayoutWidget.h>
+#include "../../Framework/Deprecated/Viewport/WidgetViewport.h"
+#include "../../Framework/Deprecated/Widgets/LayoutWidget.h"
 #include <Applications/IStoneApplication.h>
 #include <Platforms/Wasm/WasmPlatformApplicationAdapter.h>
 
--- a/Platforms/Wasm/WasmDelayedCallExecutor.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Wasm/WasmDelayedCallExecutor.h	Mon Jun 24 14:35:00 2019 +0200
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <Framework/Toolbox/IDelayedCallExecutor.h>
+#include "../../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h"
 #include <Core/OrthancException.h>
 
 namespace Deprecated
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -1,6 +1,5 @@
 #include "WasmPlatformApplicationAdapter.h"
 
-#include "Framework/Toolbox/MessagingToolbox.h"
 #include "Framework/StoneException.h"
 #include <stdio.h>
 #include "Platforms/Wasm/Defaults.h"
@@ -57,4 +56,4 @@
     }
   }
 
-}
\ No newline at end of file
+}
--- a/Platforms/Wasm/WasmViewport.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Wasm/WasmViewport.h	Mon Jun 24 14:35:00 2019 +0200
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <Framework/Viewport/WidgetViewport.h>
+#include "../../Framework/Deprecated/Viewport/WidgetViewport.h"
 
 #include <emscripten/emscripten.h>
 
--- a/Platforms/Wasm/WasmWebService.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Platforms/Wasm/WasmWebService.h	Mon Jun 24 14:35:00 2019 +0200
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <Framework/Toolbox/BaseWebService.h>
+#include "../../Framework/Deprecated/Toolbox/BaseWebService.h"
 #include <Core/OrthancException.h>
 
 namespace Deprecated
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Jun 19 17:36:33 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Jun 24 14:35:00 2019 +0200
@@ -32,7 +32,6 @@
 
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include_directories(${ORTHANC_ROOT})
-include_directories(${ORTHANC_ROOT}/Core/Images) # hack for the numerous #include "../Enumerations.h" in Orthanc to work
 
 
 #####################################################################
@@ -66,6 +65,7 @@
     message(FATAL_ERROR "WebAssembly target requires the emscripten compiler")    
   endif()
 
+  set(ENABLE_THREADS OFF)
   add_definitions(-DORTHANC_ENABLE_WASM=1)
 else()
   if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR
@@ -75,6 +75,7 @@
     message(FATAL_ERROR "Trying to use a Web compiler for a native build")
   endif()
 
+  set(ENABLE_THREADS ON)
   add_definitions(-DORTHANC_ENABLE_WASM=0)
 endif()
   
@@ -110,7 +111,6 @@
   message("SDL is enabled")
   include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)
   add_definitions(
-    -DORTHANC_ENABLE_NATIVE=1
     -DORTHANC_ENABLE_QT=0
     -DORTHANC_ENABLE_SDL=1
     )
@@ -118,7 +118,6 @@
   message("QT is enabled")
   include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake)
   add_definitions(
-    -DORTHANC_ENABLE_NATIVE=1
     -DORTHANC_ENABLE_QT=1
     -DORTHANC_ENABLE_SDL=0
     )
@@ -128,11 +127,17 @@
   add_definitions(
     -DORTHANC_ENABLE_SDL=0
     -DORTHANC_ENABLE_QT=0
-    -DORTHANC_ENABLE_NATIVE=0
     )
 endif()
 
 
+if (ENABLE_THREADS)
+  add_definitions(-DORTHANC_ENABLE_THREADS=1)
+else()
+  add_definitions(-DORTHANC_ENABLE_THREADS=0)
+endif()
+
+
 if (ENABLE_OPENGL AND CMAKE_SYSTEM_NAME STREQUAL "Windows")
   include(${CMAKE_CURRENT_LIST_DIR}/GlewConfiguration.cmake)
   add_definitions(
@@ -246,7 +251,6 @@
 
 if (NOT ORTHANC_SANDBOXED)
   set(PLATFORM_SOURCES
-    ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoFont.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp
@@ -257,6 +261,12 @@
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h
     )
 
+  if (ENABLE_STONE_DEPRECATED)
+    list(APPEND PLATFORM_SOURCES
+      ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp
+      )
+  endif()
+
   if (ENABLE_SDL OR ENABLE_QT)
     list(APPEND APPLICATIONS_SOURCES
       ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp
@@ -304,63 +314,86 @@
     DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js")
 endif()
 
+if (ENABLE_SDL OR ENABLE_WASM)
+  list(APPEND APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.h
+    )
+endif()
+
+if (ENABLE_STONE_DEPRECATED)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/CircleMeasureTracker.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ColorFrameRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/FrameRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/GrayscaleFrameRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/IVolumeSlicer.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineLayerRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineMeasureTracker.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/RenderStyle.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/BaseWebService.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DownloadStack.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IWebService.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlices.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ViewportGeometry.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IMouseTracker.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IStatusBar.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IViewport.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/WidgetViewport.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Volumes/StructureSetLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/CairoWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/EmptyWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/IWidget.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/IWorldSceneInteractor.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/IWorldSceneMouseTracker.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/LayoutWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanMouseTracker.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanZoomMouseTracker.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/SliceViewerWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestCairoWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestWorldSceneWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WidgetBase.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WorldSceneWidget.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/ZoomMouseTracker.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/dev.h
+    )
+endif()
+
+
+if (ENABLE_THREADS)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp
+    )
+endif()
+
+
+if (ENABLE_WASM)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/WebAssemblyOracle.cpp
+    )
+endif()
+
+
 list(APPEND ORTHANC_STONE_SOURCES
   #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp
   #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp
 
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/FixedPointAligner.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp
-
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditCircleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditCircleMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTools.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTools.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PointerTypes.h
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
 
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp
@@ -368,19 +401,23 @@
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphBitmapAlphabet.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphTextureAlphabet.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/TextBoundingBox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomSeriesVolumeSlicer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomStructureSetSlicer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/FrameRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/GrayscaleFrameRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/IVolumeSlicer.h
-  ${ORTHANC_STONE_ROOT}/Framework/Layers/LineLayerRenderer.cpp
-  ${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/Loaders/BasicFetchingItemsSorter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancImageCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandWithPayload.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/OrthancRestApiCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
@@ -397,75 +434,94 @@
   ${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
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/GrayscaleStyleConfigurator.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/FixedPointAligner.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableStyleConfigurator.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableTextureSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureCommand.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureCommand.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureCommand.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTool.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTool.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PredeclaredTypes.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/UndoStack.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/UndoStack.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h
   ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp
   ${ORTHANC_STONE_ROOT}/Framework/StoneException.h
   ${ORTHANC_STONE_ROOT}/Framework/StoneInitialization.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/AffineTransform2D.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/BaseWebService.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomFrameConverter.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomInstanceParameters.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DownloadStack.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DynamicBitmap.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IDelayedCallExecutor.h
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IWebService.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancApiClient.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancSlicesLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Slice.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/SlicesSorter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ViewportGeometry.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/VolumeImageGeometry.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/IMouseTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h
-  ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImage.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImageReslicer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/IVolumeSlicer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Volumes/StructureSetLoader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/OrientedVolumeBoundingBox.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeReslicer.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/CairoWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/EmptyWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWidget.h
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneInteractor.h
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanMouseTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanZoomMouseTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/SliceViewerWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestWorldSceneWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Widgets/ZoomMouseTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeSceneLayerSource.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Wrappers/CairoContext.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Wrappers/CairoSurface.cpp
 
-  ${ORTHANC_STONE_ROOT}/Framework/dev.h
-
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h
-
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
-  
   ${PLATFORM_SOURCES}
   ${APPLICATIONS_SOURCES}
   ${ORTHANC_CORE_SOURCES}
@@ -487,21 +543,39 @@
 
 if (ENABLE_OPENGL)
   list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Framework/Fonts/OpenGLTextCoordinates.h
     ${ORTHANC_STONE_ROOT}/Framework/Fonts/OpenGLTextCoordinates.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLProgram.h
     ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLProgram.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLShader.h
     ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLShader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLTexture.h
     ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLTexture.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/OpenGLCompositor.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/OpenGLCompositor.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureProgram.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureProgram.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLinesProgram.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.h
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.h
     ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.cpp
     )
 
@@ -513,8 +587,6 @@
 endif()
 
 
-include_directories(${ORTHANC_STONE_ROOT})
-
 
 ##
 ## TEST - Automatically add all ".h" headers to the list of sources
--- a/Resources/CMake/OrthancStoneParameters.cmake	Wed Jun 19 17:36:33 2019 +0200
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Mon Jun 24 14:35:00 2019 +0200
@@ -54,3 +54,4 @@
 
 set(ENABLE_OPENGL ON CACHE INTERNAL "Enable support of OpenGL")
 set(ENABLE_WASM OFF CACHE INTERNAL "Enable support of WebAssembly")
+set(ENABLE_STONE_DEPRECATED OFF CACHE INTERNAL "Enable backward compatibility with deprecated Stone classes")
--- a/Samples/Sdl/BasicScene.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/BasicScene.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -29,6 +29,7 @@
 #include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/Scene2D/ZoomSceneTracker.h"
 #include "../../Framework/Scene2DViewport/ViewportController.h"
+#include "../../Framework/Scene2DViewport/UndoStack.h"
 
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Messages/MessageBroker.h"
@@ -49,10 +50,9 @@
 static const unsigned int FONT_SIZE = 32;
 static const int LAYER_POSITION = 150;
 
-using namespace OrthancStone;
-
-void PrepareScene(ViewportControllerPtr controller)
+void PrepareScene(boost::shared_ptr<OrthancStone::ViewportController> controller)
 {
+  using namespace OrthancStone;
   Scene2D& scene(*controller->GetScene());
   // Texture of 2x2 size
   {
@@ -104,21 +104,21 @@
   {
     std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
 
-    layer->SetThickness(1);
+    layer->SetThickness(10);
 
     PolylineSceneLayer::Chain chain;
     chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5));
     chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
     chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
     chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
-    layer->AddChain(chain, true);
+    layer->AddChain(chain, true, 255, 0, 0);
 
     chain.clear();
     chain.push_back(ScenePoint2D(-5, -5));
     chain.push_back(ScenePoint2D(5, -5));
     chain.push_back(ScenePoint2D(5, 5));
     chain.push_back(ScenePoint2D(-5, 5));
-    layer->AddChain(chain, true);
+    layer->AddChain(chain, true, 0, 255, 0);
 
     double dy = 1.01;
     chain.clear();
@@ -126,9 +126,8 @@
     chain.push_back(ScenePoint2D(4, -4 + dy));
     chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
     chain.push_back(ScenePoint2D(4, 2));
-    layer->AddChain(chain, false);
+    layer->AddChain(chain, false, 0, 0, 255);
 
-    layer->SetColor(0,255, 255);
     scene.SetLayer(50, layer.release());
   }
 
@@ -142,10 +141,11 @@
 
 
 void TakeScreenshot(const std::string& target,
-                    const Scene2D& scene,
+                    const OrthancStone::Scene2D& scene,
                     unsigned int canvasWidth,
                     unsigned int canvasHeight)
 {
+  using namespace OrthancStone;
   // Take a screenshot, then save it as PNG file
   CairoCompositor compositor(scene, canvasWidth, canvasHeight);
   compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1);
@@ -162,11 +162,12 @@
 }
 
 
-void HandleApplicationEvent(ViewportControllerPtr controller,
-                            const OpenGLCompositor& compositor,
+void HandleApplicationEvent(boost::shared_ptr<OrthancStone::ViewportController> controller,
+                            const OrthancStone::OpenGLCompositor& compositor,
                             const SDL_Event& event,
-                            FlexiblePointerTrackerPtr& activeTracker)
+                            boost::shared_ptr<OrthancStone::IFlexiblePointerTracker>& activeTracker)
 {
+  using namespace OrthancStone;
   Scene2D& scene(*controller->GetScene());
   if (event.type == SDL_MOUSEMOTION)
   {
@@ -276,8 +277,9 @@
 }
 
 
-void Run(ViewportControllerPtr controller)
+void Run(boost::shared_ptr<OrthancStone::ViewportController> controller)
 {
+  using namespace OrthancStone;
   SdlOpenGLWindow window("Hello", 1024, 768);
 
   controller->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
@@ -289,7 +291,7 @@
   compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
                      FONT_SIZE, Orthanc::Encoding_Latin1);
 
-  FlexiblePointerTrackerPtr tracker;
+  boost::shared_ptr<IFlexiblePointerTracker> tracker;
 
   bool stop = false;
   while (!stop)
@@ -368,14 +370,16 @@
  **/
 int main(int argc, char* argv[])
 {
+  using namespace OrthancStone;
   StoneInitialize();
   Orthanc::Logging::EnableInfoLevel(true);
 
   try
   {
     MessageBroker broker;
-    ViewportControllerPtr controller = boost::make_shared<ViewportController>(
-		boost::ref(broker));
+    boost::shared_ptr<UndoStack> undoStack(new UndoStack);
+    boost::shared_ptr<ViewportController> controller = boost::make_shared<ViewportController>(
+      undoStack, boost::ref(broker));
     PrepareScene(controller);
     Run(controller);
   }
--- a/Samples/Sdl/CMakeLists.txt	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Mon Jun 24 14:35:00 2019 +0200
@@ -47,6 +47,11 @@
 
 include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake)
 
+add_definitions(
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
+
+
 #####################################################################
 ## Build the samples
 #####################################################################
@@ -55,19 +60,19 @@
   ${ORTHANC_STONE_SOURCES}
   )
 
+#
+# BasicScene
+# 
+
 add_executable(BasicScene
   BasicScene.cpp
   )
 
 target_link_libraries(BasicScene OrthancStone)
 
-if(ENABLE_SDL_CONSOLE)
-  add_definitions(
-    -DENABLE_SDL_CONSOLE=1
-    )
-  LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.c")
-  LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h")
-endif()
+#
+# BasicScene
+# 
 
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp")
@@ -83,8 +88,23 @@
 
 target_link_libraries(TrackerSample OrthancStone)
 
+#
+# Loader
+# 
+
 add_executable(Loader
   Loader.cpp
   )
 
 target_link_libraries(Loader OrthancStone)
+
+#
+# FusionMprSdl
+# 
+
+add_executable(FusionMprSdl
+  FusionMprSdl.cpp
+  FusionMprSdl.h
+)
+
+target_link_libraries(FusionMprSdl OrthancStone)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Sdl/FusionMprSdl.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,813 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "FusionMprSdl.h"
+
+#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+
+#include "../../Framework/StoneInitialization.h"
+
+#include "../../Framework/Scene2D/CairoCompositor.h"
+#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
+#include "../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../Framework/Scene2D/PanSceneTracker.h"
+#include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2D/RotateSceneTracker.h"
+
+#include "../../Framework/Scene2DViewport/UndoStack.h"
+#include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h"
+#include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h"
+#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h"
+#include "../../Framework/Scene2DViewport/MeasureTool.h"
+#include "../../Framework/Scene2DViewport/PredeclaredTypes.h"
+
+#include "../../Framework/Volumes/VolumeSceneLayerSource.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/PngWriter.h>
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/make_shared.hpp>
+
+#include <stdio.h>
+#include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Framework/Oracle/ThreadedOracle.h"
+#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h"
+#include "../../Framework/Loaders/DicomStructureSetLoader.h"
+#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h"
+#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h"
+#include "Core/SystemToolbox.h"
+
+namespace OrthancStone
+{
+  const char* FusionMprMeasureToolToString(size_t i)
+  {
+    static const char* descs[] = {
+      "FusionMprGuiTool_Rotate",
+      "FusionMprGuiTool_Pan",
+      "FusionMprGuiTool_Zoom",
+      "FusionMprGuiTool_LineMeasure",
+      "FusionMprGuiTool_CircleMeasure",
+      "FusionMprGuiTool_AngleMeasure",
+      "FusionMprGuiTool_EllipseMeasure",
+      "FusionMprGuiTool_LAST"
+    };
+    if (i >= FusionMprGuiTool_LAST)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index");
+    }
+    return descs[i];
+  }
+
+  boost::shared_ptr<Scene2D> FusionMprSdlApp::GetScene()
+  {
+    return controller_->GetScene();
+  }
+
+  boost::shared_ptr<const Scene2D> FusionMprSdlApp::GetScene() const
+  {
+    return controller_->GetScene();
+  }
+
+  void FusionMprSdlApp::SelectNextTool()
+  {
+    currentTool_ = static_cast<FusionMprGuiTool>(currentTool_ + 1);
+    if (currentTool_ == FusionMprGuiTool_LAST)
+      currentTool_ = static_cast<FusionMprGuiTool>(0);;
+    printf("Current tool is now: %s\n", FusionMprMeasureToolToString(currentTool_));
+  }
+
+  void FusionMprSdlApp::DisplayInfoText()
+  {
+    // do not try to use stuff too early!
+    if (compositor_.get() == NULL)
+      return;
+
+    std::stringstream msg;
+	
+	for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin();
+		kv != infoTextMap_.end(); ++kv)
+    {
+      msg << kv->first << " : " << kv->second << std::endl;
+    }
+	std::string msgS = msg.str();
+
+    TextSceneLayer* layerP = NULL;
+    if (GetScene()->HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
+    {
+      TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>(
+        GetScene()->GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
+      layerP = &layer;
+    }
+    else
+    {
+      std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
+      layerP = layer.get();
+      layer->SetColor(0, 255, 0);
+      layer->SetFontIndex(1);
+      layer->SetBorder(20);
+      layer->SetAnchor(BitmapAnchor_TopLeft);
+      //layer->SetPosition(0,0);
+      GetScene()->SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
+    }
+    // position the fixed info text in the upper right corner
+    layerP->SetText(msgS.c_str());
+    double cX = compositor_->GetCanvasWidth() * (-0.5);
+    double cY = compositor_->GetCanvasHeight() * (-0.5);
+    GetScene()->GetCanvasToSceneTransform().Apply(cX,cY);
+    layerP->SetPosition(cX, cY);
+  }
+
+  void FusionMprSdlApp::DisplayFloatingCtrlInfoText(const PointerEvent& e)
+  {
+    ScenePoint2D p = e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform());
+
+    char buf[128];
+    sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", 
+      p.GetX(), p.GetY(), 
+      e.GetMainPosition().GetX(), e.GetMainPosition().GetY());
+
+    if (GetScene()->HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
+    {
+      TextSceneLayer& layer =
+        dynamic_cast<TextSceneLayer&>(GetScene()->GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
+      layer.SetText(buf);
+      layer.SetPosition(p.GetX(), p.GetY());
+    }
+    else
+    {
+      std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
+      layer->SetColor(0, 255, 0);
+      layer->SetText(buf);
+      layer->SetBorder(20);
+      layer->SetAnchor(BitmapAnchor_BottomCenter);
+      layer->SetPosition(p.GetX(), p.GetY());
+      GetScene()->SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
+    }
+  }
+
+  void FusionMprSdlApp::HideInfoText()
+  {
+    GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
+  }
+
+  void FusionMprSdlApp::HandleApplicationEvent(
+    const SDL_Event & event)
+  {
+    DisplayInfoText();
+
+    if (event.type == SDL_MOUSEMOTION)
+    {
+      int scancodeCount = 0;
+      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+      if (activeTracker_.get() == NULL &&
+        SDL_SCANCODE_LALT < scancodeCount &&
+        keyboardState[SDL_SCANCODE_LALT])
+      {
+        // The "left-ctrl" key is down, while no tracker is present
+        // Let's display the info text
+        PointerEvent e;
+        e.AddPosition(compositor_->GetPixelCenterCoordinates(
+          event.button.x, event.button.y));
+
+        DisplayFloatingCtrlInfoText(e);
+      }
+      else
+      {
+        HideInfoText();
+        //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)";
+        if (activeTracker_.get() != NULL)
+        {
+          //LOG(TRACE) << "(activeTracker_.get() != NULL)";
+          PointerEvent e;
+          e.AddPosition(compositor_->GetPixelCenterCoordinates(
+            event.button.x, event.button.y));
+          
+          //LOG(TRACE) << "event.button.x = " << event.button.x << "     " <<
+          //  "event.button.y = " << event.button.y;
+          LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
+            e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
+          
+          activeTracker_->PointerMove(e);
+          if (!activeTracker_->IsAlive())
+            activeTracker_.reset();
+        }
+      }
+    }
+    else if (event.type == SDL_MOUSEBUTTONUP)
+    {
+      if (activeTracker_)
+      {
+        PointerEvent e;
+        e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y));
+        activeTracker_->PointerUp(e);
+        if (!activeTracker_->IsAlive())
+          activeTracker_.reset();
+      }
+    }
+    else if (event.type == SDL_MOUSEBUTTONDOWN)
+    {
+      PointerEvent e;
+      e.AddPosition(compositor_->GetPixelCenterCoordinates(
+        event.button.x, event.button.y));
+      if (activeTracker_)
+      {
+        activeTracker_->PointerDown(e);
+        if (!activeTracker_->IsAlive())
+          activeTracker_.reset();
+      }
+      else
+      {
+        // we ATTEMPT to create a tracker if need be
+        activeTracker_ = CreateSuitableTracker(event, e);
+      }
+    }
+    else if (event.type == SDL_KEYDOWN &&
+      event.key.repeat == 0 /* Ignore key bounce */)
+    {
+      switch (event.key.keysym.sym)
+      {
+      case SDLK_ESCAPE:
+        if (activeTracker_)
+        {
+          activeTracker_->Cancel();
+          if (!activeTracker_->IsAlive())
+            activeTracker_.reset();
+        }
+        break;
+
+      case SDLK_t:
+        if (!activeTracker_)
+          SelectNextTool();
+        else
+        {
+          LOG(WARNING) << "You cannot change the active tool when an interaction"
+            " is taking place";
+        }
+        break;
+      case SDLK_s:
+        controller_->FitContent(compositor_->GetCanvasWidth(),
+          compositor_->GetCanvasHeight());
+        break;
+
+      case SDLK_z:
+        LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller_->CanUndo())
+          {
+            LOG(TRACE) << "Undoing...";
+            controller_->Undo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to undo!!!";
+          }
+        }
+        break;
+
+      case SDLK_y:
+        LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller_->CanRedo())
+          {
+            LOG(TRACE) << "Redoing...";
+            controller_->Redo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to redo!!!";
+          }
+        }
+        break;
+
+      case SDLK_c:
+        TakeScreenshot(
+          "screenshot.png",
+          compositor_->GetCanvasWidth(),
+          compositor_->GetCanvasHeight());
+        break;
+
+      default:
+        break;
+      }
+    }
+  }
+
+
+  void FusionMprSdlApp::OnSceneTransformChanged(
+    const ViewportController::SceneTransformChanged& message)
+  {
+    DisplayInfoText();
+  }
+
+  boost::shared_ptr<IFlexiblePointerTracker> FusionMprSdlApp::CreateSuitableTracker(
+    const SDL_Event & event,
+    const PointerEvent & e)
+  {
+    using namespace Orthanc;
+
+    switch (event.button.button)
+    {
+    case SDL_BUTTON_MIDDLE:
+      return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker
+        (controller_, e));
+
+    case SDL_BUTTON_RIGHT:
+      return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker
+        (controller_, e, compositor_->GetCanvasHeight()));
+
+    case SDL_BUTTON_LEFT:
+    {
+      //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:";
+      // TODO: we need to iterate on the set of measuring tool and perform
+      // a hit test to check if a tracker needs to be created for edition.
+      // Otherwise, depending upon the active tool, we might want to create
+      // a "measuring tool creation" tracker
+
+      // TODO: if there are conflicts, we should prefer a tracker that 
+      // pertains to the type of measuring tool currently selected (TBD?)
+      boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e);
+
+      if (hitTestTracker != NULL)
+      {
+        //LOG(TRACE) << "hitTestTracker != NULL";
+        return hitTestTracker;
+      }
+      else
+      {
+        switch (currentTool_)
+        {
+        case FusionMprGuiTool_Rotate:
+          //LOG(TRACE) << "Creating RotateSceneTracker";
+          return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker(
+            controller_, e));
+        case FusionMprGuiTool_Pan:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker(
+            controller_, e));
+        case FusionMprGuiTool_Zoom:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker(
+            controller_, e, compositor_->GetCanvasHeight()));
+        //case GuiTool_AngleMeasure:
+        //  return new AngleMeasureTracker(GetScene(), e);
+        //case GuiTool_CircleMeasure:
+        //  return new CircleMeasureTracker(GetScene(), e);
+        //case GuiTool_EllipseMeasure:
+        //  return new EllipseMeasureTracker(GetScene(), e);
+        case FusionMprGuiTool_LineMeasure:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker(
+            IObserver::GetBroker(), controller_, e));
+        case FusionMprGuiTool_AngleMeasure:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker(
+            IObserver::GetBroker(), controller_, e));
+        case FusionMprGuiTool_CircleMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return boost::shared_ptr<IFlexiblePointerTracker>();
+        case FusionMprGuiTool_EllipseMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return boost::shared_ptr<IFlexiblePointerTracker>();
+        default:
+          throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
+        }
+      }
+    }
+    default:
+      return boost::shared_ptr<IFlexiblePointerTracker>();
+    }
+  }
+
+
+  FusionMprSdlApp::FusionMprSdlApp(MessageBroker& broker)
+    : IObserver(broker)
+    , broker_(broker)
+    , oracleObservable_(broker)
+    , oracle_(*this)
+    , currentTool_(FusionMprGuiTool_Rotate)
+    , undoStack_(new UndoStack)
+  {
+    //oracleObservable.RegisterObserverCallback
+    //(new Callable
+    //  <FusionMprSdlApp, SleepOracleCommand::TimeoutMessage>(*this, &FusionMprSdlApp::Handle));
+
+    //oracleObservable.RegisterObserverCallback
+    //(new Callable
+    //  <Toto, GetOrthancImageCommand::SuccessMessage>(*this, &FusionMprSdlApp::Handle));
+
+    //oracleObservable.RegisterObserverCallback
+    //(new Callable
+    //  <FusionMprSdlApp, GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &ToFusionMprSdlAppto::Handle));
+
+    oracleObservable_.RegisterObserverCallback
+    (new Callable
+      <FusionMprSdlApp, OracleCommandExceptionMessage>(*this, &FusionMprSdlApp::Handle));
+    
+    controller_ = boost::shared_ptr<ViewportController>(
+      new ViewportController(undoStack_, broker_));
+
+    controller_->RegisterObserverCallback(
+      new Callable<FusionMprSdlApp, ViewportController::SceneTransformChanged>
+      (*this, &FusionMprSdlApp::OnSceneTransformChanged));
+
+    TEXTURE_2x2_1_ZINDEX = 1;
+    TEXTURE_1x1_ZINDEX = 2;
+    TEXTURE_2x2_2_ZINDEX = 3;
+    LINESET_1_ZINDEX = 4;
+    LINESET_2_ZINDEX = 5;
+    FLOATING_INFOTEXT_LAYER_ZINDEX = 6;
+    FIXED_INFOTEXT_LAYER_ZINDEX = 7;
+  }
+
+  void FusionMprSdlApp::PrepareScene()
+  {
+    // Texture of 2x2 size
+    {
+      Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
+
+      uint8_t* p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+      p[0] = 255;
+      p[1] = 0;
+      p[2] = 0;
+
+      p[3] = 0;
+      p[4] = 255;
+      p[5] = 0;
+
+      p = reinterpret_cast<uint8_t*>(i.GetRow(1));
+      p[0] = 0;
+      p[1] = 0;
+      p[2] = 255;
+
+      p[3] = 255;
+      p[4] = 0;
+      p[5] = 0;
+
+      GetScene()->SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i));
+    }
+  }
+
+  void FusionMprSdlApp::DisableTracker()
+  {
+    if (activeTracker_)
+    {
+      activeTracker_->Cancel();
+      activeTracker_.reset();
+    }
+  }
+
+  void FusionMprSdlApp::TakeScreenshot(const std::string& target,
+    unsigned int canvasWidth,
+    unsigned int canvasHeight)
+  {
+    CairoCompositor compositor(*GetScene(), canvasWidth, canvasHeight);
+    compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1);
+    compositor.Refresh();
+
+    Orthanc::ImageAccessor canvas;
+    compositor.GetCanvas().GetReadOnlyAccessor(canvas);
+
+    Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
+    Orthanc::ImageProcessing::Convert(png, canvas);
+
+    Orthanc::PngWriter writer;
+    writer.WriteToFile(target, png);
+  }
+
+
+  boost::shared_ptr<IFlexiblePointerTracker> FusionMprSdlApp::TrackerHitTest(const PointerEvent & e)
+  {
+    // std::vector<boost::shared_ptr<MeasureTool>> measureTools_;
+    return boost::shared_ptr<IFlexiblePointerTracker>();
+  }
+
+  static void GLAPIENTRY
+    OpenGLMessageCallback(GLenum source,
+      GLenum type,
+      GLuint id,
+      GLenum severity,
+      GLsizei length,
+      const GLchar* message,
+      const void* userParam)
+  {
+    if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
+    {
+      fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+        (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
+        type, severity, message);
+    }
+  }
+
+  static bool g_stopApplication = false;
+  
+
+  void FusionMprSdlApp::Handle(const DicomVolumeImage::GeometryReadyMessage& message)
+  {
+    printf("Geometry ready\n");
+
+    //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry();
+    //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry();
+    plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry();
+    plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f));
+
+    //Refresh();
+  }
+
+
+  void FusionMprSdlApp::Handle(const OracleCommandExceptionMessage& message)
+  {
+    printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());
+
+    switch (message.GetCommand().GetType())
+    {
+    case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+      printf("URI: [%s]\n", dynamic_cast<const GetOrthancWebViewerJpegCommand&>
+        (message.GetCommand()).GetUri().c_str());
+      break;
+
+    default:
+      break;
+    }
+  }
+
+  void FusionMprSdlApp::SetVolume1(int depth,
+    const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+    OrthancStone::ILayerStyleConfigurator* style)
+  {
+    source1_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume));
+
+    if (style != NULL)
+    {
+      source1_->SetConfigurator(style);
+    }
+  }
+
+  void FusionMprSdlApp::SetVolume2(int depth,
+    const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+    OrthancStone::ILayerStyleConfigurator* style)
+  {
+    source2_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume));
+
+    if (style != NULL)
+    {
+      source2_->SetConfigurator(style);
+    }
+  }
+
+  void FusionMprSdlApp::SetStructureSet(int depth,
+    const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
+  {
+    source3_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume));
+  }
+  
+  void FusionMprSdlApp::Run()
+  {
+    // False means we do NOT let Windows treat this as a legacy application
+    // that needs to be scaled
+    SdlOpenGLWindow window("Hello", 1024, 1024, false);
+
+    controller_->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
+
+    glEnable(GL_DEBUG_OUTPUT);
+    glDebugMessageCallback(OpenGLMessageCallback, 0);
+
+    compositor_.reset(new OpenGLCompositor(window, *GetScene()));
+
+    compositor_->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT,
+      FONT_SIZE_0, Orthanc::Encoding_Latin1);
+    compositor_->SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT,
+      FONT_SIZE_1, Orthanc::Encoding_Latin1);
+
+
+    //////// from loader
+    {
+      Orthanc::WebServiceParameters p;
+      //p.SetUrl("http://localhost:8043/");
+      p.SetCredentials("orthanc", "orthanc");
+      oracle_.SetOrthancParameters(p);
+    }
+
+    //////// from Run
+
+    boost::shared_ptr<DicomVolumeImage>  ct(new DicomVolumeImage);
+    boost::shared_ptr<DicomVolumeImage>  dose(new DicomVolumeImage);
+
+
+    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader;
+    boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader;
+    boost::shared_ptr<DicomStructureSetLoader>  rtstructLoader;
+
+    {
+      ctLoader.reset(new OrthancSeriesVolumeProgressiveLoader(ct, oracle_, oracleObservable_));
+      doseLoader.reset(new OrthancMultiframeVolumeLoader(dose, oracle_, oracleObservable_));
+      rtstructLoader.reset(new DicomStructureSetLoader(oracle_, oracleObservable_));
+    }
+
+    //toto->SetReferenceLoader(*ctLoader);
+    //doseLoader->RegisterObserverCallback
+    //(new Callable
+    //  <FusionMprSdlApp, DicomVolumeImage::GeometryReadyMessage>(*this, &FusionMprSdlApp::Handle));
+    ctLoader->RegisterObserverCallback
+    (new Callable
+      <FusionMprSdlApp, DicomVolumeImage::GeometryReadyMessage>(*this, &FusionMprSdlApp::Handle));
+
+    this->SetVolume1(0, ctLoader, new GrayscaleStyleConfigurator);
+
+    {
+      std::auto_ptr<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator);
+      config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
+
+      boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(dose));
+      this->SetVolume2(1, tmp, config.release());
+    }
+
+    this->SetStructureSet(2, rtstructLoader);
+
+#if 1
+    /*
+    BGO data
+    http://localhost:8042/twiga-orthanc-viewer-demo/twiga-orthanc-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa
+    &
+    dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb
+    &
+    struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+    */
+    ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
+    doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb");  // RT-DOSE
+    rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
+#else
+    //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
+    //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+    //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6");  // RT-STRUCT
+
+    // 2017-05-16
+    ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
+    doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad");  // RT-DOSE
+    rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
+#endif
+
+    oracle_.Start();
+
+//// END from loader
+
+    while (!g_stopApplication)
+    {
+      compositor_->Refresh();
+
+//////// from loader
+      if (source1_.get() != NULL)
+      {
+        source1_->Update(plane_);
+      }
+
+      if (source2_.get() != NULL)
+      {
+        source2_->Update(plane_);
+      }
+
+      if (source3_.get() != NULL)
+      {
+        source3_->Update(plane_);
+      }
+//// END from loader
+
+      SDL_Event event;
+      while (!g_stopApplication && SDL_PollEvent(&event))
+      {
+        if (event.type == SDL_QUIT)
+        {
+          g_stopApplication = true;
+          break;
+        }
+        else if (event.type == SDL_WINDOWEVENT &&
+          event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+        {
+          DisableTracker(); // was: tracker.reset(NULL);
+          compositor_->UpdateSize();
+        }
+        else if (event.type == SDL_KEYDOWN &&
+          event.key.repeat == 0 /* Ignore key bounce */)
+        {
+          switch (event.key.keysym.sym)
+          {
+          case SDLK_f:
+            window.GetWindow().ToggleMaximize();
+            break;
+
+          case SDLK_s:
+            controller_->FitContent(
+              window.GetCanvasWidth(), window.GetCanvasHeight());
+            break;
+
+          case SDLK_q:
+            g_stopApplication = true;
+            break;
+          default:
+            break;
+          }
+        }
+        HandleApplicationEvent(event);
+      }
+      SDL_Delay(1);
+    }
+
+    // the following is paramount because the compositor holds a reference
+    // to the scene and we do not want this reference to become dangling
+    compositor_.reset(NULL);
+
+    //// from loader
+
+    //Orthanc::SystemToolbox::ServerBarrier();
+
+    /**
+     * WARNING => The oracle must be stopped BEFORE the objects using
+     * it are destroyed!!! This forces to wait for the completion of
+     * the running callback methods. Otherwise, the callbacks methods
+     * might still be running while their parent object is destroyed,
+     * resulting in crashes. This is very visible if adding a sleep(),
+     * as in (*).
+     **/
+
+    oracle_.Stop();
+    //// END from loader
+  }
+
+  void FusionMprSdlApp::SetInfoDisplayMessage(
+    std::string key, std::string value)
+  {
+    if (value == "")
+      infoTextMap_.erase(key);
+    else
+      infoTextMap_[key] = value;
+    DisplayInfoText();
+  }
+
+}
+
+
+boost::weak_ptr<OrthancStone::FusionMprSdlApp> g_app;
+
+void FusionMprSdl_SetInfoDisplayMessage(std::string key, std::string value)
+{
+  boost::shared_ptr<OrthancStone::FusionMprSdlApp> app = g_app.lock();
+  if (app)
+  {
+    app->SetInfoDisplayMessage(key, value);
+  }
+}
+
+/**
+ * IMPORTANT: The full arguments to "main()" are needed for SDL on
+ * Windows. Otherwise, one gets the linking error "undefined reference
+ * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
+ **/
+int main(int argc, char* argv[])
+{
+  using namespace OrthancStone;
+
+  StoneInitialize();
+  Orthanc::Logging::EnableInfoLevel(true);
+//  Orthanc::Logging::EnableTraceLevel(true);
+
+  try
+  {
+    OrthancStone::MessageBroker broker;
+    boost::shared_ptr<FusionMprSdlApp> app(new FusionMprSdlApp(broker));
+    g_app = app;
+    app->PrepareScene();
+    app->Run();
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "EXCEPTION: " << e.What();
+  }
+
+  StoneFinalize();
+
+  return 0;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Sdl/FusionMprSdl.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,205 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/Messages/IMessageEmitter.h"
+#include "../../Framework/Oracle/OracleCommandExceptionMessage.h"
+#include "../../Framework/Scene2DViewport/ViewportController.h"
+#include "../../Framework/Volumes/DicomVolumeImage.h"
+#include "../../Framework/Oracle/ThreadedOracle.h"
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/thread.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <SDL.h>
+
+namespace OrthancStone
+{
+  class OpenGLCompositor;
+  class IVolumeSlicer;
+  class ILayerStyleConfigurator;
+  class DicomStructureSetLoader;
+  class IOracle;
+  class ThreadedOracle;
+  class VolumeSceneLayerSource;
+  class NativeFusionMprApplicationContext;
+
+   
+  enum FusionMprGuiTool
+  {
+    FusionMprGuiTool_Rotate = 0,
+    FusionMprGuiTool_Pan,
+    FusionMprGuiTool_Zoom,
+    FusionMprGuiTool_LineMeasure,
+    FusionMprGuiTool_CircleMeasure,
+    FusionMprGuiTool_AngleMeasure,
+    FusionMprGuiTool_EllipseMeasure,
+    FusionMprGuiTool_LAST
+  };
+
+  const char* MeasureToolToString(size_t i);
+
+  static const unsigned int FONT_SIZE_0 = 32;
+  static const unsigned int FONT_SIZE_1 = 24;
+
+  class Scene2D;
+  class UndoStack;
+
+  /**
+  This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that
+  can be sent from multiple threads)
+  */
+  class FusionMprSdlApp : public IObserver
+    , public boost::enable_shared_from_this<FusionMprSdlApp>
+    , public IMessageEmitter
+  {
+  public:
+    // 12 because.
+    FusionMprSdlApp(MessageBroker& broker);
+
+    void PrepareScene();
+    void Run();
+    void SetInfoDisplayMessage(std::string key, std::string value);
+    void DisableTracker();
+
+    boost::shared_ptr<Scene2D> GetScene();
+    boost::shared_ptr<const Scene2D> GetScene() const;
+
+    void HandleApplicationEvent(const SDL_Event& event);
+
+    /**
+    This method is called when the scene transform changes. It allows to
+    recompute the visual elements whose content depend upon the scene transform
+    */
+    void OnSceneTransformChanged(
+      const ViewportController::SceneTransformChanged& message);
+
+
+    virtual void EmitMessage(const IObserver& observer,
+      const IMessage& message) ORTHANC_OVERRIDE
+    {
+      try
+      {
+        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
+        oracleObservable_.EmitMessage(observer, message);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while emitting a message: " << e.What();
+        throw;
+      }
+    }
+    
+  private:
+#if 1
+    // if threaded (not wasm)
+    MessageBroker& broker_;
+    IObservable oracleObservable_;
+    ThreadedOracle oracle_;
+    boost::shared_mutex mutex_; // to serialize messages from the ThreadedOracle
+#endif
+
+    void SelectNextTool();
+
+    /**
+    This returns a random point in the canvas part of the scene, but in
+    scene coordinates
+    */
+    ScenePoint2D GetRandomPointInScene() const;
+
+    boost::shared_ptr<IFlexiblePointerTracker> TrackerHitTest(const PointerEvent& e);
+
+    boost::shared_ptr<IFlexiblePointerTracker> CreateSuitableTracker(
+      const SDL_Event& event,
+      const PointerEvent& e);
+
+    void TakeScreenshot(
+      const std::string& target,
+      unsigned int canvasWidth,
+      unsigned int canvasHeight);
+
+    /**
+      This adds the command at the top of the undo stack
+    */
+    void Commit(boost::shared_ptr<TrackerCommand> cmd);
+    void Undo();
+    void Redo();
+
+
+    // TODO private
+    void Handle(const DicomVolumeImage::GeometryReadyMessage& message);
+    void Handle(const OracleCommandExceptionMessage& message);
+
+    void SetVolume1(
+      int depth,
+      const boost::shared_ptr<IVolumeSlicer>& volume,
+      ILayerStyleConfigurator* style);
+    
+    void SetVolume2(
+      int depth,
+      const boost::shared_ptr<IVolumeSlicer>& volume,
+      ILayerStyleConfigurator* style);
+
+    void SetStructureSet(
+      int depth, 
+      const boost::shared_ptr<DicomStructureSetLoader>& volume);
+
+
+
+  private:
+    void DisplayFloatingCtrlInfoText(const PointerEvent& e);
+    void DisplayInfoText();
+    void HideInfoText();
+
+  private:
+    CoordinateSystem3D  plane_;
+
+    boost::shared_ptr<VolumeSceneLayerSource>  source1_, source2_, source3_;
+
+    std::auto_ptr<OpenGLCompositor> compositor_;
+    /**
+    WARNING: the measuring tools do store a reference to the scene, and it
+    paramount that the scene gets destroyed AFTER the measurement tools.
+    */
+    boost::shared_ptr<ViewportController> controller_;
+
+    std::map<std::string, std::string> infoTextMap_;
+    boost::shared_ptr<IFlexiblePointerTracker> activeTracker_;
+
+    //static const int LAYER_POSITION = 150;
+
+    int TEXTURE_2x2_1_ZINDEX;
+    int TEXTURE_1x1_ZINDEX;
+    int TEXTURE_2x2_2_ZINDEX;
+    int LINESET_1_ZINDEX;
+    int LINESET_2_ZINDEX;
+    int FLOATING_INFOTEXT_LAYER_ZINDEX;
+    int FIXED_INFOTEXT_LAYER_ZINDEX;
+
+    FusionMprGuiTool currentTool_;
+    boost::shared_ptr<UndoStack> undoStack_;
+
+  };
+
+}
+
+
+ 
\ No newline at end of file
--- a/Samples/Sdl/Loader.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -18,2245 +18,36 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-// From Stone
-#include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
-#include "../../Framework/Loaders/BasicFetchingStrategy.h"
-#include "../../Framework/Messages/ICallable.h"
-#include "../../Framework/Messages/IMessage.h"
-#include "../../Framework/Messages/IObservable.h"
-#include "../../Framework/Messages/MessageBroker.h"
-#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Framework/Scene2D/FloatTextureSceneLayer.h"
-#include "../../Framework/Scene2D/Scene2D.h"
+
+#include "../../Framework/Loaders/DicomStructureSetLoader.h"
+#include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h"
+#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../Framework/Oracle/SleepOracleCommand.h"
+#include "../../Framework/Oracle/ThreadedOracle.h"
+#include "../../Framework/Scene2D/CairoCompositor.h"
+#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h"
 #include "../../Framework/StoneInitialization.h"
-#include "../../Framework/Toolbox/GeometryToolbox.h"
-#include "../../Framework/Toolbox/SlicesSorter.h"
-#include "../../Framework/Volumes/ImageBuffer3D.h"
+#include "../../Framework/Volumes/VolumeSceneLayerSource.h"
+#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h"
+#include "../../Framework/Volumes/DicomVolumeImageReslicer.h"
 
 // From Orthanc framework
-#include <Core/Compression/GzipCompressor.h>
-#include <Core/Compression/ZlibCompressor.h>
-#include <Core/DicomFormat/DicomArray.h>
-#include <Core/DicomFormat/DicomImageInformation.h>
-#include <Core/HttpClient.h>
-#include <Core/IDynamicObject.h>
-#include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
-#include <Core/Images/JpegReader.h>
-#include <Core/Images/PamReader.h>
-#include <Core/Images/PngReader.h>
 #include <Core/Images/PngWriter.h>
 #include <Core/Logging.h>
-#include <Core/MultiThreading/SharedMessageQueue.h>
 #include <Core/OrthancException.h>
 #include <Core/SystemToolbox.h>
-#include <Core/Toolbox.h>
-
-#include <json/reader.h>
-#include <json/value.h>
-#include <json/writer.h>
-
-#include <list>
-#include <stdio.h>
-
-
-
-namespace Refactoring
-{
-  class IOracleCommand : public boost::noncopyable
-  {
-  public:
-    enum Type
-    {
-      Type_OrthancRestApi,
-      Type_GetOrthancImage,
-      Type_GetOrthancWebViewerJpeg
-    };
-
-    virtual ~IOracleCommand()
-    {
-    }
-
-    virtual Type GetType() const = 0;
-  };
-
-
-  class IMessageEmitter : public boost::noncopyable
-  {
-  public:
-    virtual ~IMessageEmitter()
-    {
-    }
-
-    virtual void EmitMessage(const OrthancStone::IObserver& observer,
-                             const OrthancStone::IMessage& message) = 0;
-  };
-
-
-  class IOracle : public boost::noncopyable
-  {
-  public:
-    virtual ~IOracle()
-    {
-    }
-
-    virtual void Schedule(const OrthancStone::IObserver& receiver,
-                          IOracleCommand* command) = 0;  // Takes ownership
-  };
-
-
-
-  class IVolumeSlicer : public boost::noncopyable
-  {
-  public:
-    virtual ~IVolumeSlicer()
-    {
-    }
-
-    virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) = 0;
-  };
-
-
-
-  class OracleCommandWithPayload : public IOracleCommand
-  {
-  private:
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-
-  public:
-    void SetPayload(Orthanc::IDynamicObject* payload)
-    {
-      if (payload == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-      else
-      {
-        payload_.reset(payload);
-      }    
-    }
-
-    bool HasPayload() const
-    {
-      return (payload_.get() != NULL);
-    }
-
-    const Orthanc::IDynamicObject& GetPayload() const
-    {
-      if (HasPayload())
-      {
-        return *payload_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-  };
-
-
-
-  class OracleCommandExceptionMessage : public OrthancStone::IMessage
-  {
-    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-  private:
-    const IOracleCommand&       command_;
-    Orthanc::OrthancException   exception_;
-
-  public:
-    OracleCommandExceptionMessage(const IOracleCommand& command,
-                                  const Orthanc::OrthancException& exception) :
-      command_(command),
-      exception_(exception)
-    {
-    }
-
-    OracleCommandExceptionMessage(const IOracleCommand& command,
-                                  const Orthanc::ErrorCode& error) :
-      command_(command),
-      exception_(error)
-    {
-    }
-
-    const IOracleCommand& GetCommand() const
-    {
-      return command_;
-    }
-    
-    const Orthanc::OrthancException& GetException() const
-    {
-      return exception_;
-    }
-  };
-  
-
-  typedef std::map<std::string, std::string>  HttpHeaders;
-
-  class OrthancRestApiCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      HttpHeaders   headers_;
-      std::string   answer_;
-
-    public:
-      SuccessMessage(const OrthancRestApiCommand& command,
-                     const HttpHeaders& answerHeaders,
-                     std::string& answer  /* will be swapped to avoid a memcpy() */) :
-        OriginMessage(command),
-        headers_(answerHeaders),
-        answer_(answer)
-      {
-      }
-
-      const std::string& GetAnswer() const
-      {
-        return answer_;
-      }
-
-      void ParseJsonBody(Json::Value& target) const
-      {
-        Json::Reader reader;
-        if (!reader.parse(answer_, target))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      }
-
-      const HttpHeaders&  GetAnswerHeaders() const
-      {
-        return headers_;
-      }
-    };
-
-
-  private:
-    Orthanc::HttpMethod  method_;
-    std::string          uri_;
-    std::string          body_;
-    HttpHeaders          headers_;
-    unsigned int         timeout_;
-
-    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
-
-  public:
-    OrthancRestApiCommand() :
-      method_(Orthanc::HttpMethod_Get),
-      uri_("/"),
-      timeout_(10)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_OrthancRestApi;
-    }
-
-    void SetMethod(Orthanc::HttpMethod method)
-    {
-      method_ = method;
-    }
-
-    void SetUri(const std::string& uri)
-    {
-      uri_ = uri;
-    }
-
-    void SetBody(const std::string& body)
-    {
-      body_ = body;
-    }
-
-    void SetBody(const Json::Value& json)
-    {
-      Json::FastWriter writer;
-      body_ = writer.write(json);
-    }
-
-    void SetHttpHeaders(const HttpHeaders& headers)
-    {
-      headers_ = headers;
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    Orthanc::HttpMethod GetMethod() const
-    {
-      return method_;
-    }
-
-    const std::string& GetUri() const
-    {
-      return uri_;
-    }
-
-    const std::string& GetBody() const
-    {
-      if (method_ == Orthanc::HttpMethod_Post ||
-          method_ == Orthanc::HttpMethod_Put)
-      {
-        return body_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-  };
-
-
-
-
-  class GetOrthancImageCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      std::auto_ptr<Orthanc::ImageAccessor>  image_;
-      Orthanc::MimeType                      mime_;
-
-    public:
-      SuccessMessage(const GetOrthancImageCommand& command,
-                     Orthanc::ImageAccessor* image,   // Takes ownership
-                     Orthanc::MimeType mime) :
-        OriginMessage(command),
-        image_(image),
-        mime_(mime)
-      {
-        if (image == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      const Orthanc::ImageAccessor& GetImage() const
-      {
-        return *image_;
-      }
-
-      Orthanc::MimeType GetMimeType() const
-      {
-        return mime_;
-      }
-    };
-
-
-  private:
-    std::string           uri_;
-    HttpHeaders           headers_;
-    unsigned int          timeout_;
-    bool                  hasExpectedFormat_;
-    Orthanc::PixelFormat  expectedFormat_;
-
-    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
-
-  public:
-    GetOrthancImageCommand() :
-      uri_("/"),
-      timeout_(10),
-      hasExpectedFormat_(false)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_GetOrthancImage;
-    }
-
-    void SetExpectedPixelFormat(Orthanc::PixelFormat format)
-    {
-      hasExpectedFormat_ = true;
-      expectedFormat_ = format;
-    }
-
-    void SetUri(const std::string& uri)
-    {
-      uri_ = uri;
-    }
-
-    void SetInstanceUri(const std::string& instance,
-                        Orthanc::PixelFormat pixelFormat)
-    {
-      uri_ = "/instances/" + instance;
-          
-      switch (pixelFormat)
-      {
-        case Orthanc::PixelFormat_RGB24:
-          uri_ += "/preview";
-          break;
-      
-        case Orthanc::PixelFormat_Grayscale16:
-          uri_ += "/image-uint16";
-          break;
-      
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          uri_ += "/image-int16";
-          break;
-      
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    const std::string& GetUri() const
-    {
-      return uri_;
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    void ProcessHttpAnswer(IMessageEmitter& emitter,
-                           const OrthancStone::IObserver& receiver,
-                           const std::string& answer,
-                           const HttpHeaders& answerHeaders) const
-    {
-      Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
-
-      for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
-           it != answerHeaders.end(); ++it)
-      {
-        std::string s;
-        Orthanc::Toolbox::ToLowerCase(s, it->first);
-
-        if (s == "content-type")
-        {
-          contentType = Orthanc::StringToMimeType(it->second);
-          break;
-        }
-      }
-
-      std::auto_ptr<Orthanc::ImageAccessor> image;
-
-      switch (contentType)
-      {
-        case Orthanc::MimeType_Png:
-        {
-          image.reset(new Orthanc::PngReader);
-          dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
-          break;
-        }
-
-        case Orthanc::MimeType_Pam:
-        {
-          image.reset(new Orthanc::PamReader);
-          dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
-          break;
-        }
-
-        case Orthanc::MimeType_Jpeg:
-        {
-          image.reset(new Orthanc::JpegReader);
-          dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
-          break;
-        }
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                          "Unsupported HTTP Content-Type for an image: " + 
-                                          std::string(Orthanc::EnumerationToString(contentType)));
-      }
-
-      if (hasExpectedFormat_)
-      {
-        if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
-            image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-        {
-          image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-        }
-
-        if (expectedFormat_ != image->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-        }
-      }
-
-      SuccessMessage message(*this, image.release(), contentType);
-      emitter.EmitMessage(receiver, message);
-    }
-  };
-
-
-
-  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      std::auto_ptr<Orthanc::ImageAccessor>  image_;
-
-    public:
-      SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
-                     Orthanc::ImageAccessor* image) :   // Takes ownership
-        OriginMessage(command),
-        image_(image)
-      {
-        if (image == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      const Orthanc::ImageAccessor& GetImage() const
-      {
-        return *image_;
-      }
-    };
-
-  private:
-    std::string           instanceId_;
-    unsigned int          frame_;
-    unsigned int          quality_;
-    HttpHeaders           headers_;
-    unsigned int          timeout_;
-    Orthanc::PixelFormat  expectedFormat_;
-
-    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
-
-  public:
-    GetOrthancWebViewerJpegCommand() :
-      frame_(0),
-      quality_(95),
-      timeout_(10),
-      expectedFormat_(Orthanc::PixelFormat_Grayscale8)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_GetOrthancWebViewerJpeg;
-    }
-
-    void SetExpectedPixelFormat(Orthanc::PixelFormat format)
-    {
-      expectedFormat_ = format;
-    }
-
-    void SetInstance(const std::string& instanceId)
-    {
-      instanceId_ = instanceId;
-    }
-
-    void SetFrame(unsigned int frame)
-    {
-      frame_ = frame;
-    }
-
-    void SetQuality(unsigned int quality)
-    {
-      if (quality <= 0 ||
-          quality > 100)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        quality_ = quality;
-      }
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    Orthanc::PixelFormat GetExpectedPixelFormat() const
-    {
-      return expectedFormat_;
-    }
-
-    const std::string& GetInstanceId() const
-    {
-      return instanceId_;
-    }
-
-    unsigned int GetFrame() const
-    {
-      return frame_;
-    }
-
-    unsigned int GetQuality() const
-    {
-      return quality_;
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    std::string GetUri() const
-    {
-      return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
-              "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
-    }
-
-    void ProcessHttpAnswer(IMessageEmitter& emitter,
-                           const OrthancStone::IObserver& receiver,
-                           const std::string& answer) const
-    {
-      // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
-      
-      Json::Value encoded;
-
-      {
-        Json::Reader reader;
-        if (!reader.parse(answer, encoded))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      }
-
-      if (encoded.type() != Json::objectValue ||
-          !encoded.isMember("Orthanc") ||
-          encoded["Orthanc"].type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      const Json::Value& info = encoded["Orthanc"];
-      if (!info.isMember("PixelData") ||
-          !info.isMember("Stretched") ||
-          !info.isMember("Compression") ||
-          info["Compression"].type() != Json::stringValue ||
-          info["PixelData"].type() != Json::stringValue ||
-          info["Stretched"].type() != Json::booleanValue ||
-          info["Compression"].asString() != "Jpeg")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      bool isSigned = false;
-      bool isStretched = info["Stretched"].asBool();
-    
-      if (info.isMember("IsSigned"))
-      {
-        if (info["IsSigned"].type() != Json::booleanValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          isSigned = info["IsSigned"].asBool();
-        }
-      }
-    
-      std::auto_ptr<Orthanc::ImageAccessor> reader;
-    
-      {
-        std::string jpeg;
-        Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
-      
-        reader.reset(new Orthanc::JpegReader);
-        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
-      }
-    
-      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
-      {
-        if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      
-        if (isSigned || isStretched)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          SuccessMessage message(*this, reader.release());
-          emitter.EmitMessage(receiver, message);
-          return;
-        }
-      }
-    
-      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      if (!isStretched)
-      {
-        if (expectedFormat_ != reader->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          SuccessMessage message(*this, reader.release());
-          emitter.EmitMessage(receiver, message);
-          return;
-        }
-      }
-    
-      int32_t stretchLow = 0;
-      int32_t stretchHigh = 0;
-    
-      if (!info.isMember("StretchLow") ||
-          !info.isMember("StretchHigh") ||
-          info["StretchLow"].type() != Json::intValue ||
-          info["StretchHigh"].type() != Json::intValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      stretchLow = info["StretchLow"].asInt();
-      stretchHigh = info["StretchHigh"].asInt();
-    
-      if (stretchLow < -32768 ||
-          stretchHigh > 65535 ||
-          (stretchLow < 0 && stretchHigh > 32767))
-      {
-        // This range cannot be represented with a uint16_t or an int16_t
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
-      std::auto_ptr<Orthanc::ImageAccessor> image
-        (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false));
-
-      Orthanc::ImageProcessing::Convert(*image, *reader);
-      reader.reset();
-    
-      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-    
-      if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
-      {
-        float offset = static_cast<float>(stretchLow) / scaling;
-        Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
-      }
-    
-      SuccessMessage message(*this, image.release());
-      emitter.EmitMessage(receiver, message);
-    }
-  };
-
-
-
-
-
-  class DicomInstanceParameters :
-    public Orthanc::IDynamicObject  /* to be used as a payload of SlicesSorter */
-  {
-  private:
-    struct Data   // Struct to ease the copy constructor
-    {
-      std::string                       orthancInstanceId_;
-      std::string                       studyInstanceUid_;
-      std::string                       seriesInstanceUid_;
-      std::string                       sopInstanceUid_;
-      Orthanc::DicomImageInformation    imageInformation_;
-      OrthancStone::SopClassUid         sopClassUid_;
-      double                            thickness_;
-      double                            pixelSpacingX_;
-      double                            pixelSpacingY_;
-      OrthancStone::CoordinateSystem3D  geometry_;
-      OrthancStone::Vector              frameOffsets_;
-      bool                              isColor_;
-      bool                              hasRescale_;
-      double                            rescaleIntercept_;
-      double                            rescaleSlope_;
-      bool                              hasDefaultWindowing_;
-      float                             defaultWindowingCenter_;
-      float                             defaultWindowingWidth_;
-      Orthanc::PixelFormat              expectedPixelFormat_;
-
-      void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
-      {
-        // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
-
-        {
-          std::string increment;
-
-          if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
-          {
-            Orthanc::Toolbox::ToUpperCase(increment);
-            if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
-            {
-              LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
-              return;
-            }
-          }
-        }
-
-        if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
-            frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
-        {
-          LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
-          frameOffsets_.clear();
-        }
-        else
-        {
-          if (frameOffsets_.size() >= 2)
-          {
-            thickness_ = frameOffsets_[1] - frameOffsets_[0];
-
-            if (thickness_ < 0)
-            {
-              thickness_ = -thickness_;
-            }
-          }
-        }
-      }
-
-      Data(const Orthanc::DicomMap& dicom) :
-        imageInformation_(dicom)
-      {
-        if (imageInformation_.GetNumberOfFrames() <= 0)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
-            !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
-            !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        
-        std::string s;
-        if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          sopClassUid_ = OrthancStone::StringToSopClassUid(s);
-        }
-
-        if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
-        {
-          thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
-        }
-
-        OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
-
-        std::string position, orientation;
-        if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
-            dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
-        {
-          geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
-        }
-
-        if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
-        {
-          ComputeDoseOffsets(dicom);
-        }
-
-        isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
-                    imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
-
-        double doseGridScaling;
-
-        if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
-            dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
-        {
-          hasRescale_ = true;
-        }
-        else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
-        {
-          hasRescale_ = true;
-          rescaleIntercept_ = 0;
-          rescaleSlope_ = doseGridScaling;
-        }
-        else
-        {
-          hasRescale_ = false;
-        }
-
-        OrthancStone::Vector c, w;
-        if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-            OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
-            c.size() > 0 && 
-            w.size() > 0)
-        {
-          hasDefaultWindowing_ = true;
-          defaultWindowingCenter_ = static_cast<float>(c[0]);
-          defaultWindowingWidth_ = static_cast<float>(w[0]);
-        }
-        else
-        {
-          hasDefaultWindowing_ = false;
-        }
-
-        if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
-        {
-          switch (imageInformation_.GetBitsStored())
-          {
-            case 16:
-              expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-              break;
-
-            case 32:
-              expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          } 
-        }
-        else if (isColor_)
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
-        }
-        else if (imageInformation_.IsSigned())
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
-        }
-        else
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-        }
-      }
-
-      OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
-      {
-        if (frame == 0)
-        {
-          return geometry_;
-        }
-        else if (frame >= imageInformation_.GetNumberOfFrames())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-        else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
-        {
-          if (frame >= frameOffsets_.size())
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-
-          return OrthancStone::CoordinateSystem3D(
-            geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
-            geometry_.GetAxisX(),
-            geometry_.GetAxisY());
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-      }
-
-      // TODO - Is this necessary?
-      bool FrameContainsPlane(unsigned int frame,
-                              const OrthancStone::CoordinateSystem3D& plane) const
-      {
-        if (frame >= imageInformation_.GetNumberOfFrames())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        OrthancStone::CoordinateSystem3D tmp = geometry_;
-
-        if (frame != 0)
-        {
-          tmp = GetFrameGeometry(frame);
-        }
-
-        double distance;
-
-        return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
-                distance <= thickness_ / 2.0);
-      }
-
-      
-      void ApplyRescale(Orthanc::ImageAccessor& image,
-                        bool useDouble) const
-      {
-        if (image.GetFormat() != Orthanc::PixelFormat_Float32)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-        }
-    
-        if (hasRescale_)
-        {
-          const unsigned int width = image.GetWidth();
-          const unsigned int height = image.GetHeight();
-        
-          for (unsigned int y = 0; y < height; y++)
-          {
-            float* p = reinterpret_cast<float*>(image.GetRow(y));
-
-            if (useDouble)
-            {
-              // Slower, accurate implementation using double
-              for (unsigned int x = 0; x < width; x++, p++)
-              {
-                double value = static_cast<double>(*p);
-                *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
-              }
-            }
-            else
-            {
-              // Fast, approximate implementation using float
-              for (unsigned int x = 0; x < width; x++, p++)
-              {
-                *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
-              }
-            }
-          }
-        }
-      }
-    };
-
-    
-    Data  data_;
 
 
-  public:
-    DicomInstanceParameters(const DicomInstanceParameters& other) :
-      data_(other.data_)
-    {
-    }
-
-    DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
-      data_(dicom)
-    {
-    }
-
-    void SetOrthancInstanceIdentifier(const std::string& id)
-    {
-      data_.orthancInstanceId_ = id;
-    }
-
-    const std::string& GetOrthancInstanceIdentifier() const
-    {
-      return data_.orthancInstanceId_;
-    }
-
-    const Orthanc::DicomImageInformation& GetImageInformation() const
-    {
-      return data_.imageInformation_;
-    }
-
-    const std::string& GetStudyInstanceUid() const
-    {
-      return data_.studyInstanceUid_;
-    }
-
-    const std::string& GetSeriesInstanceUid() const
-    {
-      return data_.seriesInstanceUid_;
-    }
-
-    const std::string& GetSopInstanceUid() const
-    {
-      return data_.sopInstanceUid_;
-    }
-
-    OrthancStone::SopClassUid GetSopClassUid() const
-    {
-      return data_.sopClassUid_;
-    }
-
-    double GetThickness() const
-    {
-      return data_.thickness_;
-    }
-
-    double GetPixelSpacingX() const
-    {
-      return data_.pixelSpacingX_;
-    }
-
-    double GetPixelSpacingY() const
-    {
-      return data_.pixelSpacingY_;
-    }
-
-    const OrthancStone::CoordinateSystem3D&  GetGeometry() const
-    {
-      return data_.geometry_;
-    }
-
-    OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
-    {
-      return data_.GetFrameGeometry(frame);
-    }
-
-    // TODO - Is this necessary?
-    bool FrameContainsPlane(unsigned int frame,
-                            const OrthancStone::CoordinateSystem3D& plane) const
-    {
-      return data_.FrameContainsPlane(frame, plane);
-    }
-
-    bool IsColor() const
-    {
-      return data_.isColor_;
-    }
-
-    bool HasRescale() const
-    {
-      return data_.hasRescale_;
-    }
-
-    double GetRescaleIntercept() const
-    {
-      if (data_.hasRescale_)
-      {
-        return data_.rescaleIntercept_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    double GetRescaleSlope() const
-    {
-      if (data_.hasRescale_)
-      {
-        return data_.rescaleSlope_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    bool HasDefaultWindowing() const
-    {
-      return data_.hasDefaultWindowing_;
-    }
-
-    float GetDefaultWindowingCenter() const
-    {
-      if (data_.hasDefaultWindowing_)
-      {
-        return data_.defaultWindowingCenter_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    float GetDefaultWindowingWidth() const
-    {
-      if (data_.hasDefaultWindowing_)
-      {
-        return data_.defaultWindowingWidth_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    Orthanc::PixelFormat GetExpectedPixelFormat() const
-    {
-      return data_.expectedPixelFormat_;
-    }
-
-
-    OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const
-    {
-      assert(sizeof(float) == 4);
-
-      Orthanc::PixelFormat sourceFormat = source.GetFormat();
-
-      if (sourceFormat != GetExpectedPixelFormat())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      if (sourceFormat == Orthanc::PixelFormat_RGB24)
-      {
-        // This is the case of a color image. No conversion has to be done.
-        return new OrthancStone::ColorTextureSceneLayer(source);
-      }
-      else
-      {
-        if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
-            sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
-            sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-
-        std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture;
-        
-        {
-          // This is the case of a grayscale frame. Convert it to Float32.
-          std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
-                                                                     source.GetWidth(), 
-                                                                     source.GetHeight(),
-                                                                     false));
-          Orthanc::ImageProcessing::Convert(*converted, source);
-
-          // Correct rescale slope/intercept if need be
-          data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
-
-          texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted));
-        }
-
-        if (data_.hasDefaultWindowing_)
-        {
-          texture->SetCustomWindowing(data_.defaultWindowingCenter_,
-                                      data_.defaultWindowingWidth_);
-        }
-        
-        return texture.release();
-      }
-    }
-  };
-
-
-  class DicomVolumeImage : public boost::noncopyable
-  {
-  private:
-    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;
-    std::vector<DicomInstanceParameters*>       slices_;
-    uint64_t                                    revision_;
-    std::vector<uint64_t>                       slicesRevision_;
-    std::vector<unsigned int>                   slicesQuality_;
-
-    void CheckSlice(size_t index,
-                    const DicomInstanceParameters& reference) const
-    {
-      const DicomInstanceParameters& slice = *slices_[index];
-      
-      if (!OrthancStone::GeometryToolbox::IsParallel(
-            reference.GetGeometry().GetNormal(),
-            slice.GetGeometry().GetNormal()))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "A slice in the volume image is not parallel to the others");
-      }
-
-      if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
-                                        "The pixel format changes across the slices of the volume image");
-      }
-
-      if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
-          reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
-                                        "The width/height of slices are not constant in the volume image");
-      }
-
-      if (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
-          !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "The pixel spacing of the slices change across the volume image");
-      }
-    }
-
-    
-    void CheckVolume() const
-    {
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        assert(slices_[i] != NULL);
-        if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                          "This class does not support multi-frame images");
-        }
-      }
-
-      if (slices_.size() != 0)
-      {
-        const DicomInstanceParameters& reference = *slices_[0];
-
-        for (size_t i = 1; i < slices_.size(); i++)
-        {
-          CheckSlice(i, reference);
-        }
-      }
-    }
-
-
-    void Clear()
-    {
-      image_.reset();
-      
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        assert(slices_[i] != NULL);
-        delete slices_[i];
-      }
-
-      slices_.clear();
-      slicesRevision_.clear();
-      slicesQuality_.clear();
-    }
-
-
-    void CheckSliceIndex(size_t index) const
-    {
-      assert(slices_.size() == image_->GetDepth() &&
-             slices_.size() == slicesRevision_.size());
-
-      if (!HasGeometry())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else if (index >= slices_.size())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    
-  public:
-    DicomVolumeImage()
-    {
-    }
-
-    ~DicomVolumeImage()
-    {
-      Clear();
-    }
-
-    // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
-    void SetGeometry(OrthancStone::SlicesSorter& slices)
-    {
-      Clear();
-      
-      if (!slices.Sort())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                        "Cannot sort the 3D slices of a DICOM series");          
-      }
-
-      if (slices.GetSlicesCount() == 0)
-      {
-        // Empty volume
-        image_.reset(new OrthancStone::ImageBuffer3D(Orthanc::PixelFormat_Grayscale8, 0, 0, 0,
-                                                     false /* don't compute range */));
-      }
-      else
-      {
-        slices_.reserve(slices.GetSlicesCount());
-        slicesRevision_.resize(slices.GetSlicesCount(), 0);
-        slicesQuality_.resize(slices.GetSlicesCount(), 0);
-
-        for (size_t i = 0; i < slices.GetSlicesCount(); i++)
-        {
-          const DicomInstanceParameters& slice =
-            dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
-          slices_.push_back(new DicomInstanceParameters(slice));
-        }
-
-        CheckVolume();
-
-        const double spacingZ = slices.ComputeSpacingBetweenSlices();
-        LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
-      
-        const DicomInstanceParameters& parameters = *slices_[0];
-
-        image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(),
-                                                     parameters.GetImageInformation().GetWidth(),
-                                                     parameters.GetImageInformation().GetHeight(),
-                                                     slices.GetSlicesCount(), false /* don't compute range */));      
-
-        image_->GetGeometry().SetAxialGeometry(slices.GetSliceGeometry(0));
-        image_->GetGeometry().SetVoxelDimensions(parameters.GetPixelSpacingX(),
-                                                 parameters.GetPixelSpacingY(), spacingZ);
-      }
-      
-      image_->Clear();
-
-      revision_++;
-    }
-
-    uint64_t GetRevision() const
-    {
-      return revision_;
-    }
-
-    bool HasGeometry() const
-    {
-      return (image_.get() != NULL);
-    }
-
-    const OrthancStone::ImageBuffer3D& GetImage() const
-    {
-      if (!HasGeometry())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        return *image_;
-      }
-    }
-
-    size_t GetSlicesCount() const
-    {
-      if (!HasGeometry())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        return slices_.size();
-      }
-    }
-
-    const DicomInstanceParameters& GetSliceParameters(size_t index) const
-    {
-      CheckSliceIndex(index);
-      return *slices_[index];
-    }
-
-    uint64_t GetSliceRevision(size_t index) const
-    {
-      CheckSliceIndex(index);
-      return slicesRevision_[index];
-    }
-
-    void SetSliceContent(size_t index,
-                         const Orthanc::ImageAccessor& image,
-                         unsigned int quality)
-    {
-      CheckSliceIndex(index);
-
-      // If a better image quality is already available, don't update the content
-      if (quality >= slicesQuality_[index])
-      {
-        {
-          OrthancStone::ImageBuffer3D::SliceWriter writer
-            (*image_, OrthancStone::VolumeProjection_Axial, index);
-          Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
-        }
-        
-        revision_ ++;
-        slicesRevision_[index] += 1;
-      }
-    }
-  };
-
-
-
-  class IDicomVolumeSource : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomVolumeSource()
-    {
-    }
-
-    virtual const DicomVolumeImage& GetVolume() const = 0;
-
-    virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0;
-  };
-  
-  
-
-  class VolumeSeriesOrthancLoader :
-    public OrthancStone::IObserver,
-    public IDicomVolumeSource
-  {
-  private:
-    static const unsigned int LOW_QUALITY = 0;
-    static const unsigned int MIDDLE_QUALITY = 1;
-    static const unsigned int BEST_QUALITY = 2;
-    
-    
-    static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
-    {
-      return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
-    }
-
-
-    void ScheduleNextSliceDownload()
-    {
-      assert(strategy_.get() != NULL);
-      
-      unsigned int sliceIndex, quality;
-      
-      if (strategy_->GetNext(sliceIndex, quality))
-      {
-        assert(quality <= BEST_QUALITY);
-
-        const DicomInstanceParameters& slice = volume_.GetSliceParameters(sliceIndex);
-          
-        const std::string& instance = slice.GetOrthancInstanceIdentifier();
-        if (instance.empty())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        std::auto_ptr<Refactoring::OracleCommandWithPayload> command;
-        
-        if (quality == BEST_QUALITY)
-        {
-          std::auto_ptr<Refactoring::GetOrthancImageCommand> tmp(
-            new Refactoring::GetOrthancImageCommand);
-          tmp->SetHttpHeader("Accept-Encoding", "gzip");
-          tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-          tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());          
-          tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-          command.reset(tmp.release());
-        }
-        else
-        {
-          std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> tmp(
-            new Refactoring::GetOrthancWebViewerJpegCommand);
-          tmp->SetHttpHeader("Accept-Encoding", "gzip");
-          tmp->SetInstance(instance);
-          tmp->SetQuality((quality == 0 ? 50 : 90));
-          tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-          command.reset(tmp.release());
-        }
-
-        command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
-        oracle_.Schedule(*this, command.release());
-      }
-    }
-
-
-    void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      Json::Value body;
-      message.ParseJsonBody(body);
-      
-      if (body.type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      {
-        Json::Value::Members instances = body.getMemberNames();
-
-        OrthancStone::SlicesSorter slices;
-        
-        for (size_t i = 0; i < instances.size(); i++)
-        {
-          Orthanc::DicomMap dicom;
-          dicom.FromDicomAsJson(body[instances[i]]);
-
-          std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
-          instance->SetOrthancInstanceIdentifier(instances[i]);
-
-          OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
-          slices.AddSlice(geometry, instance.release());
-        }
-
-        volume_.SetGeometry(slices);
-      }
-
-      if (volume_.GetSlicesCount() != 0)
-      {
-        strategy_.reset(new OrthancStone::BasicFetchingStrategy(
-                          new OrthancStone::BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY));
-
-        for (unsigned int i = 0; i < 4; i++)   // Schedule up to 4 simultaneous downloads (TODO - parameter)
-        {
-          ScheduleNextSliceDownload();
-        }
-      }
-    }
-
-
-    void LoadBestQualitySliceContent(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
-    {      
-      volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()),
-                              message.GetImage(), BEST_QUALITY);
-
-      ScheduleNextSliceDownload();
-    }
-
-
-    void LoadJpegSliceContent(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
-    {
-      unsigned int quality;
-      
-      switch (message.GetOrigin().GetQuality())
-      {
-        case 50:
-          quality = LOW_QUALITY;
-          break;
-
-        case 90:
-          quality = MIDDLE_QUALITY;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      
-      volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
-
-      ScheduleNextSliceDownload();
-    }
-
-
-    IOracle&          oracle_;
-    bool              active_;
-    DicomVolumeImage  volume_;
-    
-    std::auto_ptr<OrthancStone::IFetchingStrategy>   strategy_;
-
-  public:
-    VolumeSeriesOrthancLoader(IOracle& oracle,
-                              OrthancStone::IObservable& oracleObservable) :
-      IObserver(oracleObservable.GetBroker()),
-      oracle_(oracle),
-      active_(false)
-    {
-      oracleObservable.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::LoadGeometry));
-
-      oracleObservable.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent));
-
-      oracleObservable.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent));
-    }
-
-    void LoadSeries(const std::string& seriesId)
-    {
-      if (active_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      active_ = true;
-
-      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
-      command->SetUri("/series/" + seriesId + "/instances-tags");
-
-      oracle_.Schedule(*this, command.release());
-    }
-    
-
-    virtual const DicomVolumeImage& GetVolume() const
-    {
-      return volume_;
-    }
-
-    
-    virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex)
-    {
-      if (strategy_.get() == NULL)
-      {
-        // Should have called GetVolume().HasGeometry() before
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        strategy_->SetCurrent(sliceIndex);
-      }
-    }
-  };
-
-
-
-#if 0
-  void LoadInstance(const std::string& instanceId)
-  {
-    if (active_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    active_ = true;
-
-    // Tag "3004-000c" is "Grid Frame Offset Vector", which is
-    // mandatory to read RT DOSE, but is too long to be returned by default
-
-    // TODO => Should be part of a second call if needed
-
-    std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
-    command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
-    command->SetPayload(new LoadInstanceGeometryHandler(*this));
-
-    oracle_.Schedule(*this, command.release());
-  }
-#endif
-
-
-  /*  class VolumeSlicerBase : public IVolumeSlicer
-      {
-      private:
-      OrthancStone::Scene2D&            scene_;
-      int                               layerDepth_;
-      bool                              first_;
-      OrthancStone::CoordinateSystem3D  lastPlane_;
-
-      protected:
-      bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const
-      {
-      if (first_ ||
-      !OrthancStone::LinearAlgebra::IsCloseToZero(
-      boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal())))
-      {
-      // This is the first rendering, or the plane has not the same orientation
-      return false;
-      }
-      else
-      {
-      double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin());
-      double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin());
-      return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1);
-      }
-      }
-
-      void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
-      {
-      first_ = false;
-      lastPlane_ = plane;
-      }
-
-      void SetLayer(OrthancStone::ISceneLayer* layer)
-      {
-      scene_.SetLayer(layerDepth_, layer);
-      }
-
-      void DeleteLayer()
-      {
-      scene_.DeleteLayer(layerDepth_);
-      }
-    
-      public:
-      VolumeSlicerBase(OrthancStone::Scene2D& scene,
-      int layerDepth) :
-      scene_(scene),
-      layerDepth_(layerDepth),
-      first_(true)
-      {
-      }
-      };*/
-  
-
-
-  class DicomVolumeMPRSlicer : public IVolumeSlicer
-  {
-  private:
-    bool                            linearInterpolation_;
-    OrthancStone::Scene2D&          scene_;
-    int                             layerDepth_;
-    IDicomVolumeSource&             source_;
-    bool                            first_;
-    OrthancStone::VolumeProjection  lastProjection_;
-    unsigned int                    lastSliceIndex_;
-    uint64_t                        lastSliceRevision_;
-
-  public:
-    DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene,
-                         int layerDepth,
-                         IDicomVolumeSource& source) :
-      linearInterpolation_(false),
-      scene_(scene),
-      layerDepth_(layerDepth),
-      source_(source),
-      first_(true)
-    {
-    }
-
-    void SetLinearInterpolation(bool enabled)
-    {
-      linearInterpolation_ = enabled;
-    }
-
-    bool IsLinearInterpolation() const
-    {
-      return linearInterpolation_;
-    }
-    
-    virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
-    {
-      if (!source_.GetVolume().HasGeometry() ||
-          source_.GetVolume().GetSlicesCount() == 0)
-      {
-        scene_.DeleteLayer(layerDepth_);
-        return;
-      }
-
-      const OrthancStone::VolumeImageGeometry& geometry = source_.GetVolume().GetImage().GetGeometry();
-
-      OrthancStone::VolumeProjection projection;
-      unsigned int sliceIndex;
-      if (!geometry.DetectSlice(projection, sliceIndex, plane))
-      {
-        // The cutting plane is neither axial, nor coronal, nor
-        // sagittal. Could use "VolumeReslicer" here.
-        scene_.DeleteLayer(layerDepth_);
-        return;
-      }
-
-      uint64_t sliceRevision;
-      if (projection == OrthancStone::VolumeProjection_Axial)
-      {
-        sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex);
-
-        if (first_ ||
-            lastSliceIndex_ != sliceIndex)
-        {
-          // Reorder the prefetching queue
-          source_.NotifyAxialSliceAccessed(sliceIndex);
-        }
-      }
-      else
-      {
-        // For coronal and sagittal projections, we take the global
-        // revision of the volume
-        sliceRevision = source_.GetVolume().GetRevision();
-      }
-
-      if (first_ ||
-          lastProjection_ != projection ||
-          lastSliceIndex_ != sliceIndex ||
-          lastSliceRevision_ != sliceRevision)
-      {
-        // Either the viewport plane, or the content of the slice have not
-        // changed since the last time the layer was set: Update is needed
-
-        first_ = false;
-        lastProjection_ = projection;
-        lastSliceIndex_ = sliceIndex;
-        lastSliceRevision_ = sliceRevision;
-
-        std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture;
-        
-        {
-          const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters
-            (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0);
-
-          OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex);
-          texture.reset(parameters.CreateTexture(reader.GetAccessor()));
-        }
-
-        const OrthancStone::CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection);
-
-        double x0, y0, x1, y1;
-        system.ProjectPoint(x0, y0, system.GetOrigin());
-        system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX());
-        texture->SetOrigin(x0, y0);
-
-        double dx = x1 - x0;
-        double dy = y1 - y0;
-        if (!OrthancStone::LinearAlgebra::IsCloseToZero(dx) ||
-            !OrthancStone::LinearAlgebra::IsCloseToZero(dy))
-        {
-          texture->SetAngle(atan2(dy, dx));
-        }
-        
-        OrthancStone::Vector tmp;
-        geometry.GetVoxelDimensions(projection);
-        texture->SetPixelSpacing(tmp[0], tmp[1]);
-
-        texture->SetLinearInterpolation(linearInterpolation_);
-    
-        scene_.SetLayer(layerDepth_, texture.release());    
-      }
-    }
-  };
-  
-  
-
-
-
-  class NativeOracle : public IOracle
-  {
-  private:
-    class Item : public Orthanc::IDynamicObject
-    {
-    private:
-      const OrthancStone::IObserver&  receiver_;
-      std::auto_ptr<IOracleCommand>   command_;
-
-    public:
-      Item(const OrthancStone::IObserver& receiver,
-           IOracleCommand* command) :
-        receiver_(receiver),
-        command_(command)
-      {
-        if (command == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      const OrthancStone::IObserver& GetReceiver() const
-      {
-        return receiver_;
-      }
-
-      const IOracleCommand& GetCommand() const
-      {
-        assert(command_.get() != NULL);
-        return *command_;
-      }
-    };
-
-
-    enum State
-    {
-      State_Setup,
-      State_Running,
-      State_Stopped
-    };
-
-
-    IMessageEmitter&               emitter_;
-    Orthanc::WebServiceParameters  orthanc_;
-    Orthanc::SharedMessageQueue    queue_;
-    State                          state_;
-    boost::mutex                   mutex_;
-    std::vector<boost::thread*>    workers_;
-
-
-    void CopyHttpHeaders(Orthanc::HttpClient& client,
-                         const HttpHeaders& headers)
-    {
-      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
-      {
-        client.AddHeader(it->first, it->second);
-      }
-    }
-
-
-    void DecodeAnswer(std::string& answer,
-                      const HttpHeaders& headers)
-    {
-      Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
-
-      for (HttpHeaders::const_iterator it = headers.begin(); 
-           it != headers.end(); ++it)
-      {
-        std::string s;
-        Orthanc::Toolbox::ToLowerCase(s, it->first);
-
-        if (s == "content-encoding")
-        {
-          if (it->second == "gzip")
-          {
-            contentEncoding = Orthanc::HttpCompression_Gzip;
-          }
-          else 
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                            "Unsupported HTTP Content-Encoding: " + it->second);
-          }
-
-          break;
-        }
-      }
-
-      if (contentEncoding == Orthanc::HttpCompression_Gzip)
-      {
-        std::string compressed;
-        answer.swap(compressed);
-          
-        Orthanc::GzipCompressor compressor;
-        compressor.Uncompress(answer, compressed.c_str(), compressed.size());
-      }
-    }
-
-
-    void Execute(const OrthancStone::IObserver& receiver,
-                 const OrthancRestApiCommand& command)
-    {
-      Orthanc::HttpClient client(orthanc_, command.GetUri());
-      client.SetMethod(command.GetMethod());
-      client.SetTimeout(command.GetTimeout());
-
-      CopyHttpHeaders(client, command.GetHttpHeaders());
-
-      if (command.GetMethod() == Orthanc::HttpMethod_Post ||
-          command.GetMethod() == Orthanc::HttpMethod_Put)
-      {
-        client.SetBody(command.GetBody());
-      }
-
-      std::string answer;
-      HttpHeaders answerHeaders;
-      client.ApplyAndThrowException(answer, answerHeaders);
-
-      DecodeAnswer(answer, answerHeaders);
-
-      OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
-      emitter_.EmitMessage(receiver, message);
-    }
-
-
-    void Execute(const OrthancStone::IObserver& receiver,
-                 const GetOrthancImageCommand& command)
-    {
-      Orthanc::HttpClient client(orthanc_, command.GetUri());
-      client.SetTimeout(command.GetTimeout());
-
-      CopyHttpHeaders(client, command.GetHttpHeaders());
-
-      std::string answer;
-      HttpHeaders answerHeaders;
-      client.ApplyAndThrowException(answer, answerHeaders);
-
-      DecodeAnswer(answer, answerHeaders);
-
-      command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders);
-    }
-
-
-    void Execute(const OrthancStone::IObserver& receiver,
-                 const GetOrthancWebViewerJpegCommand& command)
-    {
-      Orthanc::HttpClient client(orthanc_, command.GetUri());
-      client.SetTimeout(command.GetTimeout());
-
-      CopyHttpHeaders(client, command.GetHttpHeaders());
-
-      std::string answer;
-      HttpHeaders answerHeaders;
-      client.ApplyAndThrowException(answer, answerHeaders);
-
-      DecodeAnswer(answer, answerHeaders);
-
-      command.ProcessHttpAnswer(emitter_, receiver, answer);
-    }
-
-
-    void Step()
-    {
-      std::auto_ptr<Orthanc::IDynamicObject>  object(queue_.Dequeue(100));
-
-      if (object.get() != NULL)
-      {
-        const Item& item = dynamic_cast<Item&>(*object);
-
-        try
-        {
-          switch (item.GetCommand().GetType())
-          {
-            case IOracleCommand::Type_OrthancRestApi:
-              Execute(item.GetReceiver(), 
-                      dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
-              break;
-
-            case IOracleCommand::Type_GetOrthancImage:
-              Execute(item.GetReceiver(), 
-                      dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
-              break;
-
-            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-              Execute(item.GetReceiver(), 
-                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          }
-        }
-        catch (Orthanc::OrthancException& e)
-        {
-          LOG(ERROR) << "Exception within the oracle: " << e.What();
-          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
-        }
-        catch (...)
-        {
-          LOG(ERROR) << "Native exception within the oracle";
-          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
-                               (item.GetCommand(), Orthanc::ErrorCode_InternalError));
-        }
-      }
-    }
-
-
-    static void Worker(NativeOracle* that)
-    {
-      assert(that != NULL);
-      
-      for (;;)
-      {
-        {
-          boost::mutex::scoped_lock lock(that->mutex_);
-          if (that->state_ != State_Running)
-          {
-            return;
-          }
-        }
-
-        that->Step();
-      }
-    }
-
-
-    void StopInternal()
-    {
-      {
-        boost::mutex::scoped_lock lock(mutex_);
-
-        if (state_ == State_Setup ||
-            state_ == State_Stopped)
-        {
-          return;
-        }
-        else
-        {
-          state_ = State_Stopped;
-        }
-      }
-
-      for (size_t i = 0; i < workers_.size(); i++)
-      {
-        if (workers_[i] != NULL)
-        {
-          if (workers_[i]->joinable())
-          {
-            workers_[i]->join();
-          }
-
-          delete workers_[i];
-        }
-      } 
-    }
-
-
-  public:
-    NativeOracle(IMessageEmitter& emitter) :
-      emitter_(emitter),
-      state_(State_Setup),
-      workers_(4)
-    {
-    }
-
-    virtual ~NativeOracle()
-    {
-      StopInternal();
-    }
-
-    void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (state_ != State_Setup)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        orthanc_ = orthanc;
-      }
-    }
-
-    void SetWorkersCount(unsigned int count)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (count <= 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      else if (state_ != State_Setup)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        workers_.resize(count);
-      }
-    }
-
-    void Start()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (state_ != State_Setup)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        state_ = State_Running;
-
-        for (unsigned int i = 0; i < workers_.size(); i++)
-        {
-          workers_[i] = new boost::thread(Worker, this);
-        }
-      }      
-    }
-
-    void Stop()
-    {
-      StopInternal();
-    }
-
-    virtual void Schedule(const OrthancStone::IObserver& receiver,
-                          IOracleCommand* command)
-    {
-      queue_.Enqueue(new Item(receiver, command));
-    }
-  };
-
-
+namespace OrthancStone
+{
   class NativeApplicationContext : public IMessageEmitter
   {
   private:
-    boost::shared_mutex            mutex_;
-    OrthancStone::MessageBroker    broker_;
-    OrthancStone::IObservable      oracleObservable_;
+    boost::shared_mutex  mutex_;
+    MessageBroker        broker_;
+    IObservable          oracleObservable_;
 
   public:
     NativeApplicationContext() :
@@ -2265,8 +56,8 @@
     }
 
 
-    virtual void EmitMessage(const OrthancStone::IObserver& observer,
-                             const OrthancStone::IMessage& message)
+    virtual void EmitMessage(const IObserver& observer,
+                             const IMessage& message) ORTHANC_OVERRIDE
     {
       try
       {
@@ -2308,12 +99,12 @@
       {
       }
 
-      OrthancStone::MessageBroker& GetBroker() 
+      MessageBroker& GetBroker() 
       {
         return that_.broker_;
       }
 
-      OrthancStone::IObservable& GetOracleObservable()
+      IObservable& GetOracleObservable()
       {
         return that_.oracleObservable_;
       }
@@ -2326,7 +117,88 @@
 class Toto : public OrthancStone::IObserver
 {
 private:
-  void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message)
+  OrthancStone::CoordinateSystem3D  plane_;
+  OrthancStone::IOracle&            oracle_;
+  OrthancStone::Scene2D             scene_;
+  std::auto_ptr<OrthancStone::VolumeSceneLayerSource>  source1_, source2_, source3_;
+
+
+  void Refresh()
+  {
+    if (source1_.get() != NULL)
+    {
+      source1_->Update(plane_);
+    }
+      
+    if (source2_.get() != NULL)
+    {
+      source2_->Update(plane_);
+    }
+
+    if (source3_.get() != NULL)
+    {
+      source3_->Update(plane_);
+    }
+
+    scene_.FitContent(1024, 768);
+      
+    {
+      OrthancStone::CairoCompositor compositor(scene_, 1024, 768);
+      compositor.Refresh();
+        
+      Orthanc::ImageAccessor accessor;
+      compositor.GetCanvas().GetReadOnlyAccessor(accessor);
+
+      Orthanc::Image tmp(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false);
+      Orthanc::ImageProcessing::Convert(tmp, accessor);
+        
+      static unsigned int count = 0;
+      char buf[64];
+      sprintf(buf, "scene-%06d.png", count++);
+        
+      Orthanc::PngWriter writer;
+      writer.WriteToFile(buf, tmp);
+    }
+  }
+
+  
+  void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message)
+  {
+    printf("Geometry ready\n");
+    
+    plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry();
+    //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry();
+    //plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry();
+    plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f));
+
+    Refresh();
+  }
+  
+  
+  void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message)
+  {
+    if (message.GetOrigin().HasPayload())
+    {
+      printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue());
+    }
+    else
+    {
+      printf("TIMEOUT\n");
+
+      Refresh();
+
+      /**
+       * The sleep() leads to a crash if the oracle is still running,
+       * while this object is destroyed. Always stop the oracle before
+       * destroying active objects.  (*)
+       **/
+      // boost::this_thread::sleep(boost::posix_time::seconds(2));
+
+      oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay()));
+    }
+  }
+
+  void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
   {
     Json::Value v;
     message.ParseJsonBody(v);
@@ -2334,24 +206,24 @@
     printf("ICI [%s]\n", v.toStyledString().c_str());
   }
 
-  void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
+  void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
   {
     printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
   }
 
-  void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
   {
     printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
   }
 
-  void Handle(const Refactoring::OracleCommandExceptionMessage& message)
+  void Handle(const OrthancStone::OracleCommandExceptionMessage& message)
   {
     printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());
 
     switch (message.GetCommand().GetType())
     {
-      case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg:
-        printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&>
+      case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg:
+        printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>
                (message.GetCommand()).GetUri().c_str());
         break;
       
@@ -2361,48 +233,133 @@
   }
 
 public:
-  Toto(OrthancStone::IObservable& oracle) :
-    IObserver(oracle.GetBroker())
+  Toto(OrthancStone::IOracle& oracle,
+       OrthancStone::IObservable& oracleObservable) :
+    IObserver(oracleObservable.GetBroker()),
+    oracle_(oracle)
   {
-    oracle.RegisterObserverCallback
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle));
+
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracleObservable.RegisterObserverCallback
       (new OrthancStone::Callable
-       <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
 
-    oracle.RegisterObserverCallback
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle));
+  }
+
+  void SetReferenceLoader(OrthancStone::IObservable& loader)
+  {
+    loader.RegisterObserverCallback
       (new OrthancStone::Callable
-       <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle));
+  }
+
+  void SetVolume1(int depth,
+                  const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+                  OrthancStone::ILayerStyleConfigurator* style)
+  {
+    source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
+
+    if (style != NULL)
+    {
+      source1_->SetConfigurator(style);
+    }
+  }
 
-    oracle.RegisterObserverCallback
-      (new OrthancStone::Callable
-       <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
+  void SetVolume2(int depth,
+                  const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+                  OrthancStone::ILayerStyleConfigurator* style)
+  {
+    source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
 
-    oracle.RegisterObserverCallback
-      (new OrthancStone::Callable
-       <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle));
+    if (style != NULL)
+    {
+      source2_->SetConfigurator(style);
+    }
   }
+
+  void SetStructureSet(int depth,
+                       const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
+  {
+    source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
+  }
+                       
 };
 
 
-void Run(Refactoring::NativeApplicationContext& context,
-         Refactoring::IOracle& oracle)
+void Run(OrthancStone::NativeApplicationContext& context,
+         OrthancStone::ThreadedOracle& oracle)
 {
-  std::auto_ptr<Toto> toto;
-  std::auto_ptr<Refactoring::VolumeSeriesOrthancLoader> loader1, loader2;
+  // the oracle has been supplied with the context (as an IEmitter) upon
+  // creation
+  boost::shared_ptr<OrthancStone::DicomVolumeImage>  ct(new OrthancStone::DicomVolumeImage);
+  boost::shared_ptr<OrthancStone::DicomVolumeImage>  dose(new OrthancStone::DicomVolumeImage);
+
+  
+  boost::shared_ptr<Toto> toto;
+  boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader;
+  boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader;
+  boost::shared_ptr<OrthancStone::DicomStructureSetLoader>  rtstructLoader;
 
   {
-    Refactoring::NativeApplicationContext::WriterLock lock(context);
-    toto.reset(new Toto(lock.GetOracleObservable()));
-    loader1.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable()));
-    loader2.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable()));
+    OrthancStone::NativeApplicationContext::WriterLock lock(context);
+    toto.reset(new Toto(oracle, lock.GetOracleObservable()));
+
+    // the oracle is used to schedule commands
+    // the oracleObservable is used by the loaders to:
+    // - request the broker (lifetime mgmt)
+    // - register the loader callbacks (called indirectly by the oracle)
+    ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable()));
+    doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable()));
+    rtstructLoader.reset(new OrthancStone::DicomStructureSetLoader(oracle, lock.GetOracleObservable()));
   }
 
-  if (1)
+
+  //toto->SetReferenceLoader(*ctLoader);
+  toto->SetReferenceLoader(*doseLoader);
+
+
+#if 1
+  toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator);
+#else
+  {
+    boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::DicomVolumeImageReslicer(ct));
+    toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator);
+  }
+#endif  
+  
+
+  {
+    std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator);
+    config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
+
+    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose));
+    toto->SetVolume2(1, tmp, config.release());
+  }
+
+  toto->SetStructureSet(2, rtstructLoader);
+  
+  oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100));
+
+  if (0)
   {
     Json::Value v = Json::objectValue;
     v["Level"] = "Series";
     v["Query"] = Json::objectValue;
 
-    std::auto_ptr<Refactoring::OrthancRestApiCommand>  command(new Refactoring::OrthancRestApiCommand);
+    std::auto_ptr<OrthancStone::OrthancRestApiCommand>  command(new OrthancStone::OrthancRestApiCommand);
     command->SetMethod(Orthanc::HttpMethod_Post);
     command->SetUri("/tools/find");
     command->SetBody(v);
@@ -2410,67 +367,112 @@
     oracle.Schedule(*toto, command.release());
   }
   
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
-    oracle.Schedule(*toto, command.release());
-  }
-  
-  if (1)
+  if(0)
   {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
-    oracle.Schedule(*toto, command.release());
-  }
-  
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
-    oracle.Schedule(*toto, command.release());
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+      oracle.Schedule(*toto, command.release());
+    }
+
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand>  command(new OrthancStone::GetOrthancWebViewerJpegCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
+      command->SetQuality(90);
+      oracle.Schedule(*toto, command.release());
+    }
+
+
+    if (0)
+    {
+      for (unsigned int i = 0; i < 10; i++)
+      {
+        std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000));
+        command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i));
+        oracle.Schedule(*toto, command.release());
+      }
+    }
   }
   
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept-Encoding", "gzip");
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
-    oracle.Schedule(*toto, command.release());
-  }
+  // 2017-11-17-Anonymized
+#if 0
+  // BGO data
+  ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
+  doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb");  // RT-DOSE
+  //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
+#else
+  //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
+  //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+  //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6");  // RT-STRUCT
+
+  // 2017-05-16
+  ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
+  doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad");  // RT-DOSE
+  rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
+#endif
+  // 2015-01-28-Multiframe
+  //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279");  // Multiframe CT
   
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
-    oracle.Schedule(*toto, command.release());
-  }
-
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand>  command(new Refactoring::GetOrthancWebViewerJpegCommand);
-    command->SetHttpHeader("Accept-Encoding", "gzip");
-    command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
-    command->SetQuality(90);
-    oracle.Schedule(*toto, command.release());
-  }
+  // Delphine
+  //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e");  // CT
+  //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");  // Lung 1/10mm
 
 
-  // 2017-11-17-Anonymized
-  //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
-  //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+  {
+    LOG(WARNING) << "...Waiting for Ctrl-C...";
+
+    oracle.Start();
+
+    Orthanc::SystemToolbox::ServerBarrier();
 
-  // Delphine
-  loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e");  // CT
+    /**
+     * WARNING => The oracle must be stopped BEFORE the objects using
+     * it are destroyed!!! This forces to wait for the completion of
+     * the running callback methods. Otherwise, the callbacks methods
+     * might still be running while their parent object is destroyed,
+     * resulting in crashes. This is very visible if adding a sleep(),
+     * as in (*).
+     **/
 
-  LOG(WARNING) << "...Waiting for Ctrl-C...";
-  Orthanc::SystemToolbox::ServerBarrier();
-  //boost::this_thread::sleep(boost::posix_time::seconds(1));
+    oracle.Stop();
+  }
 }
 
 
@@ -2483,13 +485,14 @@
 int main(int argc, char* argv[])
 {
   OrthancStone::StoneInitialize();
-  Orthanc::Logging::EnableInfoLevel(true);
+  //Orthanc::Logging::EnableInfoLevel(true);
 
   try
   {
-    Refactoring::NativeApplicationContext context;
+    OrthancStone::NativeApplicationContext context;
 
-    Refactoring::NativeOracle oracle(context);
+    OrthancStone::ThreadedOracle oracle(context);
+    //oracle.SetThreadsCount(1);
 
     {
       Orthanc::WebServiceParameters p;
@@ -2498,11 +501,11 @@
       oracle.SetOrthancParameters(p);
     }
 
-    oracle.Start();
+    //oracle.Start();
 
     Run(context, oracle);
-
-    oracle.Stop();
+    
+    //oracle.Stop();
   }
   catch (Orthanc::OrthancException& e)
   {
--- a/Samples/Sdl/TrackerSample.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/TrackerSample.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -48,18 +48,11 @@
 
 */
 
-
-using namespace Orthanc;
-using namespace OrthancStone;
-
-
-
-
-boost::weak_ptr<TrackerSampleApp> g_app;
+boost::weak_ptr<OrthancStone::TrackerSampleApp> g_app;
 
 void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value)
 {
-  boost::shared_ptr<TrackerSampleApp> app = g_app.lock();
+  boost::shared_ptr<OrthancStone::TrackerSampleApp> app = g_app.lock();
   if (app)
   {
     app->SetInfoDisplayMessage(key, value);
@@ -73,9 +66,11 @@
  **/
 int main(int argc, char* argv[])
 {
+  using namespace OrthancStone;
+
   StoneInitialize();
   Orthanc::Logging::EnableInfoLevel(true);
-  Orthanc::Logging::EnableTraceLevel(true);
+//  Orthanc::Logging::EnableTraceLevel(true);
 
   try
   {
--- a/Samples/Sdl/TrackerSampleApp.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -20,20 +20,19 @@
 
 #include "TrackerSampleApp.h"
 
-#include <Framework/Scene2DViewport/CreateLineMeasureTracker.h>
-#include <Framework/Scene2DViewport/CreateAngleMeasureTracker.h>
+#include "../../Applications/Sdl/SdlOpenGLWindow.h"
 
-#include <Framework/Scene2D/PanSceneTracker.h>
-#include <Framework/Scene2D/RotateSceneTracker.h>
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ZoomSceneTracker.h>
-#include <Framework/Scene2D/CairoCompositor.h>
-#include <Framework/Scene2D/ColorTextureSceneLayer.h>
-#include <Framework/Scene2D/OpenGLCompositor.h>
-
-#include <Framework/StoneInitialization.h>
-
-#include <Applications/Sdl/SdlOpenGLWindow.h>
+#include "../../Framework/Scene2D/CairoCompositor.h"
+#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
+#include "../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../Framework/Scene2D/PanSceneTracker.h"
+#include "../../Framework/Scene2D/RotateSceneTracker.h"
+#include "../../Framework/Scene2D/Scene2D.h"
+#include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2DViewport/UndoStack.h"
+#include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h"
+#include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h"
+#include "../../Framework/StoneInitialization.h"
 
 // From Orthanc framework
 #include <Core/Logging.h>
@@ -42,11 +41,12 @@
 #include <Core/Images/ImageProcessing.h>
 #include <Core/Images/PngWriter.h>
 
+#include <boost/ref.hpp>
+#include <boost/make_shared.hpp>
 #include <SDL.h>
+
 #include <stdio.h>
 
-using namespace Orthanc;
-
 namespace OrthancStone
 {
   const char* MeasureToolToString(size_t i)
@@ -68,7 +68,12 @@
     return descs[i];
   }
 
-  Scene2DPtr TrackerSampleApp::GetScene()
+  boost::shared_ptr<Scene2D> TrackerSampleApp::GetScene()
+  {
+    return controller_->GetScene();
+  }
+
+  boost::shared_ptr<const Scene2D> TrackerSampleApp::GetScene() const
   {
     return controller_->GetScene();
   }
@@ -155,6 +160,71 @@
     GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
   }
 
+  ScenePoint2D TrackerSampleApp::GetRandomPointInScene() const
+  {
+    unsigned int w = compositor_->GetCanvasWidth();
+    LOG(TRACE) << "compositor_->GetCanvasWidth() = " << 
+      compositor_->GetCanvasWidth();
+    unsigned int h = compositor_->GetCanvasHeight();
+    LOG(TRACE) << "compositor_->GetCanvasHeight() = " << 
+      compositor_->GetCanvasHeight();
+
+    if ((w >= RAND_MAX) || (h >= RAND_MAX))
+      LOG(WARNING) << "Canvas is too big : tools will not be randomly placed";
+
+    int x = rand() % w;
+    int y = rand() % h;
+    LOG(TRACE) << "random x = " << x << "random y = " << y;
+
+    ScenePoint2D p = compositor_->GetPixelCenterCoordinates(x, y);
+    LOG(TRACE) << "--> p.GetX() = " << p.GetX() << " p.GetY() = " << p.GetY();
+
+    ScenePoint2D r = p.Apply(GetScene()->GetCanvasToSceneTransform());
+    LOG(TRACE) << "--> r.GetX() = " << r.GetX() << " r.GetY() = " << r.GetY();
+    return r;
+  }
+
+  void TrackerSampleApp::CreateRandomMeasureTool()
+  {
+    static bool srandCalled = false;
+    if (!srandCalled)
+    {
+      srand(42);
+      srandCalled = true;
+    }
+
+    int i = rand() % 2;
+    LOG(TRACE) << "random i = " << i;
+    switch (i)
+    {
+    case 0:
+      // line measure
+      {
+        boost::shared_ptr<CreateLineMeasureCommand> cmd = 
+          boost::make_shared<CreateLineMeasureCommand>(
+		  boost::ref(IObserver::GetBroker()),
+            controller_,
+            GetRandomPointInScene());
+        cmd->SetEnd(GetRandomPointInScene());
+        controller_->PushCommand(cmd);
+      }
+      break;
+    case 1:
+      // angle measure
+      {
+      boost::shared_ptr<CreateAngleMeasureCommand> cmd =
+        boost::make_shared<CreateAngleMeasureCommand>(
+          boost::ref(IObserver::GetBroker()),
+          controller_,
+          GetRandomPointInScene());
+        cmd->SetCenter(GetRandomPointInScene());
+        cmd->SetSide2End(GetRandomPointInScene());
+        controller_->PushCommand(cmd);
+      }
+      break;
+    }
+  }
+
   void TrackerSampleApp::HandleApplicationEvent(
     const SDL_Event & event)
   {
@@ -166,8 +236,8 @@
       const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
 
       if (activeTracker_.get() == NULL &&
-        SDL_SCANCODE_LCTRL < scancodeCount &&
-        keyboardState[SDL_SCANCODE_LCTRL])
+        SDL_SCANCODE_LALT < scancodeCount &&
+        keyboardState[SDL_SCANCODE_LALT])
       {
         // The "left-ctrl" key is down, while no tracker is present
         // Let's display the info text
@@ -190,8 +260,8 @@
           
           //LOG(TRACE) << "event.button.x = " << event.button.x << "     " <<
           //  "event.button.y = " << event.button.y;
-          //LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
-          //  e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
+          LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
+            e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
           
           activeTracker_->PointerMove(e);
           if (!activeTracker_->IsAlive())
@@ -251,11 +321,46 @@
         }
         break;
 
+      case SDLK_m:
+        CreateRandomMeasureTool();
+        break;
       case SDLK_s:
         controller_->FitContent(compositor_->GetCanvasWidth(),
           compositor_->GetCanvasHeight());
         break;
 
+      case SDLK_z:
+        LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller_->CanUndo())
+          {
+            LOG(TRACE) << "Undoing...";
+            controller_->Undo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to undo!!!";
+          }
+        }
+        break;
+
+      case SDLK_y:
+        LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller_->CanRedo())
+          {
+            LOG(TRACE) << "Redoing...";
+            controller_->Redo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to redo!!!";
+          }
+        }
+        break;
+
       case SDLK_c:
         TakeScreenshot(
           "screenshot.png",
@@ -276,18 +381,20 @@
     DisplayInfoText();
   }
 
-  FlexiblePointerTrackerPtr TrackerSampleApp::CreateSuitableTracker(
+  boost::shared_ptr<IFlexiblePointerTracker> TrackerSampleApp::CreateSuitableTracker(
     const SDL_Event & event,
     const PointerEvent & e)
   {
+    using namespace Orthanc;
+
     switch (event.button.button)
     {
     case SDL_BUTTON_MIDDLE:
-      return FlexiblePointerTrackerPtr(new PanSceneTracker
+      return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker
         (controller_, e));
 
     case SDL_BUTTON_RIGHT:
-      return FlexiblePointerTrackerPtr(new ZoomSceneTracker
+      return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker
         (controller_, e, compositor_->GetCanvasHeight()));
 
     case SDL_BUTTON_LEFT:
@@ -300,7 +407,7 @@
 
       // TODO: if there are conflicts, we should prefer a tracker that 
       // pertains to the type of measuring tool currently selected (TBD?)
-      FlexiblePointerTrackerPtr hitTestTracker = TrackerHitTest(e);
+      boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e);
 
       if (hitTestTracker != NULL)
       {
@@ -313,13 +420,13 @@
         {
         case GuiTool_Rotate:
           //LOG(TRACE) << "Creating RotateSceneTracker";
-          return FlexiblePointerTrackerPtr(new RotateSceneTracker(
+          return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker(
             controller_, e));
         case GuiTool_Pan:
-          return FlexiblePointerTrackerPtr(new PanSceneTracker(
+          return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker(
             controller_, e));
         case GuiTool_Zoom:
-          return FlexiblePointerTrackerPtr(new ZoomSceneTracker(
+          return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker(
             controller_, e, compositor_->GetCanvasHeight()));
         //case GuiTool_AngleMeasure:
         //  return new AngleMeasureTracker(GetScene(), e);
@@ -328,32 +435,34 @@
         //case GuiTool_EllipseMeasure:
         //  return new EllipseMeasureTracker(GetScene(), e);
         case GuiTool_LineMeasure:
-          return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker(
+          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker(
             IObserver::GetBroker(), controller_, e));
         case GuiTool_AngleMeasure:
-          return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker(
+          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker(
             IObserver::GetBroker(), controller_, e));
         case GuiTool_CircleMeasure:
           LOG(ERROR) << "Not implemented yet!";
-          return FlexiblePointerTrackerPtr();
+          return boost::shared_ptr<IFlexiblePointerTracker>();
         case GuiTool_EllipseMeasure:
           LOG(ERROR) << "Not implemented yet!";
-          return FlexiblePointerTrackerPtr();
+          return boost::shared_ptr<IFlexiblePointerTracker>();
         default:
           throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
         }
       }
     }
     default:
-      return FlexiblePointerTrackerPtr();
+      return boost::shared_ptr<IFlexiblePointerTracker>();
     }
   }
 
 
   TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker)
     , currentTool_(GuiTool_Rotate)
+    , undoStack_(new UndoStack)
   {
-    controller_ = ViewportControllerPtr(new ViewportController(broker));
+    controller_ = boost::shared_ptr<ViewportController>(
+      new ViewportController(undoStack_, broker));
 
     controller_->RegisterObserverCallback(
       new Callable<TrackerSampleApp, ViewportController::SceneTransformChanged>
@@ -427,14 +536,14 @@
       chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
       chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
       chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
-      layer->AddChain(chain, true);
+      layer->AddChain(chain, true, 255, 0, 0);
 
       chain.clear();
       chain.push_back(ScenePoint2D(-5, -5));
       chain.push_back(ScenePoint2D(5, -5));
       chain.push_back(ScenePoint2D(5, 5));
       chain.push_back(ScenePoint2D(-5, 5));
-      layer->AddChain(chain, true);
+      layer->AddChain(chain, true, 0, 255, 0);
 
       double dy = 1.01;
       chain.clear();
@@ -442,9 +551,8 @@
       chain.push_back(ScenePoint2D(4, -4 + dy));
       chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
       chain.push_back(ScenePoint2D(4, 2));
-      layer->AddChain(chain, false);
+      layer->AddChain(chain, false, 0, 0, 255);
 
-      layer->SetColor(0, 255, 255);
       GetScene()->SetLayer(LINESET_1_ZINDEX, layer.release());
     }
 
@@ -485,10 +593,10 @@
   }
 
 
-  FlexiblePointerTrackerPtr TrackerSampleApp::TrackerHitTest(const PointerEvent & e)
+  boost::shared_ptr<IFlexiblePointerTracker> TrackerSampleApp::TrackerHitTest(const PointerEvent & e)
   {
-    // std::vector<MeasureToolPtr> measureTools_;
-    return FlexiblePointerTrackerPtr();
+    // std::vector<boost::shared_ptr<MeasureTool>> measureTools_;
+    return boost::shared_ptr<IFlexiblePointerTracker>();
   }
 
   static void GLAPIENTRY
--- a/Samples/Sdl/TrackerSampleApp.h	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Mon Jun 24 14:35:00 2019 +0200
@@ -18,15 +18,13 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-#include <Framework/Scene2DViewport/PointerTypes.h>
 
-#include <Framework/Messages/IObserver.h>
-
-#include <Framework/Scene2D/OpenGLCompositor.h>
-
-#include <Framework/Scene2DViewport/ViewportController.h>
-#include <Framework/Scene2DViewport/IFlexiblePointerTracker.h>
-#include <Framework/Scene2DViewport/MeasureTools.h>
+#include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h"
+#include "../../Framework/Scene2DViewport/MeasureTool.h"
+#include "../../Framework/Scene2DViewport/PredeclaredTypes.h"
+#include "../../Framework/Scene2DViewport/ViewportController.h"
 
 #include <SDL.h>
 
@@ -54,6 +52,7 @@
   static const unsigned int FONT_SIZE_1 = 24;
 
   class Scene2D;
+  class UndoStack;
 
   class TrackerSampleApp : public IObserver
     , public boost::enable_shared_from_this<TrackerSampleApp>
@@ -66,7 +65,8 @@
     void SetInfoDisplayMessage(std::string key, std::string value);
     void DisableTracker();
 
-    Scene2DPtr GetScene();
+    boost::shared_ptr<Scene2D> GetScene();
+    boost::shared_ptr<const Scene2D> GetScene() const;
 
     void HandleApplicationEvent(const SDL_Event& event);
 
@@ -79,11 +79,17 @@
 
   private:
     void SelectNextTool();
-
+    void CreateRandomMeasureTool();
 
-    FlexiblePointerTrackerPtr TrackerHitTest(const PointerEvent& e);
+    /**
+    This returns a random point in the canvas part of the scene, but in
+    scene coordinates
+    */
+    ScenePoint2D GetRandomPointInScene() const;
 
-    FlexiblePointerTrackerPtr CreateSuitableTracker(
+    boost::shared_ptr<IFlexiblePointerTracker> TrackerHitTest(const PointerEvent& e);
+
+    boost::shared_ptr<IFlexiblePointerTracker> CreateSuitableTracker(
       const SDL_Event& event,
       const PointerEvent& e);
 
@@ -95,7 +101,7 @@
     /**
       This adds the command at the top of the undo stack
     */
-    void Commit(TrackerCommandPtr cmd);
+    void Commit(boost::shared_ptr<TrackerCommand> cmd);
     void Undo();
     void Redo();
 
@@ -110,10 +116,10 @@
     WARNING: the measuring tools do store a reference to the scene, and it 
     paramount that the scene gets destroyed AFTER the measurement tools.
     */
-    ViewportControllerPtr controller_;
+    boost::shared_ptr<ViewportController> controller_;
 
     std::map<std::string, std::string> infoTextMap_;
-    FlexiblePointerTrackerPtr activeTracker_;
+    boost::shared_ptr<IFlexiblePointerTracker> activeTracker_;
 
     //static const int LAYER_POSITION = 150;
 
@@ -126,6 +132,7 @@
     int FIXED_INFOTEXT_LAYER_ZINDEX;
 
     GuiTool currentTool_;
+    boost::shared_ptr<UndoStack> undoStack_;
   };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/WebAssembly/BasicMPR.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,427 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#include "dev.h"
+
+#include <emscripten.h>
+
+#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../Framework/Oracle/SleepOracleCommand.h"
+#include "../../Framework/Oracle/WebAssemblyOracle.h"
+#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../Framework/StoneInitialization.h"
+#include "../../Framework/Volumes/VolumeSceneLayerSource.h"
+
+
+namespace OrthancStone
+{
+  class VolumeSlicerWidget : public IObserver
+  {
+  private:
+    OrthancStone::WebAssemblyViewport      viewport_;
+    std::auto_ptr<VolumeSceneLayerSource>  source_;
+    VolumeProjection                       projection_;
+    std::vector<CoordinateSystem3D>        planes_;
+    size_t                                 currentPlane_;
+
+    void Handle(const DicomVolumeImage::GeometryReadyMessage& message)
+    {
+      LOG(INFO) << "Geometry is available";
+
+      const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry();
+
+      const unsigned int depth = geometry.GetProjectionDepth(projection_);
+      currentPlane_ = depth / 2;
+      
+      planes_.resize(depth);
+
+      for (unsigned int z = 0; z < depth; z++)
+      {
+        planes_[z] = geometry.GetProjectionSlice(projection_, z);
+      }
+
+      Refresh();
+
+      viewport_.FitContent();
+    }
+    
+  public:
+    VolumeSlicerWidget(MessageBroker& broker,
+                         const std::string& canvas,
+                         VolumeProjection projection) :
+      IObserver(broker),
+      viewport_(broker, canvas),
+      projection_(projection),
+      currentPlane_(0)
+    {
+    }
+
+    void UpdateSize()
+    {
+      viewport_.UpdateSize();
+    }
+
+    void SetSlicer(int layerDepth,
+                   const boost::shared_ptr<IVolumeSlicer>& slicer,
+                   IObservable& loader,
+                   ILayerStyleConfigurator* configurator)
+    {
+      if (source_.get() != NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "Only one slicer can be registered");
+      }
+      
+      loader.RegisterObserverCallback(
+        new Callable<VolumeSlicerWidget, DicomVolumeImage::GeometryReadyMessage>
+        (*this, &VolumeSlicerWidget::Handle));
+
+      source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer));
+
+      if (configurator != NULL)
+      {
+        source_->SetConfigurator(configurator);
+      }
+    }    
+
+    void Refresh()
+    {
+      if (source_.get() != NULL &&
+          currentPlane_ < planes_.size())
+      {
+        source_->Update(planes_[currentPlane_]);
+        viewport_.Refresh();
+      }
+    }
+
+    size_t GetSlicesCount() const
+    {
+      return planes_.size();
+    }
+
+    void Scroll(int delta)
+    {
+      if (!planes_.empty())
+      {
+        int tmp = static_cast<int>(currentPlane_) + delta;
+        unsigned int next;
+
+        if (tmp < 0)
+        {
+          next = 0;
+        }
+        else if (tmp >= static_cast<int>(planes_.size()))
+        {
+          next = planes_.size() - 1;
+        }
+        else
+        {
+          next = static_cast<size_t>(tmp);
+        }
+
+        if (next != currentPlane_)
+        {
+          currentPlane_ = next;
+          Refresh();
+        }
+      }
+    }
+  };
+}
+
+
+
+
+boost::shared_ptr<OrthancStone::DicomVolumeImage>  ct_(new OrthancStone::DicomVolumeImage);
+
+boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader>  loader_;
+
+std::auto_ptr<OrthancStone::VolumeSlicerWidget>  widget1_;
+std::auto_ptr<OrthancStone::VolumeSlicerWidget>  widget2_;
+std::auto_ptr<OrthancStone::VolumeSlicerWidget>  widget3_;
+
+OrthancStone::MessageBroker  broker_;
+OrthancStone::WebAssemblyOracle  oracle_(broker_);
+
+
+EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
+{
+  try
+  {
+    if (widget1_.get() != NULL)
+    {
+      widget1_->UpdateSize();
+    }
+  
+    if (widget2_.get() != NULL)
+    {
+      widget2_->UpdateSize();
+    }
+  
+    if (widget3_.get() != NULL)
+    {
+      widget3_->UpdateSize();
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception while updating canvas size: " << e.What();
+  }
+  
+  return true;
+}
+
+
+
+
+EM_BOOL OnAnimationFrame(double time, void *userData)
+{
+  try
+  {
+    if (widget1_.get() != NULL)
+    {
+      widget1_->Refresh();
+    }
+  
+    if (widget2_.get() != NULL)
+    {
+      widget2_->Refresh();
+    }
+  
+    if (widget3_.get() != NULL)
+    {
+      widget3_->Refresh();
+    }
+
+    return true;
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What();
+    return false;
+  }  
+}
+
+
+static bool ctrlDown_ = false;
+
+
+EM_BOOL OnMouseWheel(int eventType,
+                     const EmscriptenWheelEvent *wheelEvent,
+                     void *userData)
+{
+  try
+  {
+    if (userData != NULL)
+    {
+      int delta = 0;
+
+      if (wheelEvent->deltaY < 0)
+      {
+        delta = -1;
+      }
+           
+      if (wheelEvent->deltaY > 0)
+      {
+        delta = 1;
+      }
+
+      OrthancStone::VolumeSlicerWidget& widget =
+        *reinterpret_cast<OrthancStone::VolumeSlicerWidget*>(userData);
+      
+      if (ctrlDown_)
+      {
+        delta *= static_cast<int>(widget.GetSlicesCount() / 10);
+      }
+
+      widget.Scroll(delta);
+    }
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "Exception in the wheel event: " << e.What();
+  }
+  
+  return true;
+}
+
+
+EM_BOOL OnKeyDown(int eventType,
+                  const EmscriptenKeyboardEvent *keyEvent,
+                  void *userData)
+{
+  ctrlDown_ = keyEvent->ctrlKey;
+  return false;
+}
+
+
+EM_BOOL OnKeyUp(int eventType,
+                const EmscriptenKeyboardEvent *keyEvent,
+                void *userData)
+{
+  ctrlDown_ = false;
+  return false;
+}
+
+
+
+
+namespace OrthancStone
+{
+  class TestSleep : public IObserver
+  {
+  private:
+    WebAssemblyOracle&  oracle_;
+
+    void Schedule()
+    {
+      oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000));
+    }
+    
+    void Handle(const SleepOracleCommand::TimeoutMessage& message)
+    {
+      LOG(INFO) << "TIMEOUT";
+      Schedule();
+    }
+    
+  public:
+    TestSleep(MessageBroker& broker,
+              WebAssemblyOracle& oracle) :
+      IObserver(broker),
+      oracle_(oracle)
+    {
+      oracle.RegisterObserverCallback(
+        new Callable<TestSleep, SleepOracleCommand::TimeoutMessage>
+        (*this, &TestSleep::Handle));
+
+      LOG(INFO) << "STARTING";
+      Schedule();
+    }
+  };
+
+  //static TestSleep testSleep(broker_, oracle_);
+}
+
+
+
+static std::map<std::string, std::string> arguments_;
+
+static bool GetArgument(std::string& value,
+                        const std::string& key)
+{
+  std::map<std::string, std::string>::const_iterator found = arguments_.find(key);
+
+  if (found == arguments_.end())
+  {
+    return false;
+  }
+  else
+  {
+    value = found->second;
+    return true;
+  }
+}
+
+
+extern "C"
+{
+  int main(int argc, char const *argv[]) 
+  {
+    OrthancStone::StoneInitialize();
+    Orthanc::Logging::EnableInfoLevel(true);
+    // Orthanc::Logging::EnableTraceLevel(true);
+    EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded")););
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetArgument(const char* key, const char* value)
+  {
+    // This is called for each GET argument (cf. "app.js")
+    LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]";
+    arguments_[key] = value;
+  }
+
+  EMSCRIPTEN_KEEPALIVE
+  void Initialize()
+  {
+    try
+    {
+      oracle_.SetOrthancRoot("..");
+      
+      loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_));
+    
+      widget1_.reset(new OrthancStone::VolumeSlicerWidget(broker_, "mycanvas1", OrthancStone::VolumeProjection_Axial));
+      {
+        std::auto_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
+        style->SetLinearInterpolation(true);
+        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
+        widget1_->SetSlicer(0, loader_, *loader_, style.release());
+      }
+      widget1_->UpdateSize();
+
+      widget2_.reset(new OrthancStone::VolumeSlicerWidget(broker_, "mycanvas2", OrthancStone::VolumeProjection_Coronal));
+      {
+        std::auto_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
+        style->SetLinearInterpolation(true);
+        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
+        widget2_->SetSlicer(0, loader_, *loader_, style.release());
+      }
+      widget2_->UpdateSize();
+
+      widget3_.reset(new OrthancStone::VolumeSlicerWidget(broker_, "mycanvas3", OrthancStone::VolumeProjection_Sagittal));
+      {
+        std::auto_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator);
+        style->SetLinearInterpolation(true);
+        style->SetWindowing(OrthancStone::ImageWindowing_Bone);
+        widget3_->SetSlicer(0, loader_, *loader_, style.release());
+      }
+      widget3_->UpdateSize();
+
+      emscripten_set_resize_callback("#window", NULL, false, OnWindowResize);
+
+      emscripten_set_wheel_callback("mycanvas1", widget1_.get(), false, OnMouseWheel);
+      emscripten_set_wheel_callback("mycanvas2", widget2_.get(), false, OnMouseWheel);
+      emscripten_set_wheel_callback("mycanvas3", widget3_.get(), false, OnMouseWheel);
+
+      emscripten_set_keydown_callback("#window", NULL, false, OnKeyDown);
+      emscripten_set_keyup_callback("#window", NULL, false, OnKeyUp);
+    
+      emscripten_request_animation_frame_loop(OnAnimationFrame, NULL);
+
+
+      std::string ct;
+      if (GetArgument(ct, "ct"))
+      {
+        //loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");
+        loader_->LoadSeries(ct);
+      }
+      else
+      {
+        LOG(ERROR) << "No Orthanc identifier for the CT series was provided";
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception during Initialize(): " << e.What();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/WebAssembly/BasicMPR.html	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,60 @@
+<!doctype html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+    <!-- Disable pinch zoom on mobile devices -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="HandheldFriendly" content="true" />
+    
+    
+    <title>Stone of Orthanc</title>
+
+    <style>
+      html, body {
+      width: 100%;
+      height: 100%;
+      margin: 0px;
+      border: 0;
+      overflow: hidden; /*  Disable scrollbars */
+      display: block;  /* No floating content on sides */
+      }
+
+      #mycanvas1 {
+      position:absolute;
+      left:0%;
+      top:0%;
+      background-color: red;
+      width: 50%;
+      height: 100%;
+      }
+
+      #mycanvas2 {
+      position:absolute;
+      left:50%;
+      top:0%;
+      background-color: green;
+      width: 50%;
+      height: 50%;
+      }
+
+      #mycanvas3 {
+      position:absolute;
+      left:50%;
+      top:50%;
+      background-color: blue;
+      width: 50%;
+      height: 50%;
+      }
+    </style>
+  </head>
+  <body>
+    <canvas id="mycanvas1" oncontextmenu="return false;"></canvas>
+    <canvas id="mycanvas2" oncontextmenu="return false;"></canvas>
+    <canvas id="mycanvas3" oncontextmenu="return false;"></canvas>
+
+    <script type="text/javascript" src="app.js"></script>
+    <script type="text/javascript" async src="BasicMPR.js"></script>
+  </body>
+</html>
--- a/Samples/WebAssembly/BasicScene.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/WebAssembly/BasicScene.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,32 +19,20 @@
  **/
 
 
+#include "dev.h"
 
 #include <emscripten.h>
 #include <emscripten/html5.h>
 
 // From Stone
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
-#include "../../Framework/Scene2D/CairoCompositor.h"
 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Framework/Scene2D/OpenGLCompositor.h"
-#include "../../Framework/Scene2D/PanSceneTracker.h"
-#include "../../Framework/Scene2D/RotateSceneTracker.h"
-#include "../../Framework/Scene2D/Scene2D.h"
-#include "../../Framework/Scene2D/ZoomSceneTracker.h"
 #include "../../Framework/StoneInitialization.h"
-#include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h"
 
 // From Orthanc framework
 #include <Core/Images/Image.h>
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 
-#include <stdio.h>
-
-static const unsigned int FONT_SIZE = 32;
-
-
 void PrepareScene(OrthancStone::Scene2D& scene)
 {
   using namespace OrthancStone;
@@ -108,14 +96,14 @@
     chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
     chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
     chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
-    layer->AddChain(chain, true);
+    layer->AddChain(chain, true, 255, 0, 0);
 
     chain.clear();
     chain.push_back(ScenePoint2D(-5, -5));
     chain.push_back(ScenePoint2D(5, -5));
     chain.push_back(ScenePoint2D(5, 5));
     chain.push_back(ScenePoint2D(-5, 5));
-    layer->AddChain(chain, true);
+    layer->AddChain(chain, true, 0, 255, 0);
 
     double dy = 1.01;
     chain.clear();
@@ -123,9 +111,8 @@
     chain.push_back(ScenePoint2D(4, -4 + dy));
     chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
     chain.push_back(ScenePoint2D(4, 2));
-    layer->AddChain(chain, false);
+    layer->AddChain(chain, false, 0, 0, 255);
 
-    layer->SetColor(0,255, 255);
     scene.SetLayer(50, layer.release());
   }
 
@@ -139,234 +126,13 @@
 }
 
 
-
-
-namespace OrthancStone
-{
-  class WebAssemblyViewport : public boost::noncopyable
-  {
-  private:
-    OpenGL::WebAssemblyOpenGLContext  context_;
-    Scene2D                           scene_;
-    OpenGLCompositor                  compositor_;
-
-    void SetupEvents(const std::string& canvas);
-
-  public:
-    WebAssemblyViewport(MessageBroker& broker,
-                        const std::string& canvas) :
-      context_(canvas),
-      scene_(broker),
-      compositor_(context_, scene_)
-    {
-      compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
-                          FONT_SIZE, Orthanc::Encoding_Latin1);
-      SetupEvents(canvas);
-    }
-
-    Scene2D& GetScene()
-    {
-      return scene_;
-    }
-
-    void UpdateSize()
-    {
-      context_.UpdateSize();
-      compositor_.UpdateSize();
-      Refresh();
-    }
-
-    void Refresh()
-    {
-      compositor_.Refresh();
-    }
-
-    const std::string& GetCanvasIdentifier() const
-    {
-      return context_.GetCanvasIdentifier();
-    }
-
-    ScenePoint2D GetPixelCenterCoordinates(int x, int y) const
-    {
-      return compositor_.GetPixelCenterCoordinates(x, y);
-    }
-
-    unsigned int GetCanvasWidth() const
-    {
-      return context_.GetCanvasWidth();
-    }
-
-    unsigned int GetCanvasHeight() const
-    {
-      return context_.GetCanvasHeight();
-    }
-  };
-
-
-
-  class ActiveTracker : public boost::noncopyable
-  {
-  private:
-    std::auto_ptr<IPointerTracker>  tracker_;
-    std::string                     canvasIdentifier_;
-    bool                            insideCanvas_;
-    
-  public:
-    ActiveTracker(IPointerTracker* tracker,
-                  const WebAssemblyViewport& viewport) :
-      tracker_(tracker),
-      canvasIdentifier_(viewport.GetCanvasIdentifier()),
-      insideCanvas_(true)
-    {
-      if (tracker_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-    }
-
-    void Update(const PointerEvent& event)
-    {
-      tracker_->Update(event);
-    }
-
-    void Release()
-    {
-      tracker_->Release();
-    }
-  };
-}
-
-
-
-static OrthancStone::PointerEvent* ConvertMouseEvent(const EmscriptenMouseEvent& source,
-                                                     OrthancStone::WebAssemblyViewport& viewport)
-{
-  std::auto_ptr<OrthancStone::PointerEvent> target(new OrthancStone::PointerEvent);
-
-  target->AddPosition(viewport.GetPixelCenterCoordinates(source.targetX, source.targetY));
-  target->SetAltModifier(source.altKey);
-  target->SetControlModifier(source.ctrlKey);
-  target->SetShiftModifier(source.shiftKey);
-
-  return target.release();
-}
-
-
-std::auto_ptr<OrthancStone::ActiveTracker>      tracker_;
-
-
-EM_BOOL OnMouseEvent(int eventType, 
-                     const EmscriptenMouseEvent *mouseEvent, 
-                     void *userData)
-{
-  if (mouseEvent != NULL &&
-      userData != NULL)
-  {
-    OrthancStone::WebAssemblyViewport& viewport = 
-      *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData);
-
-    switch (eventType)
-    {
-      case EMSCRIPTEN_EVENT_CLICK:
-      {
-        static unsigned int count = 0;
-        char buf[64];
-        sprintf(buf, "click %d", count++);
-
-        std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
-        layer->SetText(buf);
-        viewport.GetScene().SetLayer(100, layer.release());
-        viewport.Refresh();
-        break;
-      }
-
-      case EMSCRIPTEN_EVENT_MOUSEDOWN:
-      {
-        std::auto_ptr<OrthancStone::IPointerTracker> t;
-
-        {
-          std::auto_ptr<OrthancStone::PointerEvent> event(ConvertMouseEvent(*mouseEvent, viewport));
-
-          switch (mouseEvent->button)
-          {
-            case 0:  // Left button
-              t.reset(new OrthancStone::RotateSceneTracker(viewport.GetScene(), *event));
-              break;
-
-            case 1:  // Middle button
-              t.reset(new OrthancStone::PanSceneTracker(viewport.GetScene(), *event));
-              break;
-
-            case 2:  // Right button
-              t.reset(new OrthancStone::ZoomSceneTracker
-                      (viewport.GetScene(), *event, viewport.GetCanvasWidth()));
-              break;
-
-            default:
-              break;
-          }
-        }
-
-        if (t.get() != NULL)
-        {
-          tracker_.reset(new OrthancStone::ActiveTracker(t.release(), viewport));
-          viewport.Refresh();
-        }
-
-        break;
-      }
-
-      case EMSCRIPTEN_EVENT_MOUSEMOVE:
-        if (tracker_.get() != NULL)
-        {
-          std::auto_ptr<OrthancStone::PointerEvent> event(ConvertMouseEvent(*mouseEvent, viewport));
-          tracker_->Update(*event);
-          viewport.Refresh();
-        }
-        break;
-
-      case EMSCRIPTEN_EVENT_MOUSEUP:
-        if (tracker_.get() != NULL)
-        {
-          tracker_->Release();
-          viewport.Refresh();
-          tracker_.reset();
-        }
-        break;
-
-      default:
-        break;
-    }
-  }
-
-  return true;
-}
-
-
-void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas)
-{
-  if (0)
-  {
-    emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent);
-  }
-  else
-  {
-    emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent);
-    emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent);
-    emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent);
-  }
-}
-
-
-
-
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport1_;
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport2_;
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport3_;
 OrthancStone::MessageBroker  broker_;
 
-
-EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
+EM_BOOL OnWindowResize(
+  int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
 {
   if (viewport1_.get() != NULL)
   {
@@ -386,28 +152,31 @@
   return true;
 }
 
-
-
 extern "C"
 {
   int main(int argc, char const *argv[]) 
   {
     OrthancStone::StoneInitialize();
+    // Orthanc::Logging::EnableInfoLevel(true);
+    // Orthanc::Logging::EnableTraceLevel(true);
     EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded")););
   }
 
   EMSCRIPTEN_KEEPALIVE
   void Initialize()
   {
-    viewport1_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1"));
+    viewport1_.reset(
+      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1"));
     PrepareScene(viewport1_->GetScene());
     viewport1_->UpdateSize();
 
-    viewport2_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2"));
+    viewport2_.reset(
+      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2"));
     PrepareScene(viewport2_->GetScene());
     viewport2_->UpdateSize();
 
-    viewport3_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3"));
+    viewport3_.reset(
+      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3"));
     PrepareScene(viewport3_->GetScene());
     viewport3_->UpdateSize();
 
--- a/Samples/WebAssembly/CMakeLists.txt	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/WebAssembly/CMakeLists.txt	Mon Jun 24 14:35:00 2019 +0200
@@ -13,6 +13,7 @@
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXIT_RUNTIME=1")
 
 
 #####################################################################
@@ -59,6 +60,10 @@
 
 include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake)
 
+add_definitions(
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
+
 
 #####################################################################
 ## Build the samples
@@ -68,22 +73,43 @@
   ${ORTHANC_STONE_SOURCES}
   )
 
-add_executable(BasicScene
-  BasicScene.cpp
-  )
+
+if (ON)
+  add_executable(BasicScene
+    BasicScene.cpp
+    )
+
+  target_link_libraries(BasicScene OrthancStone)
+
+  install(
+    TARGETS BasicScene
+    RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
+    )
+endif()
 
-target_link_libraries(BasicScene OrthancStone)
+
+if (ON)
+  add_executable(BasicMPR
+    BasicMPR.cpp
+    )
 
-install(
-  TARGETS BasicScene
-  RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
-  )
+  target_link_libraries(BasicMPR OrthancStone)
+
+  install(
+    TARGETS BasicMPR
+    RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
+    )
+endif()
+  
 
 install(
   FILES
+  ${CMAKE_CURRENT_BINARY_DIR}/BasicMPR.wasm
   ${CMAKE_CURRENT_BINARY_DIR}/BasicScene.wasm
+  ${CMAKE_SOURCE_DIR}/BasicMPR.html
   ${CMAKE_SOURCE_DIR}/BasicScene.html
   ${CMAKE_SOURCE_DIR}/Configuration.json
+  ${CMAKE_SOURCE_DIR}/app.js
   ${CMAKE_SOURCE_DIR}/index.html
   DESTINATION ${CMAKE_INSTALL_PREFIX}
   )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/WebAssembly/ConfigurationLocalSJO.json	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,20 @@
+{
+  "Plugins": [
+    "/home/jodogne/Subversion/orthanc-webviewer/r/libOrthancWebViewer.so",
+    "/home/jodogne/Subversion/orthanc/r/libServeFolders.so"
+  ],
+  "StorageDirectory" : "/tmp/orthanc-db",
+  "IndexDirectory" : "/tmp/orthanc-db",
+  "RemoteAccessAllowed" : true,
+  "AuthenticationEnabled" : false,
+  "ServeFolders" : {
+    "AllowCache" : false,
+    "GenerateETag" : true,
+    "Folders" : {
+      "/stone" : "/tmp/stone"
+    }
+  },
+  "WebViewer" : {
+    "CachePath" : "/tmp/orthanc-db/WebViewerCache"
+  }
+}
--- a/Samples/WebAssembly/NOTES.txt	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/WebAssembly/NOTES.txt	Mon Jun 24 14:35:00 2019 +0200
@@ -1,4 +1,65 @@
+Docker SJO
+==========
+
 $ source ~/Downloads/emsdk/emsdk_env.sh
 $ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone
 $ ninja install
-$ sudo docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose
+$ docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro -v /tmp/stone-db/:/var/lib/orthanc/db/ jodogne/orthanc-plugins:latest /root/stone/Configuration.json --verbose
+
+WARNING: This won't work using "orthanc-plugins:1.5.6", as support for
+PAM is mandatatory in "/instances/.../image-uint16".
+
+
+Docker BGO
+==========
+
+On Ubuntu WSL
+-------------
+. ~/apps/emsdk/emsdk_env.sh
+cd /mnt/c/osi/dev/
+mkdir -p build_stone_newsamples_wasm_wsl
+mkdir -p build_install_stone_newsamples_wasm_wsl
+cd build_stone_newsamples_wasm_wsl
+cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/osi/dev/build_install_stone_newsamples_wasm_wsl
+ninja install
+
+Then, on Windows
+-----------------
+docker run -p 4242:4242 -p 8042:8042 --rm -v "C:/osi/dev/build_install_stone_newsamples_wasm_wsl:/root/stone:ro" jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose
+
+# WAIT A COUPLE OF SECS
+# if the archive has NOT already been unzipped, unzip it
+# upload dicom files to running orthanc
+
+cd C:\osi\dev\twiga-orthanc-viewer\demo\dicomfiles
+if (-not (test-path RTVIEWER-c8febcc6-eb9e22a4-130f208c-e0a6a4cd-4d432c57)) { unzip RTVIEWER-c8febcc6-eb9e22a4-130f208c-e0a6a4cd-4d432c57.zip}
+ImportDicomFiles.ps1 127.0.0.1 8042 .\RTVIEWER-c8febcc6-eb9e22a4-130f208c-e0a6a4cd-4d432c57\
+
+--> localhost:8042 --> Plugins --> serve-folders --> stone --> ...
+
+Local BGO
+==========
+
+. ~/apps/emsdk/emsdk_env.sh
+cd /mnt/c/osi/dev/
+mkdir -p build_stone_newsamples_wasm_wsl
+mkdir -p build_install_stone_newsamples_wasm_wsl
+cd build_stone_newsamples_wasm_wsl
+cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/osi/dev/build_install_stone_newsamples_wasm_wsl
+
+
+
+TODO: Orthanc.exe 
+
+
+Local SJO
+==========
+
+$ source ~/Downloads/emsdk/emsdk_env.sh
+$ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone
+$ ninja install
+
+$ make -C ~/Subversion/orthanc/r -j4
+$ make -C ~/Subversion/orthanc-webviewer/r -j4
+$ ~/Subversion/orthanc/r/Orthanc ../ConfigurationLocalSJO.json
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/WebAssembly/app.js	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,33 @@
+/**
+ * This is a generic bootstrap code that is shared by all the Stone
+ * sample applications.
+ **/
+
+// Check support for WebAssembly
+if (!('WebAssembly' in window)) {
+  alert('Sorry, your browser does not support WebAssembly :(');
+} else {
+
+  // Wait for the module to be loaded (the event "WebAssemblyLoaded"
+  // must be emitted by the "main" function)
+  window.addEventListener('WebAssemblyLoaded', function() {
+
+    // Loop over the GET arguments
+    var parameters = window.location.search.substr(1);
+    if (parameters != null && parameters != '') {
+      var tokens = parameters.split('&');
+      for (var i = 0; i < tokens.length; i++) {
+        var arg = tokens[i].split('=');
+        if (arg.length == 2) {
+
+          // Send each GET argument to WebAssembly
+          Module.ccall('SetArgument', null, [ 'string', 'string' ],
+                       [ arg[0], decodeURIComponent(arg[1]) ]);
+        }
+      }
+    }
+
+    // Inform the WebAssembly module that it can start
+    Module.ccall('Initialize', null, null, null);
+  });
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/WebAssembly/dev.h	Mon Jun 24 14:35:00 2019 +0200
@@ -0,0 +1,271 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h"
+#include "../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../Framework/Scene2D/PanSceneTracker.h"
+#include "../../Framework/Scene2D/RotateSceneTracker.h"
+#include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2DViewport/ViewportController.h"
+
+#include <Core/OrthancException.h>
+
+#include <emscripten/html5.h>
+
+static const unsigned int FONT_SIZE = 32;
+
+namespace OrthancStone
+{
+  class WebAssemblyViewport : public boost::noncopyable
+  {
+  private:
+    // the construction order is important because compositor_
+    // will hold a reference to the scene that belong to the 
+    // controller_ object
+    OpenGL::WebAssemblyOpenGLContext       context_;
+    boost::shared_ptr<ViewportController>  controller_;
+    OpenGLCompositor                       compositor_;
+
+    void SetupEvents(const std::string& canvas);
+
+  public:
+    WebAssemblyViewport(MessageBroker& broker,
+                        const std::string& canvas) :
+      context_(canvas),
+      controller_(new ViewportController(broker)),
+      compositor_(context_, *controller_->GetScene())
+    {
+      compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
+                          FONT_SIZE, Orthanc::Encoding_Latin1);
+      SetupEvents(canvas);
+    }
+
+    Scene2D& GetScene()
+    {
+      return *controller_->GetScene();
+    }
+
+    const boost::shared_ptr<ViewportController>& GetController()
+    {
+      return controller_;
+    }
+
+    void UpdateSize()
+    {
+      context_.UpdateSize();
+      compositor_.UpdateSize();
+      Refresh();
+    }
+
+    void Refresh()
+    {
+      compositor_.Refresh();
+    }
+
+    void FitContent()
+    {
+      GetScene().FitContent(context_.GetCanvasWidth(), context_.GetCanvasHeight());
+    }
+
+    const std::string& GetCanvasIdentifier() const
+    {
+      return context_.GetCanvasIdentifier();
+    }
+
+    ScenePoint2D GetPixelCenterCoordinates(int x, int y) const
+    {
+      return compositor_.GetPixelCenterCoordinates(x, y);
+    }
+
+    unsigned int GetCanvasWidth() const
+    {
+      return context_.GetCanvasWidth();
+    }
+
+    unsigned int GetCanvasHeight() const
+    {
+      return context_.GetCanvasHeight();
+    }
+  };
+
+  class ActiveTracker : public boost::noncopyable
+  {
+  private:
+    boost::shared_ptr<IFlexiblePointerTracker> tracker_;
+    std::string                             canvasIdentifier_;
+    bool                                    insideCanvas_;
+    
+  public:
+    ActiveTracker(const boost::shared_ptr<IFlexiblePointerTracker>& tracker,
+                  const WebAssemblyViewport& viewport) :
+      tracker_(tracker),
+      canvasIdentifier_(viewport.GetCanvasIdentifier()),
+      insideCanvas_(true)
+    {
+      if (tracker_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    bool IsAlive() const
+    {
+      return tracker_->IsAlive();
+    }
+
+    void PointerMove(const PointerEvent& event)
+    {
+      tracker_->PointerMove(event);
+    }
+
+    void PointerUp(const PointerEvent& event)
+    {
+      tracker_->PointerUp(event);
+    }
+  };
+}
+
+static OrthancStone::PointerEvent* ConvertMouseEvent(
+  const EmscriptenMouseEvent&        source,
+  OrthancStone::WebAssemblyViewport& viewport)
+{
+  std::auto_ptr<OrthancStone::PointerEvent> target(
+    new OrthancStone::PointerEvent);
+
+  target->AddPosition(viewport.GetPixelCenterCoordinates(
+    source.targetX, source.targetY));
+  target->SetAltModifier(source.altKey);
+  target->SetControlModifier(source.ctrlKey);
+  target->SetShiftModifier(source.shiftKey);
+
+  return target.release();
+}
+
+std::auto_ptr<OrthancStone::ActiveTracker> tracker_;
+
+EM_BOOL OnMouseEvent(int eventType, 
+                     const EmscriptenMouseEvent *mouseEvent, 
+                     void *userData)
+{
+  if (mouseEvent != NULL &&
+      userData != NULL)
+  {
+    OrthancStone::WebAssemblyViewport& viewport = 
+      *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData);
+
+    switch (eventType)
+    {
+      case EMSCRIPTEN_EVENT_CLICK:
+      {
+        static unsigned int count = 0;
+        char buf[64];
+        sprintf(buf, "click %d", count++);
+
+        std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
+        layer->SetText(buf);
+        viewport.GetScene().SetLayer(100, layer.release());
+        viewport.Refresh();
+        break;
+      }
+
+      case EMSCRIPTEN_EVENT_MOUSEDOWN:
+      {
+        boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t;
+
+        {
+          std::auto_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, viewport));
+
+          switch (mouseEvent->button)
+          {
+            case 0:  // Left button
+              emscripten_console_log("Creating RotateSceneTracker");
+              t.reset(new OrthancStone::RotateSceneTracker(
+                viewport.GetController(), *event));
+              break;
+
+            case 1:  // Middle button
+              emscripten_console_log("Creating PanSceneTracker");
+              LOG(INFO) << "Creating PanSceneTracker" ;
+              t.reset(new OrthancStone::PanSceneTracker(
+                viewport.GetController(), *event));
+              break;
+
+            case 2:  // Right button
+              emscripten_console_log("Creating ZoomSceneTracker");
+              t.reset(new OrthancStone::ZoomSceneTracker(
+                viewport.GetController(), *event, viewport.GetCanvasWidth()));
+              break;
+
+            default:
+              break;
+          }
+        }
+
+        if (t.get() != NULL)
+        {
+          tracker_.reset(
+            new OrthancStone::ActiveTracker(t, viewport));
+          viewport.Refresh();
+        }
+
+        break;
+      }
+
+      case EMSCRIPTEN_EVENT_MOUSEMOVE:
+        if (tracker_.get() != NULL)
+        {
+          std::auto_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, viewport));
+          tracker_->PointerMove(*event);
+          viewport.Refresh();
+        }
+        break;
+
+      case EMSCRIPTEN_EVENT_MOUSEUP:
+        if (tracker_.get() != NULL)
+        {
+          std::auto_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, viewport));
+          tracker_->PointerUp(*event);
+          viewport.Refresh();
+          if (!tracker_->IsAlive())
+            tracker_.reset();
+        }
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  return true;
+}
+
+
+void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas)
+{
+  emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent);
+  emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent);
+  emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent);
+}
--- a/Samples/WebAssembly/index.html	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/WebAssembly/index.html	Mon Jun 24 14:35:00 2019 +0200
@@ -10,6 +10,7 @@
     <h1>Available samples</h1>
     <ul>
       <li><a href="BasicScene.html">Basic scene</a></li>
+      <li><a href="BasicMPR.html">Basic MPR display</a></li>
     </ul>
   </body>
 </html>
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -19,14 +19,14 @@
  **/
 
 
-#include "../Framework/dev.h"
 #include "gtest/gtest.h"
 
-#include "../Framework/Layers/FrameRenderer.h"
-#include "../Framework/Toolbox/DownloadStack.h"
+#include "../Framework/Deprecated/Layers/FrameRenderer.h"
+#include "../Framework/Deprecated/Toolbox/DownloadStack.h"
+#include "../Framework/Deprecated/Toolbox/MessagingToolbox.h"
+#include "../Framework/Deprecated/Toolbox/OrthancSlicesLoader.h"
 #include "../Framework/Toolbox/FiniteProjectiveCamera.h"
-#include "../Framework/Toolbox/MessagingToolbox.h"
-#include "../Framework/Toolbox/OrthancSlicesLoader.h"
+#include "../Framework/Toolbox/GeometryToolbox.h"
 #include "../Framework/Volumes/ImageBuffer3D.h"
 #include "../Platforms/Generic/OracleWebService.h"
 
@@ -42,97 +42,6 @@
 #include <boost/math/special_functions/round.hpp>
 
 
-#if 0
-namespace OrthancStone
-{
-  class Tata : public OrthancSlicesLoader::ICallback
-  {
-  public:
-    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader)
-    {
-      printf(">> %d\n", (int) loader.GetSliceCount());
-
-      for (size_t i = 0; i < loader.GetSliceCount(); i++)
-      {
-        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_FullPng);
-      }
-    }
-
-    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader)
-    {
-      printf("Error\n");
-    }
-
-    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
-                                       SliceImageQuality quality)
-    {
-      std::auto_ptr<Orthanc::ImageAccessor> tmp(image);
-      printf("Slice OK %dx%d\n", tmp->GetWidth(), tmp->GetHeight());
-    }
-
-    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
-                                       unsigned int sliceIndex,
-                                       const Slice& slice,
-                                       SliceImageQuality quality)
-    {
-      printf("ERROR 2\n");
-    }
-  };
-}
-
-
-TEST(Toto, DISABLED_Tutu)
-{
-  OrthancStone::Oracle oracle(4);
-  oracle.Start();
-
-  Orthanc::WebServiceParameters web;
-  //OrthancStone::OrthancAsynchronousWebService orthanc(web, 4);
-  OrthancStone::OracleWebService orthanc(oracle, web);
-  //orthanc.Start();
-
-  OrthancStone::Tata tata;
-  OrthancStone::OrthancSlicesLoader loader(tata, orthanc);
-  loader.ScheduleLoadSeries("c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5");
-  //loader.ScheduleLoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");
-
-  //loader.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
-
-  /*printf(">> %d\n", loader.GetSliceCount());
-    loader.ScheduleLoadSliceImage(31);*/
-
-  boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
-
-  //orthanc.Stop();
-  oracle.Stop();
-}
-
-
-TEST(Toto, Tata)
-{
-  OrthancStone::Oracle oracle(4);
-  oracle.Start();
-
-  Orthanc::WebServiceParameters web;
-  OrthancStone::OracleWebService orthanc(oracle, web);
-  OrthancStone::OrthancVolumeImage volume(orthanc, true);
-
-  //volume.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
-  //volume.ScheduleLoadSeries("318603c5-03e8cffc-a82b6ee1-3ccd3c1e-18d7e3bb"); // COMUNIX PET
-  //volume.ScheduleLoadSeries("7124dba7-09803f33-98b73826-33f14632-ea842d29"); // COMUNIX CT
-  //volume.ScheduleLoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // Delphine sagital
-  volume.ScheduleLoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); // Delphine ax 2.5
-
-  boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
-
-  oracle.Stop();
-}
-#endif
-
-
 TEST(GeometryToolbox, Interpolation)
 {
   using namespace OrthancStone::GeometryToolbox;
@@ -727,7 +636,7 @@
 {
   Json::Value response;
   std::string source = "{\"command\":\"panel:takeDarkImage\",\"commandType\":\"simple\",\"args\":{}}";
-  ASSERT_TRUE(OrthancStone::MessagingToolbox::ParseJson(response, source.c_str(), source.size()));
+  ASSERT_TRUE(Deprecated::MessagingToolbox::ParseJson(response, source.c_str(), source.size()));
 }
 
 TEST(VolumeImageGeometry, Basic)
@@ -790,18 +699,22 @@
     OrthancStone::VolumeProjection projection = (OrthancStone::VolumeProjection) p;
     const OrthancStone::CoordinateSystem3D& s = g.GetProjectionGeometry(projection);
     
+    ASSERT_THROW(g.GetProjectionSlice(projection, g.GetProjectionDepth(projection)), Orthanc::OrthancException);
+
     for (unsigned int i = 0; i < g.GetProjectionDepth(projection); i++)
     {
-      OrthancStone::CoordinateSystem3D plane(
-        s.GetOrigin() + static_cast<double>(i) * s.GetNormal() * g.GetVoxelDimensions(projection)[2],
-        s.GetAxisX(),
-        s.GetAxisY());
+      OrthancStone::CoordinateSystem3D plane = g.GetProjectionSlice(projection, i);
+
+      ASSERT_TRUE(IsEqualVector(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(i) * 
+                                s.GetNormal() * g.GetVoxelDimensions(projection)[2]));
+      ASSERT_TRUE(IsEqualVector(plane.GetAxisX(), s.GetAxisX()));
+      ASSERT_TRUE(IsEqualVector(plane.GetAxisY(), s.GetAxisY()));
 
       unsigned int slice;
       OrthancStone::VolumeProjection q;
       ASSERT_TRUE(g.DetectSlice(q, slice, plane));
       ASSERT_EQ(projection, q);
-      ASSERT_EQ(i, slice);
+      ASSERT_EQ(i, slice);     
     }
   }
 }