changeset 197:fc3f85b29b43

integration wasm->default
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 20 Mar 2018 20:03:02 +0100
parents 4cff7b1ed31d (current diff) fccffbf99ba1 (diff)
children dabe9982fca3
files Applications/BinarySemaphore.cpp Applications/BinarySemaphore.h Applications/Sdl/SdlBuffering.cpp Applications/Sdl/SdlBuffering.h CMakeLists.txt Framework/Enumerations.h Framework/Layers/ILayerRendererFactory.h Framework/Toolbox/IThreadSafety.h Framework/Toolbox/OrthancSeriesLoader.cpp Framework/Toolbox/OrthancSeriesLoader.h Framework/Toolbox/SharedValue.h Framework/Toolbox/SliceGeometry.cpp Framework/Toolbox/SliceGeometry.h Framework/Volumes/ISliceableVolume.h Framework/Volumes/VolumeImage.cpp Framework/Volumes/VolumeImage.h Framework/Volumes/VolumeImagePolicyBase.cpp Framework/Volumes/VolumeImagePolicyBase.h Framework/Volumes/VolumeImageProgressivePolicy.cpp Framework/Volumes/VolumeImageProgressivePolicy.h Framework/Volumes/VolumeImageSimplePolicy.cpp Framework/Volumes/VolumeImageSimplePolicy.h Framework/Widgets/LayeredSceneWidget.cpp Framework/Widgets/LayeredSceneWidget.h Resources/CMake/OrthancStone.cmake Resources/Orthanc/Core/ChunkedBuffer.cpp Resources/Orthanc/Core/ChunkedBuffer.h Resources/Orthanc/Core/Compression/DeflateBaseCompressor.cpp Resources/Orthanc/Core/Compression/DeflateBaseCompressor.h Resources/Orthanc/Core/Compression/GzipCompressor.cpp Resources/Orthanc/Core/Compression/GzipCompressor.h Resources/Orthanc/Core/Compression/IBufferCompressor.h Resources/Orthanc/Core/Enumerations.cpp Resources/Orthanc/Core/Enumerations.h Resources/Orthanc/Core/HttpClient.cpp Resources/Orthanc/Core/HttpClient.h Resources/Orthanc/Core/Images/Image.cpp Resources/Orthanc/Core/Images/Image.h Resources/Orthanc/Core/Images/ImageAccessor.cpp Resources/Orthanc/Core/Images/ImageAccessor.h Resources/Orthanc/Core/Images/ImageBuffer.cpp Resources/Orthanc/Core/Images/ImageBuffer.h Resources/Orthanc/Core/Images/ImageProcessing.cpp Resources/Orthanc/Core/Images/ImageProcessing.h Resources/Orthanc/Core/Images/JpegErrorManager.cpp Resources/Orthanc/Core/Images/JpegErrorManager.h Resources/Orthanc/Core/Images/JpegReader.cpp Resources/Orthanc/Core/Images/JpegReader.h Resources/Orthanc/Core/Images/PngReader.cpp Resources/Orthanc/Core/Images/PngReader.h Resources/Orthanc/Core/Logging.cpp Resources/Orthanc/Core/Logging.h Resources/Orthanc/Core/OrthancException.h Resources/Orthanc/Core/PrecompiledHeaders.h Resources/Orthanc/Core/SystemToolbox.cpp Resources/Orthanc/Core/SystemToolbox.h Resources/Orthanc/Core/Toolbox.cpp Resources/Orthanc/Core/Toolbox.h Resources/Orthanc/Core/WebServiceParameters.cpp Resources/Orthanc/Core/WebServiceParameters.h Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp Resources/Orthanc/Plugins/Samples/Common/DicomPath.h Resources/Orthanc/Plugins/Samples/Common/DicomTag.cpp Resources/Orthanc/Plugins/Samples/Common/DicomTag.h Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h Resources/Orthanc/README.txt Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake Resources/Orthanc/Resources/CMake/Compiler.cmake Resources/Orthanc/Resources/CMake/DownloadPackage.cmake Resources/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake Resources/Orthanc/Resources/CMake/LibCurlConfiguration.cmake Resources/Orthanc/Resources/CMake/LibIconvConfiguration.cmake Resources/Orthanc/Resources/CMake/LibJpegConfiguration.cmake Resources/Orthanc/Resources/CMake/LibPngConfiguration.cmake Resources/Orthanc/Resources/CMake/OpenSslConfiguration.cmake Resources/Orthanc/Resources/CMake/ZlibConfiguration.cmake Resources/Orthanc/Resources/EmbedResources.py Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake Resources/Orthanc/Resources/MinGWToolchain.cmake Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp Resources/Orthanc/Resources/ThirdParty/base64/base64.h Resources/Orthanc/Resources/ThirdParty/patch/msys-1.0.dll Resources/Orthanc/Resources/ThirdParty/patch/patch.exe Resources/Orthanc/Resources/ThirdParty/patch/patch.exe.manifest Resources/Orthanc/Resources/WindowsResources.py Resources/Orthanc/Resources/WindowsResources.rc Resources/SyncOrthancFolder.py
diffstat 255 files changed, 15640 insertions(+), 19289 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/BasicApplicationContext.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/BasicApplicationContext.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,15 +21,30 @@
 
 #include "BasicApplicationContext.h"
 
-#include "../../Framework/Toolbox/OrthancSeriesLoader.h"
-#include "../../Framework/Volumes/VolumeImageSimplePolicy.h"
-#include "../../Framework/Volumes/VolumeImageProgressivePolicy.h"
-
 namespace OrthancStone
 {
-  BasicApplicationContext::BasicApplicationContext(OrthancPlugins::IOrthancConnection& orthanc) :
-    orthanc_(orthanc)
+  void BasicApplicationContext::UpdateThread(BasicApplicationContext* that)
   {
+    while (!that->stopped_)
+    {
+      {
+        ViewportLocker locker(*that);
+        locker.GetViewport().UpdateContent();
+      }
+      
+      boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelay_));
+    }
+  }
+  
+
+  BasicApplicationContext::BasicApplicationContext(Orthanc::WebServiceParameters& orthanc) :
+    oracle_(viewportMutex_, 4),  // Use 4 threads to download
+    //oracle_(viewportMutex_, 1),  // Disable threading to be reproducible
+    webService_(oracle_, orthanc),
+    stopped_(true),
+    updateDelay_(100)   // By default, 100ms between each refresh of the content
+  {
+    srand(time(NULL)); 
   }
 
 
@@ -41,13 +56,13 @@
       delete *it;
     }
 
-    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    for (SlicedVolumes::iterator it = slicedVolumes_.begin(); it != slicedVolumes_.end(); ++it)
     {
       assert(*it != NULL);
       delete *it;
     }
 
-    for (StructureSets::iterator it = structureSets_.begin(); it != structureSets_.end(); ++it)
+    for (VolumeLoaders::iterator it = volumeLoaders_.begin(); it != volumeLoaders_.end(); ++it)
     {
       assert(*it != NULL);
       delete *it;
@@ -62,38 +77,31 @@
   }
 
 
-  VolumeImage& BasicApplicationContext::AddSeriesVolume(const std::string& series,
-                                                        bool isProgressiveDownload,
-                                                        size_t downloadThreadCount)
+  ISlicedVolume& BasicApplicationContext::AddSlicedVolume(ISlicedVolume* volume)
   {
-    std::auto_ptr<VolumeImage> volume(new VolumeImage(new OrthancSeriesLoader(orthanc_, series)));
-
-    if (isProgressiveDownload)
+    if (volume == NULL)
     {
-      volume->SetDownloadPolicy(new VolumeImageProgressivePolicy);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
     else
     {
-      volume->SetDownloadPolicy(new VolumeImageSimplePolicy);
+      slicedVolumes_.push_back(volume);
+      return *volume;
     }
-
-    volume->SetThreadCount(downloadThreadCount);
-
-    VolumeImage& result = *volume;
-    volumes_.push_back(volume.release());
-
-    return result;
   }
 
 
-  DicomStructureSet& BasicApplicationContext::AddStructureSet(const std::string& instance)
+  IVolumeLoader& BasicApplicationContext::AddVolumeLoader(IVolumeLoader* loader)
   {
-    std::auto_ptr<DicomStructureSet> structureSet(new DicomStructureSet(orthanc_, instance));
-
-    DicomStructureSet& result = *structureSet;
-    structureSets_.push_back(structureSet.release());
-
-    return result;
+    if (loader == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      volumeLoaders_.push_back(loader);
+      return *loader;
+    }
   }
 
 
@@ -101,7 +109,7 @@
   {
     if (interactor == NULL)
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
 
     interactors_.push_back(interactor);
@@ -112,24 +120,25 @@
 
   void BasicApplicationContext::Start()
   {
-    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    oracle_.Start();
+
+    if (viewport_.HasUpdateContent())
     {
-      assert(*it != NULL);
-      (*it)->Start();
+      stopped_ = false;
+      updateThread_ = boost::thread(UpdateThread, this);
     }
-
-    viewport_.Start();
   }
 
 
   void BasicApplicationContext::Stop()
   {
-    viewport_.Stop();
-
-    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    stopped_ = true;
+    
+    if (updateThread_.joinable())
     {
-      assert(*it != NULL);
-      (*it)->Stop();
+      updateThread_.join();
     }
+    
+    oracle_.Stop();
   }
 }
--- a/Applications/BasicApplicationContext.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/BasicApplicationContext.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,56 +21,82 @@
 
 #pragma once
 
-#include "../../Framework/Volumes/VolumeImage.h"
-#include "../../Framework/Viewport/WidgetViewport.h"
-#include "../../Framework/Widgets/IWorldSceneInteractor.h"
-#include "../../Framework/Toolbox/DicomStructureSet.h"
+#include "../Framework/Viewport/WidgetViewport.h"
+#include "../Framework/Volumes/ISlicedVolume.h"
+#include "../Framework/Volumes/IVolumeLoader.h"
+#include "../Framework/Widgets/IWorldSceneInteractor.h"
+#include "../Platforms/Generic/OracleWebService.h"
 
 #include <list>
+#include <boost/thread.hpp>
 
 namespace OrthancStone
 {
   class BasicApplicationContext : public boost::noncopyable
   {
   private:
-    typedef std::list<ISliceableVolume*>       Volumes;
+    typedef std::list<ISlicedVolume*>          SlicedVolumes;
+    typedef std::list<IVolumeLoader*>          VolumeLoaders;
     typedef std::list<IWorldSceneInteractor*>  Interactors;
-    typedef std::list<DicomStructureSet*>      StructureSets;
+
+    static void UpdateThread(BasicApplicationContext* that);
 
-    OrthancPlugins::IOrthancConnection&  orthanc_;
-
-    WidgetViewport   viewport_;
-    Volumes          volumes_;
-    Interactors      interactors_;
-    StructureSets    structureSets_;
+    Oracle              oracle_;
+    OracleWebService    webService_;
+    boost::mutex        viewportMutex_;
+    WidgetViewport      viewport_;
+    SlicedVolumes       slicedVolumes_;
+    VolumeLoaders       volumeLoaders_;
+    Interactors         interactors_;
+    boost::thread       updateThread_;
+    bool                stopped_;
+    unsigned int        updateDelay_;
 
   public:
-    BasicApplicationContext(OrthancPlugins::IOrthancConnection& orthanc);
+    class ViewportLocker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      IViewport&                 viewport_;
+
+    public:
+      ViewportLocker(BasicApplicationContext& that) :
+        lock_(that.viewportMutex_),
+        viewport_(that.viewport_)
+      {
+      }
+
+      IViewport& GetViewport() const
+      {
+        return viewport_;
+      }
+    };
+
+    
+    BasicApplicationContext(Orthanc::WebServiceParameters& orthanc);
 
     ~BasicApplicationContext();
 
     IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
 
-    IViewport& GetViewport()
-    {
-      return viewport_;
-    }
-
-    OrthancPlugins::IOrthancConnection& GetOrthancConnection()
+    IWebService& GetWebService()
     {
-      return orthanc_;
+      return webService_;
     }
+    
+    ISlicedVolume& AddSlicedVolume(ISlicedVolume* volume);
 
-    VolumeImage& AddSeriesVolume(const std::string& series,
-                                 bool isProgressiveDownload,
-                                 size_t downloadThreadCount);
-
-    DicomStructureSet& AddStructureSet(const std::string& instance);
+    IVolumeLoader& AddVolumeLoader(IVolumeLoader* loader);
 
     IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor);
 
     void Start();
 
     void Stop();
+
+    void SetUpdateDelay(unsigned int delay)  // In milliseconds
+    {
+      updateDelay_ = delay;
+    }
   };
 }
--- a/Applications/BinarySemaphore.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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 "BinarySemaphore.h"
-
-namespace OrthancStone
-{
-  BinarySemaphore::BinarySemaphore() : 
-    proceed_(false)
-  {
-  }
-
-  void BinarySemaphore::Signal()
-  {
-    //boost::mutex::scoped_lock lock(mutex_);
-
-    proceed_ = true;
-    condition_.notify_one(); 
-  }
-
-  void BinarySemaphore::Wait()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    while (!proceed_)
-    {
-      condition_.wait(lock);
-    }
-
-    proceed_ = false;
-  }
-}
--- a/Applications/BinarySemaphore.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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/thread/mutex.hpp>
-#include <boost/thread/condition.hpp>
-
-namespace OrthancStone
-{
-  class BinarySemaphore : public boost::noncopyable
-  {
-  private:
-    bool proceed_;
-    boost::mutex mutex_;
-    boost::condition_variable condition_;
-
-  public:
-    explicit BinarySemaphore();
-
-    void Signal();
-
-    void Wait();
-  };
-}
--- a/Applications/IBasicApplication.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/IBasicApplication.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,11 +21,13 @@
 
 #include "IBasicApplication.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/HttpClient.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h"
+#include "../Framework/Toolbox/MessagingToolbox.h"
 #include "Sdl/SdlEngine.h"
 
+#include <Core/Logging.h>
+#include <Core/HttpClient.h>
+#include <Plugins/Samples/Common/OrthancHttpConnection.h>
+
 namespace OrthancStone
 {
   // Anonymous namespace to avoid clashes against other compilation modules
@@ -197,12 +199,14 @@
       }
 
       LOG(WARNING) << "URL to the Orthanc REST API: " << webService.GetUrl();
-      OrthancPlugins::OrthancHttpConnection orthanc(webService);
 
-      if (!MessagingToolbox::CheckOrthancVersion(orthanc))
       {
-        LOG(ERROR) << "Your version of Orthanc is incompatible with Orthanc Stone, please upgrade";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        OrthancPlugins::OrthancHttpConnection orthanc(webService);
+        if (!MessagingToolbox::CheckOrthancVersion(orthanc))
+        {
+          LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
       }
 
 
@@ -210,11 +214,17 @@
        * Initialize the application
        ****************************************************************/
 
+      LOG(WARNING) << "Creating the widgets of the application";
+
       LogStatusBar statusBar;
-      BasicApplicationContext context(orthanc);
+      BasicApplicationContext context(webService);
 
       application.Initialize(context, statusBar, parameters);
-      context.GetViewport().SetStatusBar(statusBar);
+
+      {
+        BasicApplicationContext::ViewportLocker locker(context);
+        locker.GetViewport().SetStatusBar(statusBar);
+      }
 
       std::string title = application.GetTitle();
       if (title.empty())
@@ -222,8 +232,6 @@
         title = "Stone of Orthanc";
       }
 
-      context.Start();
-
       {
         /**************************************************************
          * Run the application inside a SDL window
@@ -232,11 +240,25 @@
         LOG(WARNING) << "Starting the application";
 
         SdlWindow window(title.c_str(), width, height, opengl);
-        SdlEngine sdl(window, context.GetViewport());
+        SdlEngine sdl(window, context);
 
+        {
+          BasicApplicationContext::ViewportLocker locker(context);
+          locker.GetViewport().Register(sdl);  // (*)
+        }
+
+        context.Start();
         sdl.Run();
 
         LOG(WARNING) << "Stopping the application";
+
+        // Don't move the "Stop()" command below out of the block,
+        // otherwise the application might crash, because the
+        // "SdlEngine" is an observer of the viewport (*) and the
+        // update thread started by "context.Start()" would call a
+        // destructed object (the "SdlEngine" is deleted with the
+        // lexical scope).
+        context.Stop();
       }
 
 
@@ -244,11 +266,7 @@
        * Finalize the application
        ****************************************************************/
 
-      context.Stop();
-
       LOG(WARNING) << "The application has stopped";
-
-      context.GetViewport().ResetStatusBar();
       application.Finalize();
     }
     catch (Orthanc::OrthancException& e)
--- a/Applications/Samples/BasicPetCtFusionApplication.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Samples/BasicPetCtFusionApplication.h	Tue Mar 20 20:03:02 2018 +0100
@@ -87,13 +87,13 @@
         {
           RenderStyle style = widget.GetLayerStyle(layer);
          
-          if (style.interpolation_ == ImageInterpolation_Linear)
+          if (style.interpolation_ == ImageInterpolation_Bilinear)
           {
             style.interpolation_ = ImageInterpolation_Nearest;
           }
           else
           {
-            style.interpolation_ = ImageInterpolation_Linear;
+            style.interpolation_ = ImageInterpolation_Bilinear;
           }
 
           widget.SetLayerStyle(layer, style);
--- a/Applications/Samples/SampleInteractor.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Samples/SampleInteractor.h	Tue Mar 20 20:03:02 2018 +0100
@@ -82,7 +82,6 @@
       }
 
       virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                          const SliceGeometry& slice,
                                                           const ViewportGeometry& view,
                                                           MouseButton button,
                                                           double x,
@@ -94,7 +93,6 @@
 
       virtual void MouseOver(CairoContext& context,
                              WorldSceneWidget& widget,
-                             const SliceGeometry& slice,
                              const ViewportGeometry& view,
                              double x,
                              double y,
--- a/Applications/Samples/SingleFrameApplication.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Samples/SingleFrameApplication.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,17 +23,197 @@
 
 #include "SampleApplicationBase.h"
 
-#include "../../Framework/Layers/SingleFrameRendererFactory.h"
-#include "../../Framework/Widgets/LayeredSceneWidget.h"
-#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Framework/Layers/OrthancFrameLayerSource.h"
+#include "../../Framework/Widgets/LayerWidget.h"
+
+#include <Core/Logging.h>
 
 namespace OrthancStone
 {
   namespace Samples
   {
-    class SingleFrameApplication : public SampleApplicationBase
+    class SingleFrameApplication :
+      public SampleApplicationBase,
+      private ILayerSource::IObserver
     {
+    private:
+      class Interactor : public IWorldSceneInteractor
+      {
+      private:
+        SingleFrameApplication&  application_;
+        
+      public:
+        Interactor(SingleFrameApplication&  application) :
+          application_(application)
+        {
+        }
+        
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          return NULL;
+        }
+
+        virtual void MouseOver(CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar)
+        {
+          if (statusBar != NULL)
+          {
+            Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+            
+            char buf[64];
+            sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", 
+                    p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+            statusBar->SetMessage(buf);
+          }
+        }
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+                                MouseWheelDirection direction,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1);
+          
+          switch (direction)
+          {
+            case MouseWheelDirection_Up:
+              application_.OffsetSlice(-scale);
+              break;
+
+            case MouseWheelDirection_Down:
+              application_.OffsetSlice(scale);
+              break;
+
+            default:
+              break;
+          }
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          switch (key)
+          {
+            case 's':
+              widget.SetDefaultView();
+              break;
+
+            default:
+              break;
+          }
+        }
+      };
+
+
+      void OffsetSlice(int offset)
+      {
+        if (source_ != NULL)
+        {
+          int slice = static_cast<int>(slice_) + offset;
+
+          if (slice < 0)
+          {
+            slice = 0;
+          }
+
+          if (slice >= static_cast<int>(source_->GetSliceCount()))
+          {
+            slice = source_->GetSliceCount() - 1;
+          }
+
+          if (slice != static_cast<int>(slice_)) 
+          {
+            SetSlice(slice);
+          }   
+        }
+      }
+      
+
+      void SetSlice(size_t index)
+      {
+        if (source_ != NULL &&
+            index < source_->GetSliceCount())
+        {
+          slice_ = index;
+          
+#if 1
+          widget_->SetSlice(source_->GetSlice(slice_).GetGeometry());
+#else
+          // TEST for scene extents - Rotate the axes
+          double a = 15.0 / 180.0 * M_PI;
+
+#if 1
+          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+          Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0);
+#else
+          // Flip the normal
+          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
+          Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0);
+#endif
+          
+          SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y);
+          widget_->SetSlice(s);
+#endif
+        }
+      }
+        
+      
+      virtual void NotifyGeometryReady(const ILayerSource& source)
+      {
+        // Once the geometry of the series is downloaded from Orthanc,
+        // display its first slice, and adapt the viewport to fit this
+        // slice
+        if (source_ == &source)
+        {
+          SetSlice(source_->GetSliceCount() / 2);
+        }
+
+        widget_->SetDefaultView();
+      }
+      
+      virtual void NotifyGeometryError(const ILayerSource& source)
+      {
+      }
+      
+      virtual void NotifyContentChange(const ILayerSource& source)
+      {
+      }
+
+      virtual void NotifySliceChange(const ILayerSource& source,
+                                     const Slice& slice)
+      {
+      }
+ 
+      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
+                                    const ILayerSource& source,
+                                    const CoordinateSystem3D& slice,
+                                    bool isError)
+      {
+      }
+
+      LayerWidget*                    widget_;
+      const OrthancFrameLayerSource*  source_;
+      unsigned int                    slice_;
+      
     public:
+      SingleFrameApplication() : 
+        widget_(NULL),
+        source_(NULL),
+        slice_(0)
+      {
+      }
+      
       virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
       {
         boost::program_options::options_description generic("Sample options");
@@ -43,7 +223,7 @@
           ("frame", boost::program_options::value<unsigned int>()->default_value(0),
            "Number of the frame, for multi-frame DICOM instances")
           ("smooth", boost::program_options::value<bool>()->default_value(true), 
-           "Enable linear interpolation to smooth the image")
+           "Enable bilinear interpolation to smooth the image")
           ;
 
         options.add(generic);    
@@ -55,6 +235,8 @@
       {
         using namespace OrthancStone;
 
+        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
+
         if (parameters.count("instance") != 1)
         {
           LOG(ERROR) << "The instance ID is missing";
@@ -64,20 +246,70 @@
         std::string instance = parameters["instance"].as<std::string>();
         int frame = parameters["frame"].as<unsigned int>();
 
-        std::auto_ptr<SingleFrameRendererFactory>  renderer;
-        renderer.reset(new SingleFrameRendererFactory(context.GetOrthancConnection(), instance, frame));
+        std::auto_ptr<LayerWidget> widget(new LayerWidget);
 
-        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->SetSlice(renderer->GetSliceGeometry());
-        widget->AddLayer(renderer.release());
+#if 1
+        std::auto_ptr<OrthancFrameLayerSource> layer
+          (new OrthancFrameLayerSource(context.GetWebService()));
+        //layer->SetImageQuality(SliceImageQuality_Jpeg50);
+        layer->LoadFrame(instance, frame);
+        //layer->LoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6");
+        layer->Register(*this);
+        source_ = layer.get();
+        widget->AddLayer(layer.release());
+
+        RenderStyle s;
 
         if (parameters["smooth"].as<bool>())
         {
-          RenderStyle s; 
-          s.interpolation_ = ImageInterpolation_Linear;
+          s.interpolation_ = ImageInterpolation_Bilinear;
+        }
+
+        //s.drawGrid_ = true;
+        widget->SetLayerStyle(0, s);
+#else
+        // 0178023P**
+        // Extent of the CT layer: (-35.068 -20.368) => (34.932 49.632)
+        std::auto_ptr<OrthancFrameLayerSource> ct;
+        ct.reset(new OrthancFrameLayerSource(context.GetWebService()));
+        //ct->LoadInstance("c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0);
+        //ct->LoadInstance("4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0);  // BAD SLICE
+        //ct->SetImageQuality(SliceImageQuality_Jpeg50);
+        ct->LoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d");
+
+        ct->Register(*this);
+        widget->AddLayer(ct.release());
+
+        std::auto_ptr<OrthancFrameLayerSource> pet;
+        pet.reset(new OrthancFrameLayerSource(context.GetWebService()));
+        //pet->LoadInstance("a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0);
+        pet->LoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e");
+        pet->Register(*this);
+        source_ = pet.get();
+        widget->AddLayer(pet.release());
+
+        {
+          RenderStyle s;
+          //s.drawGrid_ = true;
+          s.alpha_ = 1;
           widget->SetLayerStyle(0, s);
         }
 
+        {
+          RenderStyle s;
+          //s.drawGrid_ = true;
+          s.SetColor(255, 0, 0);  // Draw missing PET layer in red
+          s.alpha_ = 0.5;
+          s.applyLut_ = true;
+          s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
+          s.interpolation_ = ImageInterpolation_Bilinear;
+          widget->SetLayerStyle(1, s);
+        }
+#endif
+
+        widget_ = widget.get();
+        widget_->SetTransmitMouseOver(true);
+        widget_->SetInteractor(context.AddInteractor(new Interactor(*this)));
         context.SetCentralWidget(widget.release());
       }
     };
--- a/Applications/Samples/SingleVolumeApplication.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Samples/SingleVolumeApplication.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,12 +21,18 @@
 
 #pragma once
 
-#include "SampleInteractor.h"
-
-#include "../../Resources/Orthanc/Core/Toolbox.h"
+#include "SampleApplicationBase.h"
+#include "../../Framework/dev.h"
+#include "../../Framework/Layers/ILayerSource.h"
 #include "../../Framework/Layers/LineMeasureTracker.h"
 #include "../../Framework/Layers/CircleMeasureTracker.h"
-#include "../../Resources/Orthanc/Core/Logging.h"
+
+#include <Core/Toolbox.h>
+#include <Core/Logging.h>
+
+#include <Plugins/Samples/Common/OrthancHttpConnection.h>   // TODO REMOVE
+#include "../../Framework/Layers/DicomStructureSetRendererFactory.h"   // TODO REMOVE
+#include "../../Framework/Toolbox/MessagingToolbox.h"   // TODO REMOVE
 
 namespace OrthancStone
 {
@@ -35,173 +41,49 @@
     class SingleVolumeApplication : public SampleApplicationBase
     {
     private:
-      class Interactor : public SampleInteractor
+      class Interactor : public VolumeImageInteractor
       {
       private:
-        enum MouseMode
-        {
-          MouseMode_None,
-          MouseMode_TrackCoordinates,
-          MouseMode_LineMeasure,
-          MouseMode_CircleMeasure
-        };
-
-        MouseMode mouseMode_;
-
-        void SetMouseMode(MouseMode mode,
-                          IStatusBar* statusBar)
+        LayerWidget&  widget_;
+        size_t        layer_;
+        
+      protected:
+        virtual void NotifySliceChange(const ISlicedVolume& volume,
+                                       const size_t& sliceIndex,
+                                       const Slice& slice)
         {
-          if (mouseMode_ == mode)
-          {
-            mouseMode_ = MouseMode_None;
-          }
-          else
-          {
-            mouseMode_ = mode;
-          }
-
-          if (statusBar)
-          {
-            switch (mouseMode_)
-            {
-              case MouseMode_None:
-                statusBar->SetMessage("Disabling the mouse tools");
-                break;
-
-              case MouseMode_TrackCoordinates:
-                statusBar->SetMessage("Tracking the mouse coordinates");
-                break;
-
-              case MouseMode_LineMeasure:
-                statusBar->SetMessage("Mouse clicks will now measure the distances");
-                break;
+          const OrthancVolumeImage& image = dynamic_cast<const OrthancVolumeImage&>(volume);
 
-              case MouseMode_CircleMeasure:
-                statusBar->SetMessage("Mouse clicks will now draw circles");
-                break;
-
-              default:
-                break;
-            }
-          }
-        }
+          RenderStyle s = widget_.GetLayerStyle(layer_);
 
-      public:
-        Interactor(VolumeImage& volume,
-                   VolumeProjection projection, 
-                   bool reverse) :
-          SampleInteractor(volume, projection, reverse),
-          mouseMode_(MouseMode_None)
-        {
-        }
-        
-        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                            const SliceGeometry& slice,
-                                                            const ViewportGeometry& view,
-                                                            MouseButton button,
-                                                            double x,
-                                                            double y,
-                                                            IStatusBar* statusBar)
-        {
-          if (button == MouseButton_Left)
+          if (image.FitWindowingToRange(s, slice.GetConverter()))
           {
-            switch (mouseMode_)
-            {
-              case MouseMode_LineMeasure:
-                return new LineMeasureTracker(NULL, slice, x, y, 255, 0, 0, 14 /* font size */);
-              
-              case MouseMode_CircleMeasure:
-                return new CircleMeasureTracker(NULL, slice, x, y, 255, 0, 0, 14 /* font size */);
-
-              default:
-                break;
-            }
+            //printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);
+            widget_.SetLayerStyle(layer_, s);
           }
-
-          return NULL;
         }
 
         virtual void MouseOver(CairoContext& context,
                                WorldSceneWidget& widget,
-                               const SliceGeometry& slice,
                                const ViewportGeometry& view,
                                double x,
                                double y,
                                IStatusBar* statusBar)
         {
-          if (mouseMode_ == MouseMode_TrackCoordinates &&
-              statusBar != NULL)
-          {
-            Vector p = slice.MapSliceToWorldCoordinates(x, y);
-            
-            char buf[64];
-            sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
-            statusBar->SetMessage(buf);
-          }
+          const LayerWidget& w = dynamic_cast<const LayerWidget&>(widget);
+          Vector p = w.GetSlice().MapSliceToWorldCoordinates(x, y);
+          printf("%f %f %f\n", p[0], p[1], p[2]);
         }
-
-
-        virtual void KeyPressed(WorldSceneWidget& widget,
-                                char key,
-                                KeyboardModifiers modifiers,
-                                IStatusBar* statusBar)
+      
+      public:
+        Interactor(OrthancVolumeImage& volume,
+                   LayerWidget& widget,
+                   VolumeProjection projection,
+                   size_t layer) :
+          VolumeImageInteractor(volume, widget, projection),
+          widget_(widget),
+          layer_(layer)
         {
-          switch (key)
-          {
-            case 't':
-              SetMouseMode(MouseMode_TrackCoordinates, statusBar);
-              break;
-
-            case 'm':
-              SetMouseMode(MouseMode_LineMeasure, statusBar);
-              break;
-
-            case 'c':
-              SetMouseMode(MouseMode_CircleMeasure, statusBar);
-              break;
-
-            case 'b':
-            {
-              if (statusBar)
-              {
-                statusBar->SetMessage("Setting Hounsfield window to bones");
-              }
-
-              RenderStyle style;
-              style.windowing_ = ImageWindowing_Bone;
-              dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style);
-              break;
-            }
-
-            case 'l':
-            {
-              if (statusBar)
-              {
-                statusBar->SetMessage("Setting Hounsfield window to lung");
-              }
-
-              RenderStyle style;
-              style.windowing_ = ImageWindowing_Lung;
-              dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style);
-              break;
-            }
-
-            case 'd':
-            {
-              if (statusBar)
-              {
-                statusBar->SetMessage("Setting Hounsfield window to what is written in the DICOM file");
-              }
-
-              RenderStyle style;
-              style.windowing_ = ImageWindowing_Default;
-              dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style);
-              break;
-            }
-
-            default:
-              break;
-          }
         }
       };
 
@@ -213,6 +95,8 @@
         generic.add_options()
           ("series", boost::program_options::value<std::string>(), 
            "Orthanc ID of the series")
+          ("instance", boost::program_options::value<std::string>(), 
+           "Orthanc ID of a multi-frame instance that describes a 3D volume")
           ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
            "Number of download threads")
           ("projection", boost::program_options::value<std::string>()->default_value("axial"), 
@@ -230,19 +114,45 @@
       {
         using namespace OrthancStone;
 
-        if (parameters.count("series") != 1)
+        if (parameters.count("series") > 1 ||
+            parameters.count("instance") > 1)
         {
-          LOG(ERROR) << "The series ID is missing";
+          LOG(ERROR) << "Only one series or instance is allowed";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        if (parameters.count("series") == 1 &&
+            parameters.count("instance") == 1)
+        {
+          LOG(ERROR) << "Cannot specify both a series and an instance";
           throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
         }
 
-        std::string series = parameters["series"].as<std::string>();
+        std::string series;
+        if (parameters.count("series") == 1)
+        {
+          series = parameters["series"].as<std::string>();
+        }
+        
+        std::string instance;
+        if (parameters.count("instance") == 1)
+        {
+          instance = parameters["instance"].as<std::string>();
+        }
+        
+        if (series.empty() &&
+            instance.empty())
+        {
+          LOG(ERROR) << "The series ID or instance ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
         unsigned int threads = parameters["threads"].as<unsigned int>();
         bool reverse = parameters["reverse"].as<bool>();
 
         std::string tmp = parameters["projection"].as<std::string>();
         Orthanc::Toolbox::ToLowerCase(tmp);
-        
+
         VolumeProjection projection;
         if (tmp == "axial")
         {
@@ -262,22 +172,98 @@
           throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
         }
 
-        VolumeImage& volume = context.AddSeriesVolume(series, true /* progressive download */, threads);
+        std::auto_ptr<LayerWidget> widget(new LayerWidget);
 
-        std::auto_ptr<Interactor> interactor(new Interactor(volume, projection, reverse));
+#if 0
+        std::auto_ptr<OrthancVolumeImage> volume(new OrthancVolumeImage(context.GetWebService(), true));
+        if (series.empty())
+        {
+          volume->ScheduleLoadInstance(instance);
+        }
+        else
+        {
+          volume->ScheduleLoadSeries(series);
+        }
+
+        widget->AddLayer(new VolumeImageSource(*volume));
+
+        context.AddInteractor(new Interactor(*volume, *widget, projection, 0));
+        context.AddSlicedVolume(volume.release());
 
-        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->AddLayer(new VolumeImage::LayerFactory(volume));
-        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
-        widget->SetInteractor(*interactor);
+        {
+          RenderStyle s;
+          s.alpha_ = 1;
+          s.applyLut_ = true;
+          s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
+          s.interpolation_ = ImageInterpolation_Bilinear;
+          widget->SetLayerStyle(0, s);
+        }
+#else
+        std::auto_ptr<OrthancVolumeImage> ct(new OrthancVolumeImage(context.GetWebService(), false));
+        //ct->ScheduleLoadSeries("15a6f44a-ac7b88fe-19c462d9-dddd918e-b01550d8");  // 0178023P
+        //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d");
+        //ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // IBA
+        //ct->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0");  // 0522c0001 TCIA
+        ct->ScheduleLoadSeries("295e8a13-dfed1320-ba6aebb2-9a13e20f-1b3eb953");  // Captain
+        
+        std::auto_ptr<OrthancVolumeImage> pet(new OrthancVolumeImage(context.GetWebService(), true));
+        //pet->ScheduleLoadSeries("48d2997f-8e25cd81-dd715b64-bd79cdcc-e8fcee53");  // 0178023P
+        //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e");
+        //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1
+        //pet->ScheduleLoadInstance("337876a1-a68a9718-f15abccd-38faafa1-b99b496a"); // IBA 2
+        //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb");  // IBA 3
+        //pet->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c");  // 0522c0001 TCIA
+        pet->ScheduleLoadInstance("f080888c-0ab7528a-f7d9c28c-84980eb1-ff3b0ae6");  // Captain 1
+        //pet->ScheduleLoadInstance("4f78055b-6499a2c5-1e089290-394acc05-3ec781c1");  // Captain 2
 
-        context.AddInteractor(interactor.release());
-        context.SetCentralWidget(widget.release());
+        std::auto_ptr<StructureSetLoader> rtStruct(new StructureSetLoader(context.GetWebService()));
+        //rtStruct->ScheduleLoadInstance("c2ebc17b-6b3548db-5e5da170-b8ecab71-ea03add3");  // 0178023P
+        //rtStruct->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // IBA
+        //rtStruct->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20");  // 0522c0001 TCIA
+        rtStruct->ScheduleLoadInstance("96c889ab-29fe5c54-dda6e66c-3949e4da-58f90d75");  // Captain
+        
+        widget->AddLayer(new VolumeImageSource(*ct));
+        widget->AddLayer(new VolumeImageSource(*pet));
+        widget->AddLayer(new DicomStructureSetRendererFactory(*rtStruct));
+        
+        context.AddInteractor(new Interactor(*pet, *widget, projection, 1));
+        //context.AddInteractor(new VolumeImageInteractor(*ct, *widget, projection));
+
+        context.AddSlicedVolume(ct.release());
+        context.AddSlicedVolume(pet.release());
+        context.AddVolumeLoader(rtStruct.release());
+
+        {
+          RenderStyle s;
+          //s.drawGrid_ = true;
+          s.alpha_ = 1;
+          s.windowing_ = ImageWindowing_Bone;
+          widget->SetLayerStyle(0, s);
+        }
+
+        {
+          RenderStyle s;
+          //s.drawGrid_ = true;
+          s.SetColor(255, 0, 0);  // Draw missing PET layer in red
+          s.alpha_ = 0.5;
+          s.applyLut_ = true;
+          s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
+          s.interpolation_ = ImageInterpolation_Bilinear;
+          s.windowing_ = ImageWindowing_Custom;
+          s.customWindowCenter_ = 0;
+          s.customWindowWidth_ = 128;
+          widget->SetLayerStyle(1, s);
+        }
+#endif
+
 
         statusBar.SetMessage("Use the keys \"b\", \"l\" and \"d\" to change Hounsfield windowing");
         statusBar.SetMessage("Use the keys \"t\" to track the (X,Y,Z) mouse coordinates");
         statusBar.SetMessage("Use the keys \"m\" to measure distances");
         statusBar.SetMessage("Use the keys \"c\" to draw circles");
+
+        widget->SetTransmitMouseOver(true);
+        context.SetCentralWidget(widget.release());
       }
     };
   }
--- a/Applications/Samples/SynchronizedSeriesApplication.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Samples/SynchronizedSeriesApplication.h	Tue Mar 20 20:03:02 2018 +0100
@@ -39,7 +39,8 @@
       LayeredSceneWidget* CreateSeriesWidget(BasicApplicationContext& context,
                                              const std::string& series)
       {
-        std::auto_ptr<ISeriesLoader> loader(new OrthancSeriesLoader(context.GetOrthancConnection(), series));
+        std::auto_ptr<ISeriesLoader> loader
+          (new OrthancSeriesLoader(context.GetWebService().GetConnection(), series));
 
         std::auto_ptr<SampleInteractor> interactor(new SampleInteractor(*loader, false));
 
--- a/Applications/Samples/TestPatternApplication.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Samples/TestPatternApplication.h	Tue Mar 20 20:03:02 2018 +0100
@@ -54,9 +54,10 @@
         layout->SetPadding(10);
         layout->SetBackgroundCleared(true);
         layout->AddWidget(new TestCairoWidget(parameters["animate"].as<bool>()));
-        layout->AddWidget(new TestWorldSceneWidget);
+        layout->AddWidget(new TestWorldSceneWidget(parameters["animate"].as<bool>()));
 
         context.SetCentralWidget(layout.release());
+        context.SetUpdateDelay(25);  // If animation, update the content each 25ms
       }
     };
   }
--- a/Applications/Sdl/SdlBuffering.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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 "SdlBuffering.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  SdlBuffering::SdlBuffering() :
-    sdlSurface_(NULL),
-    pendingFrame_(false)
-  {
-  }
-
-
-  SdlBuffering::~SdlBuffering()
-  {
-    if (sdlSurface_)
-    {
-      SDL_FreeSurface(sdlSurface_);
-    }
-  }
-
-
-  void SdlBuffering::SetSize(unsigned int width,
-                             unsigned int height,
-                             IViewport& viewport)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    viewport.SetSize(width, height);
-
-    if (offscreenSurface_.get() == NULL ||
-        offscreenSurface_->GetWidth() != width ||
-        offscreenSurface_->GetHeight() != height)
-    {
-      offscreenSurface_.reset(new CairoSurface(width, height));
-    }
-
-    if (onscreenSurface_.get() == NULL ||
-        onscreenSurface_->GetWidth() != width ||
-        onscreenSurface_->GetHeight() != height)
-    {
-      onscreenSurface_.reset(new CairoSurface(width, height));
-
-      // TODO Big endian?
-      static const uint32_t rmask = 0x00ff0000;
-      static const uint32_t gmask = 0x0000ff00;
-      static const uint32_t bmask = 0x000000ff;
-
-      if (sdlSurface_)
-      {
-        SDL_FreeSurface(sdlSurface_);
-      }
-
-      sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32,
-                                             onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0);
-      if (!sdlSurface_)
-      {
-        LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }    
-    }
-
-    pendingFrame_ = false;
-  }
-
-
-  bool SdlBuffering::RenderOffscreen(IViewport& viewport)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (offscreenSurface_.get() == NULL)
-    {
-      return false;
-    }
-
-    Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor();
-
-    if (viewport.Render(target) &&
-        !pendingFrame_)
-    {
-      pendingFrame_ = true;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  void SdlBuffering::SwapToScreen(SdlWindow& window)
-  {
-    if (!pendingFrame_ ||
-        offscreenSurface_.get() == NULL ||
-        onscreenSurface_.get() == NULL)
-    {
-      return;
-    }
-
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      onscreenSurface_->Copy(*offscreenSurface_);
-    }
-    
-    window.Render(sdlSurface_);
-    pendingFrame_ = false;
-  }
-}
-
-#endif
--- a/Applications/Sdl/SdlBuffering.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "SdlWindow.h"
-#include "../../Framework/Viewport/CairoSurface.h"
-#include "../../Framework/Viewport/IViewport.h"
-
-#include <boost/thread/mutex.hpp>
-
-namespace OrthancStone
-{
-  class SdlBuffering : public boost::noncopyable
-  {
-  private:
-    boost::mutex                 mutex_;
-    std::auto_ptr<CairoSurface>  offscreenSurface_;
-    std::auto_ptr<CairoSurface>  onscreenSurface_;
-    SDL_Surface*                 sdlSurface_;
-    bool                         pendingFrame_;
-
-  public:
-    SdlBuffering();
-
-    ~SdlBuffering();
-
-    void SetSize(unsigned int width,
-                 unsigned int height,
-                 IViewport& viewport);
-
-    // Returns "true" if a new refresh of the display should be
-    // triggered afterwards
-    bool RenderOffscreen(IViewport& viewport);
-
-    void SwapToScreen(SdlWindow& window);
-  };
-}
-
-#endif
--- a/Applications/Sdl/SdlEngine.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Sdl/SdlEngine.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -23,54 +23,32 @@
 
 #if ORTHANC_ENABLE_SDL == 1
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-
+#include <Core/Logging.h>
 #include <SDL.h>
 
 namespace OrthancStone
 {
+  void SdlEngine::SetSize(BasicApplicationContext::ViewportLocker& locker,
+                          unsigned int width,
+                          unsigned int height)
+  {
+    locker.GetViewport().SetSize(width, height);
+    surface_.SetSize(width, height);
+  }
+    
+
   void SdlEngine::RenderFrame()
   {
-    if (!viewportChanged_)
-    {
-      return;
-    }
-
-    viewportChanged_ = false;
-
-    if (buffering_.RenderOffscreen(viewport_))
+    if (viewportChanged_)
     {
-      // Do not notify twice when a new frame was rendered, to avoid
-      // spoiling the SDL event queue
-      SDL_Event event;
-      SDL_memset(&event, 0, sizeof(event));
-      event.type = refreshEvent_;
-      event.user.code = 0;
-      event.user.data1 = 0;
-      event.user.data2 = 0;
-      SDL_PushEvent(&event);
+      BasicApplicationContext::ViewportLocker locker(context_);
+      surface_.Render(locker.GetViewport());
+
+      viewportChanged_ = false;
     }
   }
 
 
-  void SdlEngine::RenderThread(SdlEngine* that)
-  {
-    for (;;)
-    {
-      that->renderFrame_.Wait();
-
-      if (that->continue_)
-      {
-        that->RenderFrame();
-      }
-      else
-      {
-        return;
-      }
-    }
-  }             
-
-
   KeyboardModifiers SdlEngine::GetKeyboardModifiers(const uint8_t* keyboardState,
                                                     const int scancodeCount)
   {
@@ -119,53 +97,18 @@
   }
 
 
-  void SdlEngine::SetSize(unsigned int width,
-                          unsigned int height)
-  {
-    buffering_.SetSize(width, height, viewport_);
-    viewportChanged_ = true;
-    Refresh();
-  }
-
-
-  void SdlEngine::Stop()
+  SdlEngine::SdlEngine(SdlWindow& window,
+                       BasicApplicationContext& context) :
+    window_(window),
+    context_(context),
+    surface_(window),
+    viewportChanged_(true)
   {
-    if (continue_)
-    {
-      continue_ = false;
-      renderFrame_.Signal();  // Unlock the render thread
-      renderThread_.join();
-    }
-  }
-
-
-  void SdlEngine::Refresh()
-  {
-    renderFrame_.Signal();
-  }
-
-
-  SdlEngine::SdlEngine(SdlWindow& window,
-                       IViewport& viewport) :
-    window_(window),
-    viewport_(viewport),
-    continue_(true)
-  {
-    refreshEvent_ = SDL_RegisterEvents(1);
-
-    SetSize(window_.GetWidth(), window_.GetHeight());
-
-    viewport_.Register(*this);
-
-    renderThread_ = boost::thread(RenderThread, this);
   }
   
 
   SdlEngine::~SdlEngine()
   {
-    Stop();
-
-    viewport_.Unregister(*this);
   }
 
 
@@ -174,24 +117,29 @@
     int scancodeCount = 0;
     const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
 
+    {
+      BasicApplicationContext::ViewportLocker locker(context_);
+      SetSize(locker, window_.GetWidth(), window_.GetHeight());
+      locker.GetViewport().SetDefaultView();
+    }
+    
     bool stop = false;
     while (!stop)
     {
-      Refresh();
+      RenderFrame();
 
       SDL_Event event;
 
-      while (SDL_PollEvent(&event))
+      while (!stop &&
+             SDL_PollEvent(&event))
       {
+        BasicApplicationContext::ViewportLocker locker(context_);
+
         if (event.type == SDL_QUIT) 
         {
           stop = true;
           break;
         }
-        else if (event.type == refreshEvent_)
-        {
-          buffering_.SwapToScreen(window_);
-        }
         else if (event.type == SDL_MOUSEBUTTONDOWN)
         {
           KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
@@ -199,15 +147,15 @@
           switch (event.button.button)
           {
             case SDL_BUTTON_LEFT:
-              viewport_.MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers);
+              locker.GetViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers);
               break;
             
             case SDL_BUTTON_RIGHT:
-              viewport_.MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers);
+              locker.GetViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers);
               break;
             
             case SDL_BUTTON_MIDDLE:
-              viewport_.MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
+              locker.GetViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
               break;
 
             default:
@@ -216,26 +164,26 @@
         }
         else if (event.type == SDL_MOUSEMOTION)
         {
-          viewport_.MouseMove(event.button.x, event.button.y);
+          locker.GetViewport().MouseMove(event.button.x, event.button.y);
         }
         else if (event.type == SDL_MOUSEBUTTONUP)
         {
-          viewport_.MouseUp();
+          locker.GetViewport().MouseUp();
         }
         else if (event.type == SDL_WINDOWEVENT)
         {
           switch (event.window.event)
           {
             case SDL_WINDOWEVENT_LEAVE:
-              viewport_.MouseLeave();
+              locker.GetViewport().MouseLeave();
               break;
 
             case SDL_WINDOWEVENT_ENTER:
-              viewport_.MouseEnter();
+              locker.GetViewport().MouseEnter();
               break;
 
             case SDL_WINDOWEVENT_SIZE_CHANGED:
-              SetSize(event.window.data1, event.window.data2);
+              SetSize(locker, event.window.data1, event.window.data2);
               break;
 
             default:
@@ -251,45 +199,64 @@
 
           if (event.wheel.y > 0)
           {
-            viewport_.MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
+            locker.GetViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
           }
           else if (event.wheel.y < 0)
           {
-            viewport_.MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
+            locker.GetViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
           }
         }
-        else if (event.type == SDL_KEYDOWN)
+        else if (event.type == SDL_KEYDOWN &&
+                 event.key.repeat == 0 /* Ignore key bounce */)
         {
           KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
 
           switch (event.key.keysym.sym)
           {
-            case SDLK_a:  viewport_.KeyPressed('a', modifiers);  break;
-            case SDLK_b:  viewport_.KeyPressed('b', modifiers);  break;
-            case SDLK_c:  viewport_.KeyPressed('c', modifiers);  break;
-            case SDLK_d:  viewport_.KeyPressed('d', modifiers);  break;
-            case SDLK_e:  viewport_.KeyPressed('e', modifiers);  break;
-            case SDLK_f:  window_.ToggleMaximize();              break;
-            case SDLK_g:  viewport_.KeyPressed('g', modifiers);  break;
-            case SDLK_h:  viewport_.KeyPressed('h', modifiers);  break;
-            case SDLK_i:  viewport_.KeyPressed('i', modifiers);  break;
-            case SDLK_j:  viewport_.KeyPressed('j', modifiers);  break;
-            case SDLK_k:  viewport_.KeyPressed('k', modifiers);  break;
-            case SDLK_l:  viewport_.KeyPressed('l', modifiers);  break;
-            case SDLK_m:  viewport_.KeyPressed('m', modifiers);  break;
-            case SDLK_n:  viewport_.KeyPressed('n', modifiers);  break;
-            case SDLK_o:  viewport_.KeyPressed('o', modifiers);  break;
-            case SDLK_p:  viewport_.KeyPressed('p', modifiers);  break;
-            case SDLK_q:  stop = true;                           break;
-            case SDLK_r:  viewport_.KeyPressed('r', modifiers);  break;
-            case SDLK_s:  viewport_.KeyPressed('s', modifiers);  break;
-            case SDLK_t:  viewport_.KeyPressed('t', modifiers);  break;
-            case SDLK_u:  viewport_.KeyPressed('u', modifiers);  break;
-            case SDLK_v:  viewport_.KeyPressed('v', modifiers);  break;
-            case SDLK_w:  viewport_.KeyPressed('w', modifiers);  break;
-            case SDLK_x:  viewport_.KeyPressed('x', modifiers);  break;
-            case SDLK_y:  viewport_.KeyPressed('y', modifiers);  break;
-            case SDLK_z:  viewport_.KeyPressed('z', modifiers);  break;
+            case SDLK_a:    locker.GetViewport().KeyPressed('a', modifiers);  break;
+            case SDLK_b:    locker.GetViewport().KeyPressed('b', modifiers);  break;
+            case SDLK_c:    locker.GetViewport().KeyPressed('c', modifiers);  break;
+            case SDLK_d:    locker.GetViewport().KeyPressed('d', modifiers);  break;
+            case SDLK_e:    locker.GetViewport().KeyPressed('e', modifiers);  break;
+            case SDLK_f:    window_.ToggleMaximize();                         break;
+            case SDLK_g:    locker.GetViewport().KeyPressed('g', modifiers);  break;
+            case SDLK_h:    locker.GetViewport().KeyPressed('h', modifiers);  break;
+            case SDLK_i:    locker.GetViewport().KeyPressed('i', modifiers);  break;
+            case SDLK_j:    locker.GetViewport().KeyPressed('j', modifiers);  break;
+            case SDLK_k:    locker.GetViewport().KeyPressed('k', modifiers);  break;
+            case SDLK_l:    locker.GetViewport().KeyPressed('l', modifiers);  break;
+            case SDLK_m:    locker.GetViewport().KeyPressed('m', modifiers);  break;
+            case SDLK_n:    locker.GetViewport().KeyPressed('n', modifiers);  break;
+            case SDLK_o:    locker.GetViewport().KeyPressed('o', modifiers);  break;
+            case SDLK_p:    locker.GetViewport().KeyPressed('p', modifiers);  break;
+            case SDLK_q:    stop = true;                                      break;
+            case SDLK_r:    locker.GetViewport().KeyPressed('r', modifiers);  break;
+            case SDLK_s:    locker.GetViewport().KeyPressed('s', modifiers);  break;
+            case SDLK_t:    locker.GetViewport().KeyPressed('t', modifiers);  break;
+            case SDLK_u:    locker.GetViewport().KeyPressed('u', modifiers);  break;
+            case SDLK_v:    locker.GetViewport().KeyPressed('v', modifiers);  break;
+            case SDLK_w:    locker.GetViewport().KeyPressed('w', modifiers);  break;
+            case SDLK_x:    locker.GetViewport().KeyPressed('x', modifiers);  break;
+            case SDLK_y:    locker.GetViewport().KeyPressed('y', modifiers);  break;
+            case SDLK_z:    locker.GetViewport().KeyPressed('z', modifiers);  break;
+            case SDLK_KP_0: locker.GetViewport().KeyPressed('0', modifiers);  break;
+            case SDLK_KP_1: locker.GetViewport().KeyPressed('1', modifiers);  break;
+            case SDLK_KP_2: locker.GetViewport().KeyPressed('2', modifiers);  break;
+            case SDLK_KP_3: locker.GetViewport().KeyPressed('3', modifiers);  break;
+            case SDLK_KP_4: locker.GetViewport().KeyPressed('4', modifiers);  break;
+            case SDLK_KP_5: locker.GetViewport().KeyPressed('5', modifiers);  break;
+            case SDLK_KP_6: locker.GetViewport().KeyPressed('6', modifiers);  break;
+            case SDLK_KP_7: locker.GetViewport().KeyPressed('7', modifiers);  break;
+            case SDLK_KP_8: locker.GetViewport().KeyPressed('8', modifiers);  break;
+            case SDLK_KP_9: locker.GetViewport().KeyPressed('9', modifiers);  break;
+
+            case SDLK_PLUS:
+            case SDLK_KP_PLUS:
+              locker.GetViewport().KeyPressed('+', modifiers);  break;
+
+            case SDLK_MINUS:
+            case SDLK_KP_MINUS:
+              locker.GetViewport().KeyPressed('-', modifiers);  break;
 
             default:
               break;
@@ -297,10 +264,9 @@
         }
       }
 
-      SDL_Delay(10);   // Necessary for mouse wheel events to work
+      // Small delay to avoid using 100% of CPU
+      SDL_Delay(1);
     }
-
-    Stop();
   }
 
 
--- a/Applications/Sdl/SdlEngine.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Sdl/SdlEngine.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,42 +23,31 @@
 
 #if ORTHANC_ENABLE_SDL == 1
 
-#include "SdlBuffering.h"
-#include "../BinarySemaphore.h"
-
-#include <boost/thread.hpp>
+#include "SdlSurface.h"
+#include "../BasicApplicationContext.h"
 
 namespace OrthancStone
 {
-  class SdlEngine : public IViewport::IChangeObserver
+  class SdlEngine : public IViewport::IObserver
   {
   private:
-    SdlWindow&        window_;
-    IViewport&        viewport_;
-    SdlBuffering      buffering_;
-    boost::thread     renderThread_;
-    bool              continue_;
-    BinarySemaphore   renderFrame_;
-    uint32_t          refreshEvent_;
-    bool              viewportChanged_;
+    SdlWindow&                window_;
+    BasicApplicationContext&  context_;
+    SdlSurface                surface_;
+    bool                      viewportChanged_;
 
+    void SetSize(BasicApplicationContext::ViewportLocker& locker,
+                 unsigned int width,
+                 unsigned int height);
+    
     void RenderFrame();
 
-    static void RenderThread(SdlEngine* that);
-
     static KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState,
                                                   const int scancodeCount);
 
-    void SetSize(unsigned int width,
-                 unsigned int height);
-
-    void Stop();
-
-    void Refresh();
-
   public:
     SdlEngine(SdlWindow& window,
-              IViewport& viewport);
+              BasicApplicationContext& context);
   
     virtual ~SdlEngine();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlSurface.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,93 @@
+/**
+ * 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 "SdlSurface.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  SdlSurface::SdlSurface(SdlWindow& window) :
+    window_(window),
+    sdlSurface_(NULL)
+  {
+  }
+
+
+  SdlSurface::~SdlSurface()
+  {
+    if (sdlSurface_)
+    {
+      SDL_FreeSurface(sdlSurface_);
+    }
+  }
+
+
+  void SdlSurface::SetSize(unsigned int width,
+                           unsigned int height)
+  {
+    if (cairoSurface_.get() == NULL ||
+        cairoSurface_->GetWidth() != width ||
+        cairoSurface_->GetHeight() != height)
+    {
+      cairoSurface_.reset(new CairoSurface(width, height));
+
+      // TODO Big endian?
+      static const uint32_t rmask = 0x00ff0000;
+      static const uint32_t gmask = 0x0000ff00;
+      static const uint32_t bmask = 0x000000ff;
+
+      if (sdlSurface_)
+      {
+        SDL_FreeSurface(sdlSurface_);
+      }
+
+      sdlSurface_ = SDL_CreateRGBSurfaceFrom(cairoSurface_->GetBuffer(), width, height, 32,
+                                             cairoSurface_->GetPitch(), rmask, gmask, bmask, 0);
+      if (!sdlSurface_)
+      {
+        LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void SdlSurface::Render(IViewport& viewport)
+  {
+    if (cairoSurface_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    Orthanc::ImageAccessor target = cairoSurface_->GetAccessor();
+
+    if (viewport.Render(target))
+    {
+      window_.Render(sdlSurface_);
+    }
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlSurface.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,53 @@
+/**
+ * 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
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "SdlWindow.h"
+#include "../../Framework/Viewport/CairoSurface.h"
+#include "../../Framework/Viewport/IViewport.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  class SdlSurface : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<CairoSurface>  cairoSurface_;
+    SdlWindow&                   window_;
+    SDL_Surface*                 sdlSurface_;
+
+  public:
+    SdlSurface(SdlWindow& window);
+
+    ~SdlSurface();
+
+    void SetSize(unsigned int width,
+                 unsigned int height);
+
+    void Render(IViewport& viewport);
+  };
+}
+
+#endif
--- a/Applications/Sdl/SdlWindow.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Applications/Sdl/SdlWindow.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -23,8 +23,8 @@
 
 #if ORTHANC_ENABLE_SDL == 1
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
--- a/CMakeLists.txt	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-cmake_minimum_required(VERSION 2.8)
-project(OrthancStone)
-
-
-#####################################################################
-## Build a static library containing the Orthanc Stone framework
-#####################################################################
-
-SET(STONE_SANDBOXED OFF)
-include(Resources/CMake/OrthancStone.cmake)
-add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES})
-
-
-#####################################################################
-## CMake parameters for Google Test
-#####################################################################
-
-SET(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
-SET(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
-
-include(${ORTHANC_ROOT}/Resources/CMake/GoogleTestConfiguration.cmake)
-
-
-#####################################################################
-## Build all the sample applications
-#####################################################################
-
-macro(BuildSample Target Sample)
-  add_executable(${Target} Applications/Samples/SampleMainSdl.cpp)
-  set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
-  target_link_libraries(${Target} OrthancStone)
-endmacro()
-
-BuildSample(OrthancStoneEmpty 1)
-BuildSample(OrthancStoneTestPattern 2)
-BuildSample(OrthancStoneSingleFrame 3)
-BuildSample(OrthancStoneSingleVolume 4)
-BuildSample(OrthancStoneBasicPetCtFusion 5)
-BuildSample(OrthancStoneSynchronizedSeries 6)
-BuildSample(OrthancStoneLayoutPetCtFusion 7)
-
-
-#####################################################################
-## Build the unit tests
-#####################################################################
-
-add_executable(UnitTests
-  ${GTEST_SOURCES}
-  UnitTestsSources/UnitTestsMain.cpp
-  )
-
-target_link_libraries(UnitTests OrthancStone)
-
-
-#####################################################################
-## Generate the documentation if Doxygen is present
-#####################################################################
-
-find_package(Doxygen)
-if (DOXYGEN_FOUND)
-  configure_file(
-    ${ORTHANC_STONE_DIR}/Resources/OrthancStone.doxygen
-    ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen
-    @ONLY)
-
-  add_custom_target(doc
-    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen
-    COMMENT "Generating documentation with Doxygen" VERBATIM
-    )
-else()
-  message("Doxygen not found. The documentation will not be built.")
-endif()
--- a/Framework/Enumerations.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +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
-
-namespace OrthancStone
-{
-  enum SliceOffsetMode
-  {
-    SliceOffsetMode_Absolute,
-    SliceOffsetMode_Relative,
-    SliceOffsetMode_Loop
-  };
-
-  enum ImageWindowing
-  {
-    ImageWindowing_Default,
-    ImageWindowing_Bone,
-    ImageWindowing_Lung,
-    ImageWindowing_Custom
-  };
-
-  enum MouseButton
-  {
-    MouseButton_Left,
-    MouseButton_Right,
-    MouseButton_Middle
-  };
-
-  enum MouseWheelDirection
-  {
-    MouseWheelDirection_Up,
-    MouseWheelDirection_Down
-  };
-
-  enum VolumeProjection
-  {
-    VolumeProjection_Axial,
-    VolumeProjection_Coronal,
-    VolumeProjection_Sagittal
-  };
-
-  enum ImageInterpolation
-  {
-    ImageInterpolation_Nearest,
-    ImageInterpolation_Linear
-  };
-
-  enum KeyboardModifiers
-  {
-    KeyboardModifiers_None = 0,
-    KeyboardModifiers_Shift = (1 << 0),
-    KeyboardModifiers_Control = (1 << 1),
-    KeyboardModifiers_Alt = (1 << 2)
-  };
-}
--- a/Framework/Layers/CircleMeasureTracker.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/CircleMeasureTracker.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -31,7 +31,7 @@
 namespace OrthancStone
 {
   CircleMeasureTracker::CircleMeasureTracker(IStatusBar* statusBar,
-                                             const SliceGeometry& slice,
+                                             const CoordinateSystem3D& slice,
                                              double x, 
                                              double y,
                                              uint8_t red,
@@ -59,7 +59,7 @@
     double y = (y1_ + y2_) / 2.0;
 
     Vector tmp;
-    GeometryToolbox::AssignVector(tmp, x2_ - x1_, y2_ - y1_);
+    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]);
--- a/Framework/Layers/CircleMeasureTracker.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/CircleMeasureTracker.h	Tue Mar 20 20:03:02 2018 +0100
@@ -24,25 +24,25 @@
 #include "../Widgets/IWorldSceneMouseTracker.h"
 
 #include "../Viewport/IStatusBar.h"
-#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/CoordinateSystem3D.h"
 
 namespace OrthancStone
 {
   class CircleMeasureTracker : public IWorldSceneMouseTracker
   {
   private:
-    IStatusBar*    statusBar_;
-    SliceGeometry  slice_;
-    double         x1_;
-    double         y1_;
-    double         x2_;
-    double         y2_;
-    uint8_t        color_[3];
-    unsigned int   fontSize_;
+    IStatusBar*         statusBar_;
+    CoordinateSystem3D  slice_;
+    double              x1_;
+    double              y1_;
+    double              x2_;
+    double              y2_;
+    uint8_t             color_[3];
+    unsigned int        fontSize_;
 
   public:
     CircleMeasureTracker(IStatusBar* statusBar,
-                         const SliceGeometry& slice,
+                         const CoordinateSystem3D& slice,
                          double x, 
                          double y,
                          uint8_t red,
--- a/Framework/Layers/ColorFrameRenderer.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/ColorFrameRenderer.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #include "ColorFrameRenderer.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageProcessing.h>
 
 namespace OrthancStone
 {
@@ -38,12 +38,11 @@
 
 
   ColorFrameRenderer::ColorFrameRenderer(Orthanc::ImageAccessor* frame,
-                                         const SliceGeometry& viewportSlice,
-                                         const SliceGeometry& frameSlice,
+                                         const CoordinateSystem3D& frameSlice,
                                          double pixelSpacingX,
                                          double pixelSpacingY,
                                          bool isFullQuality) :
-    FrameRenderer(viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
+    FrameRenderer(frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
     frame_(frame)
   {
     if (frame == NULL)
--- a/Framework/Layers/ColorFrameRenderer.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/ColorFrameRenderer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -35,8 +35,7 @@
 
   public:
     ColorFrameRenderer(Orthanc::ImageAccessor* frame,    // Takes ownership
-                       const SliceGeometry& viewportSlice,
-                       const SliceGeometry& frameSlice,
+                       const CoordinateSystem3D& frameSlice,
                        double pixelSpacingX,
                        double pixelSpacingY,
                        bool isFullQuality);
--- a/Framework/Layers/DicomStructureSetRendererFactory.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/DicomStructureSetRendererFactory.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,43 +21,104 @@
 
 #include "DicomStructureSetRendererFactory.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-
 namespace OrthancStone
 {
   class DicomStructureSetRendererFactory::Renderer : public ILayerRenderer
   {
   private:
-    const DicomStructureSet&  structureSet_;
-    SliceGeometry             slice_;
-    bool                      visible_;
+    class Structure
+    {
+    private:
+      bool                                                         visible_;
+      uint8_t                                                      red_;
+      uint8_t                                                      green_;
+      uint8_t                                                      blue_;
+      std::string                                                  name_;
+      std::vector< std::vector<DicomStructureSet::PolygonPoint> >  polygons_;
+
+    public:
+      Structure(DicomStructureSet& structureSet,
+                const CoordinateSystem3D& slice,
+                size_t index) :
+        name_(structureSet.GetStructureName(index))
+      {
+        structureSet.GetStructureColor(red_, green_, blue_, index);
+        visible_ = structureSet.ProjectStructure(polygons_, index, slice);
+      }
 
+      void Render(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;
+    
+    CoordinateSystem3D  slice_;
+    Structures          structures_;
+    
   public:
-    Renderer(const DicomStructureSet& structureSet,
-             const SliceGeometry& slice) :
-      structureSet_(structureSet),
-      slice_(slice),
-      visible_(true)
+    Renderer(DicomStructureSet& structureSet,
+             const CoordinateSystem3D& slice) :
+      slice_(slice)
     {
+      for (size_t k = 0; k < structureSet.GetStructureCount(); k++)
+      {
+        structures_.push_back(new Structure(structureSet, slice, k));
+      }
+    }
+
+    virtual ~Renderer()
+    {
+      for (Structures::iterator it = structures_.begin();
+           it != structures_.end(); ++it)
+      {
+        delete *it;
+      }
     }
 
     virtual bool RenderLayer(CairoContext& context,
                              const ViewportGeometry& view)
     {
-      if (visible_)
+      cairo_set_line_width(context.GetObject(), 2.0f / view.GetZoom());
+
+      for (Structures::const_iterator it = structures_.begin();
+           it != structures_.end(); ++it)
       {
-        cairo_set_line_width(context.GetObject(), 3.0f / view.GetZoom());
-        structureSet_.Render(context, slice_);
+        assert(*it != NULL);
+        (*it)->Render(context);
       }
 
       return true;
     }
 
+    virtual const CoordinateSystem3D& GetLayerSlice()
+    {
+      return slice_;
+    }
+
     virtual void SetLayerStyle(const RenderStyle& style)
     {
-      visible_ = style.visible_;
     }
-
+    
     virtual bool IsFullQuality()
     {
       return true;
@@ -65,22 +126,11 @@
   };
 
 
-  ILayerRenderer* DicomStructureSetRendererFactory::CreateLayerRenderer(const SliceGeometry& displaySlice)
+  void DicomStructureSetRendererFactory::ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice)
   {
-    bool isOpposite;
-    if (GeometryToolbox::IsParallelOrOpposite(isOpposite, displaySlice.GetNormal(), structureSet_.GetNormal()))
+    if (loader_.HasStructureSet())
     {
-      return new Renderer(structureSet_, displaySlice);
-    }
-    else
-    {
-      return NULL;
+      NotifyLayerReady(new Renderer(loader_.GetStructureSet(), viewportSlice), viewportSlice, false);
     }
   }
-
-
-  ISliceableVolume& DicomStructureSetRendererFactory::GetSourceVolume() const
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
 }
--- a/Framework/Layers/DicomStructureSetRendererFactory.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/DicomStructureSetRendererFactory.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,40 +21,48 @@
 
 #pragma once
 
-#include "../Toolbox/DicomStructureSet.h"
-#include "ILayerRendererFactory.h"
+#include "LayerSourceBase.h"
+#include "../Volumes/StructureSetLoader.h"
 
 namespace OrthancStone
 {
-  class DicomStructureSetRendererFactory : public ILayerRendererFactory
+  class DicomStructureSetRendererFactory :
+    public LayerSourceBase,
+    private IVolumeLoader::IObserver
   {
   private:
     class Renderer;
 
-    const DicomStructureSet&  structureSet_;
+    virtual void NotifyGeometryReady(const IVolumeLoader& loader)
+    {
+      LayerSourceBase::NotifyGeometryReady();
+    }
+      
+    virtual void NotifyGeometryError(const IVolumeLoader& loader)
+    {
+      LayerSourceBase::NotifyGeometryError();
+    }
+      
+    virtual void NotifyContentChange(const IVolumeLoader& loader)
+    {
+      LayerSourceBase::NotifyContentChange();
+    }
+    
+    StructureSetLoader& loader_;
 
   public:
-    DicomStructureSetRendererFactory(const DicomStructureSet&  structureSet) :
-      structureSet_(structureSet)
+    DicomStructureSetRendererFactory(StructureSetLoader& loader) :
+      loader_(loader)
     {
+      loader_.Register(*this);
     }
 
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
-                           const SliceGeometry& displaySlice)
+    virtual bool GetExtent(std::vector<Vector>& points,
+                           const CoordinateSystem3D& viewportSlice)
     {
       return false;
     }
 
-    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& displaySlice);
-
-    virtual bool HasSourceVolume() const
-    {
-      return false;
-    }
-
-    virtual ISliceableVolume& GetSourceVolume() const;
+    virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice);
   };
 }
--- a/Framework/Layers/FrameRenderer.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/FrameRenderer.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -24,62 +24,14 @@
 #include "GrayscaleFrameRenderer.h"
 #include "ColorFrameRenderer.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
-  static bool ComputePixelTransform(cairo_matrix_t& target,
-                                    const SliceGeometry& viewportSlice,
-                                    const SliceGeometry& frameSlice,
-                                    double pixelSpacingX,
-                                    double pixelSpacingY)
-  {
-    bool isOpposite;
-    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal()))
-    {
-      return false;
-    }
-    else
-    {
-      double x0, y0, x1, y1, x2, y2;
-      viewportSlice.ProjectPoint(x0, y0, frameSlice.GetOrigin() 
-                                 - 0.5 * pixelSpacingX * frameSlice.GetAxisX()
-                                 - 0.5 * pixelSpacingY * frameSlice.GetAxisY());
-      viewportSlice.ProjectPoint(x1, y1, frameSlice.GetOrigin() 
-                                 + 0.5 * pixelSpacingX * frameSlice.GetAxisX()
-                                 - 0.5 * pixelSpacingY * frameSlice.GetAxisY());
-      viewportSlice.ProjectPoint(x2, y2, frameSlice.GetOrigin() 
-                                 - 0.5 * pixelSpacingX * frameSlice.GetAxisX()
-                                 + 0.5 * pixelSpacingY * frameSlice.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_init(&target, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0);
-
-      return true;
-    }
-  }
-
-
-  FrameRenderer::FrameRenderer(const SliceGeometry& viewportSlice,
-                               const SliceGeometry& frameSlice,
+  FrameRenderer::FrameRenderer(const CoordinateSystem3D& frameSlice,
                                double pixelSpacingX,
                                double pixelSpacingY,
                                bool isFullQuality) :
-    viewportSlice_(viewportSlice),
     frameSlice_(frameSlice),
     pixelSpacingX_(pixelSpacingX),
     pixelSpacingY_(pixelSpacingY),
@@ -88,56 +40,9 @@
   }
 
 
-  bool FrameRenderer::ComputeFrameExtent(double& x1,
-                                         double& y1,
-                                         double& x2,
-                                         double& y2,
-                                         const SliceGeometry& viewportSlice,
-                                         const SliceGeometry& frameSlice,
-                                         unsigned int frameWidth,
-                                         unsigned int frameHeight,
-                                         double pixelSpacingX,
-                                         double pixelSpacingY)
-  {
-    bool isOpposite;
-    if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal()))
-    {
-      return false;
-    }
-    else
-    {
-      cairo_matrix_t transform;
-      if (!ComputePixelTransform(transform, viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY))
-      {
-        return true;
-      }
-      
-      x1 = 0;
-      y1 = 0;
-      cairo_matrix_transform_point(&transform, &x1, &y1);
-      
-      x2 = frameWidth;
-      y2 = frameHeight;
-      cairo_matrix_transform_point(&transform, &x2, &y2);
-      
-      if (x1 > x2)
-      {
-        std::swap(x1, x2);
-      }
-
-      if (y1 > y2)
-      {
-        std::swap(y1, y2);
-      }
-
-      return true;
-    }
-  }
-
-
   bool FrameRenderer::RenderLayer(CairoContext& context,
                                   const ViewportGeometry& view)
-  {
+  {    
     if (!style_.visible_)
     {
       return true;
@@ -145,11 +50,6 @@
 
     if (display_.get() == NULL)
     {
-      if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_, pixelSpacingX_, pixelSpacingY_))
-      {
-        return true;
-      }
-
       display_.reset(GenerateDisplay(style_));
     }
 
@@ -159,7 +59,12 @@
 
     cairo_save(cr);
 
-    cairo_transform(cr, &transform_);
+    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);
 
@@ -169,7 +74,7 @@
         cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
         break;
 
-      case ImageInterpolation_Linear:
+      case ImageInterpolation_Bilinear:
         cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
         break;
 
@@ -213,26 +118,25 @@
 
 
   ILayerRenderer* FrameRenderer::CreateRenderer(Orthanc::ImageAccessor* frame,
-                                                const SliceGeometry& viewportSlice,
-                                                const SliceGeometry& frameSlice,
-                                                const OrthancPlugins::IDicomDataset& dicom,
-                                                double pixelSpacingX,
-                                                double pixelSpacingY,
+                                                const Slice& frameSlice,
                                                 bool isFullQuality)
-  {  
+  {
     std::auto_ptr<Orthanc::ImageAccessor> protect(frame);
 
     if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
     {
-      return new ColorFrameRenderer(protect.release(), viewportSlice, frameSlice, 
-                                    pixelSpacingX, pixelSpacingY, isFullQuality);
+      return new ColorFrameRenderer(protect.release(),
+                                    frameSlice.GetGeometry(), 
+                                    frameSlice.GetPixelSpacingX(),
+                                    frameSlice.GetPixelSpacingY(), isFullQuality);
     }
     else
     {
-      DicomFrameConverter converter;
-      converter.ReadParameters(dicom);
-      return new GrayscaleFrameRenderer(protect.release(), converter, viewportSlice, frameSlice, 
-                                        pixelSpacingX, pixelSpacingY, isFullQuality);
+      return new GrayscaleFrameRenderer(protect.release(),
+                                        frameSlice.GetConverter(),
+                                        frameSlice.GetGeometry(), 
+                                        frameSlice.GetPixelSpacingX(),
+                                        frameSlice.GetPixelSpacingY(), isFullQuality);
     }
   }
 }
--- a/Framework/Layers/FrameRenderer.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/FrameRenderer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,47 +23,37 @@
 
 #include "ILayerRenderer.h"
 
-#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/Slice.h"
 
 namespace OrthancStone
 {
   class FrameRenderer : public ILayerRenderer
   {
   private:
-    SliceGeometry                 viewportSlice_;
-    SliceGeometry                 frameSlice_;
+    CoordinateSystem3D            frameSlice_;
     double                        pixelSpacingX_;
     double                        pixelSpacingY_;
     RenderStyle                   style_;
     bool                          isFullQuality_;
-
     std::auto_ptr<CairoSurface>   display_;
-    cairo_matrix_t                transform_;
 
   protected:
     virtual CairoSurface* GenerateDisplay(const RenderStyle& style) = 0;
 
   public:
-    FrameRenderer(const SliceGeometry& viewportSlice,
-                  const SliceGeometry& frameSlice,
+    FrameRenderer(const CoordinateSystem3D& frameSlice,
                   double pixelSpacingX,
                   double pixelSpacingY,
                   bool isFullQuality);
 
-    static bool ComputeFrameExtent(double& x1,
-                                   double& y1,
-                                   double& x2,
-                                   double& y2,
-                                   const SliceGeometry& viewportSlice,
-                                   const SliceGeometry& frameSlice,
-                                   unsigned int frameWidth,
-                                   unsigned int frameHeight,
-                                   double pixelSpacingX,
-                                   double pixelSpacingY);
-
     virtual bool RenderLayer(CairoContext& context,
                              const ViewportGeometry& view);
 
+    virtual const CoordinateSystem3D& GetLayerSlice()
+    {
+      return frameSlice_;
+    }
+
     virtual void SetLayerStyle(const RenderStyle& style);
 
     virtual bool IsFullQuality() 
@@ -72,11 +62,7 @@
     }
 
     static ILayerRenderer* CreateRenderer(Orthanc::ImageAccessor* frame,
-                                          const SliceGeometry& viewportSlice,
-                                          const SliceGeometry& frameSlice,
-                                          const OrthancPlugins::IDicomDataset& dicom,
-                                          double pixelSpacingX,
-                                          double pixelSpacingY,
+                                          const Slice& frameSlice,
                                           bool isFullQuality);
   };
 }
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/GrayscaleFrameRenderer.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,7 @@
 
 #include "GrayscaleFrameRenderer.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
@@ -51,14 +51,17 @@
 
       lut = reinterpret_cast<const uint8_t*>(Orthanc::EmbeddedResources::GetFileResourceBuffer(style.lut_));
     }
-      
+
     Orthanc::ImageAccessor target = result->GetAccessor();
-    for (unsigned int y = 0; y < target.GetHeight(); y++)
+    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 < target.GetWidth(); x++, p++, q += 4)
+      for (unsigned int x = 0; x < width; x++, p++, q += 4)
       {
         uint8_t v = 0;
         if (windowWidth >= 0.001f)  // Avoid division by zero
@@ -107,12 +110,11 @@
 
   GrayscaleFrameRenderer::GrayscaleFrameRenderer(Orthanc::ImageAccessor* frame,
                                                  const DicomFrameConverter& converter,
-                                                 const SliceGeometry& viewportSlice,
-                                                 const SliceGeometry& frameSlice,
+                                                 const CoordinateSystem3D& frameSlice,
                                                  double pixelSpacingX,
                                                  double pixelSpacingY,
                                                  bool isFullQuality) :
-    FrameRenderer(viewportSlice, frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
+    FrameRenderer(frameSlice, pixelSpacingX, pixelSpacingY, isFullQuality),
     frame_(frame),
     defaultWindowCenter_(converter.GetDefaultWindowCenter()),
     defaultWindowWidth_(converter.GetDefaultWindowWidth())
--- a/Framework/Layers/GrayscaleFrameRenderer.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/GrayscaleFrameRenderer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -39,8 +39,7 @@
   public:
     GrayscaleFrameRenderer(Orthanc::ImageAccessor* frame,    // Takes ownership
                            const DicomFrameConverter& converter,
-                           const SliceGeometry& viewportSlice,
-                           const SliceGeometry& frameSlice,
+                           const CoordinateSystem3D& frameSlice,
                            double pixelSpacingX,
                            double pixelSpacingY,
                            bool isFullQuality);
--- a/Framework/Layers/ILayerRenderer.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/ILayerRenderer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -22,20 +22,26 @@
 #pragma once
 
 #include "../Viewport/CairoContext.h"
-#include "../Toolbox/IThreadSafety.h"
+#include "../Toolbox/CoordinateSystem3D.h"
 #include "../Toolbox/ViewportGeometry.h"
 #include "RenderStyle.h"
 
 namespace OrthancStone
 {
-  class ILayerRenderer : public IThreadUnsafe
+  class ILayerRenderer : public boost::noncopyable
   {
   public:
+    virtual ~ILayerRenderer()
+    {
+    }
+    
     virtual bool RenderLayer(CairoContext& context,
                              const ViewportGeometry& view) = 0;
 
     virtual void SetLayerStyle(const RenderStyle& style) = 0;
 
+    virtual const CoordinateSystem3D& GetLayerSlice() = 0;
+    
     virtual bool IsFullQuality() = 0;
   };
 }
--- a/Framework/Layers/ILayerRendererFactory.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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 "ILayerRenderer.h"
-#include "../Toolbox/SliceGeometry.h"
-#include "../Volumes/ISliceableVolume.h"
-
-namespace OrthancStone
-{
-  class ILayerRendererFactory : public IThreadUnsafe
-  {
-  public:
-    virtual ~ILayerRendererFactory()
-    {
-    }
-
-    virtual bool GetExtent(double& x1,
-                           double& y1,
-                           double& x2,
-                           double& y2,
-                           const SliceGeometry& displaySlice) = 0;
-
-    // This operation can be slow, as it might imply the download of a
-    // slice from Orthanc. The result might be NULL, if the slice is
-    // not compatible with the underlying source volume.
-    virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& displaySlice) = 0;
-
-    virtual bool HasSourceVolume() const = 0;
-
-    virtual ISliceableVolume& GetSourceVolume() const = 0;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/ILayerSource.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,73 @@
+/**
+ * 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 "ILayerRenderer.h"
+#include "../Toolbox/Slice.h"
+
+namespace OrthancStone
+{
+  class ILayerSource : public boost::noncopyable
+  {
+  public:
+    class IObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IObserver()
+      {
+      }
+
+      // Triggered as soon as the source has enough information to
+      // answer to "GetExtent()"
+      virtual void NotifyGeometryReady(const ILayerSource& source) = 0;
+      
+      virtual void NotifyGeometryError(const ILayerSource& source) = 0;
+      
+      // Triggered if the content of several slices in the source
+      // volume has changed
+      virtual void NotifyContentChange(const ILayerSource& source) = 0;
+
+      // Triggered if the content of some individual slice in the
+      // source volume has changed
+      virtual void NotifySliceChange(const ILayerSource& source,
+                                     const Slice& slice) = 0;
+ 
+      // The layer must be deleted by the observer that releases the
+      // std::auto_ptr
+      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
+                                    const ILayerSource& source,
+                                    const CoordinateSystem3D& slice,
+                                    bool isError) = 0;  // TODO Shouldn't this be separate as NotifyLayerError?
+    };
+    
+    virtual ~ILayerSource()
+    {
+    }
+
+    virtual void Register(IObserver& observer) = 0;
+
+    virtual bool GetExtent(std::vector<Vector>& points,
+                           const CoordinateSystem3D& viewportSlice) = 0;
+
+    virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/LayerSourceBase.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,87 @@
+/**
+ * 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 "LayerSourceBase.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  namespace
+  {
+    class LayerReadyFunctor : public boost::noncopyable
+    {
+    private:
+      std::auto_ptr<ILayerRenderer>  layer_;
+      const CoordinateSystem3D&      slice_;
+      bool                           isError_;
+      
+    public:
+      LayerReadyFunctor(ILayerRenderer* layer,
+                        const CoordinateSystem3D& slice,
+                        bool isError) :
+        layer_(layer),
+        slice_(slice),
+        isError_(isError)
+      {
+      }
+
+      void operator() (ILayerSource::IObserver& observer,
+                       const ILayerSource& source)
+      {
+        observer.NotifyLayerReady(layer_, source, slice_, isError_);
+      }
+    };
+  }
+
+  void LayerSourceBase::NotifyGeometryReady()
+  {
+    observers_.Apply(*this, &IObserver::NotifyGeometryReady);
+  }
+    
+  void LayerSourceBase::NotifyGeometryError()
+  {
+    observers_.Apply(*this, &IObserver::NotifyGeometryError);
+  }  
+    
+  void LayerSourceBase::NotifyContentChange()
+  {
+    observers_.Apply(*this, &IObserver::NotifyContentChange);
+  }
+
+  void LayerSourceBase::NotifySliceChange(const Slice& slice)
+  {
+    observers_.Apply(*this, &IObserver::NotifySliceChange, slice);
+  }
+
+  void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
+                                         const CoordinateSystem3D& slice,
+                                         bool isError)
+  {
+    LayerReadyFunctor functor(layer, slice, isError);
+    observers_.Notify(*this, functor);
+  }
+
+  void LayerSourceBase::Register(IObserver& observer)
+  {
+    observers_.Register(observer);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/LayerSourceBase.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,52 @@
+/**
+ * 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 "ILayerSource.h"
+#include "../Toolbox/ObserversRegistry.h"
+
+namespace OrthancStone
+{
+  class LayerSourceBase : public ILayerSource
+  {
+  private:
+    typedef ObserversRegistry<ILayerSource, IObserver>  Observers;
+
+    Observers  observers_;
+
+  protected:
+    void NotifyGeometryReady();
+    
+    void NotifyGeometryError();
+
+    void NotifyContentChange();
+
+    void NotifySliceChange(const Slice& slice);
+
+    void NotifyLayerReady(ILayerRenderer* layer,
+                          const CoordinateSystem3D& slice,
+                          bool isError);
+
+  public:
+    virtual void Register(IObserver& observer);
+  };
+}
--- a/Framework/Layers/LineLayerRenderer.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/LineLayerRenderer.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -26,11 +26,13 @@
   LineLayerRenderer::LineLayerRenderer(double x1,
                                        double y1,
                                        double x2,
-                                       double y2) : 
+                                       double y2,
+                                       const CoordinateSystem3D& slice) : 
     x1_(x1),
     y1_(y1),
     x2_(x2),
-    y2_(y2)
+    y2_(y2),
+    slice_(slice)
   {
     RenderStyle style;
     SetLayerStyle(style);
--- a/Framework/Layers/LineLayerRenderer.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/LineLayerRenderer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -28,24 +28,31 @@
   class LineLayerRenderer : public ILayerRenderer
   {
   private:
-    double   x1_;
-    double   y1_;
-    double   x2_;
-    double   y2_;
-    bool     visible_;
-    uint8_t  color_[3];
+    double              x1_;
+    double              y1_;
+    double              x2_;
+    double              y2_;
+    CoordinateSystem3D  slice_;
+    bool                visible_;
+    uint8_t             color_[3];
 
   public:
     LineLayerRenderer(double x1,
                       double y1,
                       double x2,
-                      double y2);
+                      double y2,
+                      const CoordinateSystem3D& slice);
 
     virtual bool RenderLayer(CairoContext& context,
                              const ViewportGeometry& view);
 
     virtual void SetLayerStyle(const RenderStyle& style);
 
+    virtual const CoordinateSystem3D& GetLayerSlice()
+    {
+      return slice_;
+    }
+    
     virtual bool IsFullQuality()
     {
       return true;
--- a/Framework/Layers/LineMeasureTracker.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/LineMeasureTracker.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -28,7 +28,7 @@
 namespace OrthancStone
 {
   LineMeasureTracker::LineMeasureTracker(IStatusBar* statusBar,
-                                         const SliceGeometry& slice,
+                                         const CoordinateSystem3D& slice,
                                          double x, 
                                          double y,
                                          uint8_t red,
--- a/Framework/Layers/LineMeasureTracker.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/LineMeasureTracker.h	Tue Mar 20 20:03:02 2018 +0100
@@ -24,25 +24,25 @@
 #include "../Widgets/IWorldSceneMouseTracker.h"
 
 #include "../Viewport/IStatusBar.h"
-#include "../Toolbox/SliceGeometry.h"
+#include "../Toolbox/CoordinateSystem3D.h"
 
 namespace OrthancStone
 {
   class LineMeasureTracker : public IWorldSceneMouseTracker
   {
   private:
-    IStatusBar*    statusBar_;
-    SliceGeometry  slice_;
-    double         x1_;
-    double         y1_;
-    double         x2_;
-    double         y2_;
-    uint8_t        color_[3];
-    unsigned int   fontSize_;
+    IStatusBar*         statusBar_;
+    CoordinateSystem3D  slice_;
+    double              x1_;
+    double              y1_;
+    double              x2_;
+    double              y2_;
+    uint8_t             color_[3];
+    unsigned int        fontSize_;
 
   public:
     LineMeasureTracker(IStatusBar* statusBar,
-                       const SliceGeometry& slice,
+                       const CoordinateSystem3D& slice,
                        double x, 
                        double y,
                        uint8_t red,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -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/>.
+ **/
+
+
+#include "OrthancFrameLayerSource.h"
+
+#include "FrameRenderer.h"
+#include "../Toolbox/DicomFrameConverter.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancStone
+{
+  void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader)
+  {
+    if (loader.GetSliceCount() > 0)
+    {
+      LayerSourceBase::NotifyGeometryReady();
+    }
+    else
+    {
+      LayerSourceBase::NotifyGeometryError();
+    }
+  }
+
+  void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader)
+  {
+    LayerSourceBase::NotifyGeometryError();
+  }
+
+  void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                                      unsigned int sliceIndex,
+                                                      const Slice& slice,
+                                                      std::auto_ptr<Orthanc::ImageAccessor>& image,
+                                                      SliceImageQuality quality)
+  {
+    bool isFull = (quality == SliceImageQuality_Full);
+    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull),
+                                      slice.GetGeometry(), false);
+  }
+
+  void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                                      unsigned int sliceIndex,
+                                                      const Slice& slice,
+                                                      SliceImageQuality quality)
+  {
+    LayerSourceBase::NotifyLayerReady(NULL, slice.GetGeometry(), true);
+  }
+
+
+  OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc) :
+    loader_(*this, orthanc),
+    quality_(SliceImageQuality_Full)
+  {
+  }
+
+  
+  void OrthancFrameLayerSource::LoadSeries(const std::string& seriesId)
+  {
+    loader_.ScheduleLoadSeries(seriesId);
+  }
+
+
+  void OrthancFrameLayerSource::LoadInstance(const std::string& instanceId)
+  {
+    loader_.ScheduleLoadInstance(instanceId);
+  }
+
+
+  void OrthancFrameLayerSource::LoadFrame(const std::string& instanceId,
+                                          unsigned int frame)
+  {
+    loader_.ScheduleLoadFrame(instanceId, frame);
+  }
+
+
+  bool OrthancFrameLayerSource::GetExtent(std::vector<Vector>& points,
+                                          const CoordinateSystem3D& viewportSlice)
+  {
+    size_t index;
+    if (loader_.IsGeometryReady() &&
+        loader_.LookupSlice(index, viewportSlice))
+    {
+      loader_.GetSlice(index).GetExtent(points);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  void OrthancFrameLayerSource::ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice)
+  {
+    size_t index;
+
+    if (loader_.IsGeometryReady())
+    {
+      if (loader_.LookupSlice(index, viewportSlice))
+      {
+        loader_.ScheduleLoadSliceImage(index, quality_);
+      }
+      else
+      {
+        Slice slice;
+        LayerSourceBase::NotifyLayerReady(NULL, slice.GetGeometry(), true);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,83 @@
+/**
+ * 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 "LayerSourceBase.h"
+#include "../Toolbox/IWebService.h"
+#include "../Toolbox/OrthancSlicesLoader.h"
+
+namespace OrthancStone
+{  
+  class OrthancFrameLayerSource :
+    public LayerSourceBase,
+    private OrthancSlicesLoader::ICallback
+  {
+  private:
+    OrthancSlicesLoader  loader_;
+    SliceImageQuality    quality_;
+
+    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader);
+
+    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader);
+
+    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
+                                       SliceImageQuality quality);
+
+    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       SliceImageQuality quality);
+
+  public:
+    OrthancFrameLayerSource(IWebService& 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;
+    }
+
+    size_t GetSliceCount() const
+    {
+      return loader_.GetSliceCount();
+    }
+
+    const Slice& GetSlice(size_t slice) const 
+    {
+      return loader_.GetSlice(slice);
+    }
+
+    virtual bool GetExtent(std::vector<Vector>& points,
+                           const CoordinateSystem3D& viewportSlice);
+
+    virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice);
+  };
+}
--- a/Framework/Layers/RenderStyle.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/RenderStyle.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,7 @@
 
 #include "RenderStyle.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
@@ -49,30 +49,15 @@
                                      float defaultCenter,
                                      float defaultWidth) const
   {
-    switch (windowing_)
+    if (windowing_ == ImageWindowing_Custom)
     {
-      case ImageWindowing_Default:
-        targetCenter = defaultCenter;
-        targetWidth = defaultWidth;
-        break;
-
-      case ImageWindowing_Bone:
-        targetCenter = 300;
-        targetWidth = 2000;
-        break;
-
-      case ImageWindowing_Lung:
-        targetCenter = -600;
-        targetWidth = 1600;
-        break;
-
-      case ImageWindowing_Custom:
-        targetCenter = customWindowCenter_;
-        targetWidth = customWindowWidth_;
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      targetCenter = customWindowCenter_;
+      targetWidth = customWindowWidth_;
+    }
+    else
+    {
+      return ::OrthancStone::ComputeWindowing
+        (targetCenter, targetWidth, windowing_, defaultCenter, defaultWidth);
     }
   }
 
--- a/Framework/Layers/RenderStyle.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/RenderStyle.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../Enumerations.h"
+#include "../StoneEnumerations.h"
 
 #include <EmbeddedResources.h>
 
--- a/Framework/Layers/SeriesFrameRendererFactory.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/SeriesFrameRendererFactory.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -22,11 +22,12 @@
 #include "SeriesFrameRendererFactory.h"
 
 #include "FrameRenderer.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/Toolbox.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Logging.h>
+#include <Core/Toolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginException.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
 
 
 namespace OrthancStone
@@ -155,7 +156,6 @@
     {
       SliceGeometry frameSlice(*currentDataset_);
       return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), 
-                                           viewportSlice, 
                                            frameSlice,
                                            *currentDataset_, 
                                            spacingX, spacingY,
--- a/Framework/Layers/SiblingSliceLocationFactory.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/SiblingSliceLocationFactory.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -49,7 +49,6 @@
 
   void SiblingSliceLocationFactory::SetLayerIndex(size_t layerIndex)
   {
-    boost::mutex::scoped_lock lock(mutex_);
     hasLayerIndex_ = true;
     layerIndex_ = layerIndex;
   }
@@ -57,21 +56,18 @@
 
   void SiblingSliceLocationFactory::SetStyle(const RenderStyle& style)
   {
-    boost::mutex::scoped_lock lock(mutex_);
     style_ = style;
   }
 
 
   RenderStyle SiblingSliceLocationFactory::GetRenderStyle()
   {
-    boost::mutex::scoped_lock lock(mutex_);
     return style_;
   }
 
 
   void SiblingSliceLocationFactory::SetSlice(const SliceGeometry& slice)
   {
-    boost::mutex::scoped_lock lock(mutex_);
     slice_ = slice;
 
     if (hasLayerIndex_)
@@ -84,21 +80,14 @@
   ILayerRenderer* SiblingSliceLocationFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
   {
     Vector p, d;
-    RenderStyle style;
 
+    // Compute the line of intersection between the two slices
+    if (!GeometryToolbox::IntersectTwoPlanes(p, d, 
+                                             slice_.GetOrigin(), slice_.GetNormal(),
+                                             viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
     {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      style = style_;
-
-      // Compute the line of intersection between the two slices
-      if (!GeometryToolbox::IntersectTwoPlanes(p, d, 
-                                               slice_.GetOrigin(), slice_.GetNormal(),
-                                               viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
-      {
-        // The two slice are parallel, don't try and display the intersection
-        return NULL;
-      }
+      // The two slice are parallel, don't try and display the intersection
+      return NULL;
     }
 
     double x1, y1, x2, y2;
@@ -113,7 +102,7 @@
                                              sx1, sy1, sx2, sy2))
     {
       std::auto_ptr<ILayerRenderer> layer(new LineLayerRenderer(x1, y1, x2, y2));
-      layer->SetLayerStyle(style);
+      layer->SetLayerStyle(style_);
       return layer.release();
     }
     else
--- a/Framework/Layers/SiblingSliceLocationFactory.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/SiblingSliceLocationFactory.h	Tue Mar 20 20:03:02 2018 +0100
@@ -30,7 +30,6 @@
     public LayeredSceneWidget::ISliceObserver
   {
   private:
-    boost::mutex          mutex_;
     LayeredSceneWidget&   owner_;
     LayeredSceneWidget&   sibling_;
     SliceGeometry         slice_;
--- a/Framework/Layers/SingleFrameRendererFactory.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/SingleFrameRendererFactory.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -23,11 +23,12 @@
 
 #include "FrameRenderer.h"
 #include "../Toolbox/MessagingToolbox.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
 #include "../Toolbox/DicomFrameConverter.h"
 
+#include <Core/OrthancException.h>
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
+
 namespace OrthancStone
 {
   SingleFrameRendererFactory::SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc,
@@ -76,7 +77,7 @@
   {
     SliceGeometry frameSlice(*dicom_);
     return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), 
-                                         viewportSlice, frameSlice, *dicom_, 1, 1, true);
+                                         frameSlice, *dicom_, 1, 1, true);
   }
 
 
--- a/Framework/Layers/SingleFrameRendererFactory.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Layers/SingleFrameRendererFactory.h	Tue Mar 20 20:03:02 2018 +0100
@@ -22,7 +22,7 @@
 #pragma once
 
 #include "ILayerRendererFactory.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
+#include <Plugins/Samples/Common/IOrthancConnection.h>
 
 namespace OrthancStone
 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Layers/SliceOutlineRenderer.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,54 @@
+/**
+ * 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 "SliceOutlineRenderer.h"
+
+namespace OrthancStone
+{
+  bool SliceOutlineRenderer::RenderLayer(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/Layers/SliceOutlineRenderer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,67 @@
+/**
+ * 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 "ILayerRenderer.h"
+#include "../Toolbox/Slice.h"
+
+namespace OrthancStone
+{
+  class SliceOutlineRenderer : public ILayerRenderer
+  {
+  private:
+    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(CairoContext& context,
+                             const ViewportGeometry& view);
+
+    virtual void SetLayerStyle(const RenderStyle& style)
+    {
+      style_ = style;
+    }
+
+    virtual const CoordinateSystem3D& GetLayerSlice()
+    {
+      return geometry_;
+    }
+
+    virtual bool IsFullQuality()
+    {
+      return true;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/StoneEnumerations.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,75 @@
+/**
+ * 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 "StoneEnumerations.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{  
+  bool StringToSopClassUid(SopClassUid& result,
+                           const std::string& source)
+  {
+    std::string s = Orthanc::Toolbox::StripSpaces(source);
+
+    if (s == "1.2.840.10008.5.1.4.1.1.481.2")
+    {
+      result = SopClassUid_RTDose;
+      return true;
+    }
+    else
+    {
+      //LOG(INFO) << "Unknown SOP class UID: " << source;
+      return false;
+    }
+  }  
+
+
+  void ComputeWindowing(float& targetCenter,
+                        float& targetWidth,
+                        ImageWindowing windowing,
+                        float defaultCenter,
+                        float defaultWidth)
+  {
+    switch (windowing)
+    {
+      case ImageWindowing_Default:
+        targetCenter = defaultCenter;
+        targetWidth = defaultWidth;
+        break;
+
+      case ImageWindowing_Bone:
+        targetCenter = 300;
+        targetWidth = 2000;
+        break;
+
+      case ImageWindowing_Lung:
+        targetCenter = -600;
+        targetWidth = 1600;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/StoneEnumerations.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,99 @@
+/**
+ * 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 <string>
+
+namespace OrthancStone
+{
+  enum SliceOffsetMode
+  {
+    SliceOffsetMode_Absolute,
+    SliceOffsetMode_Relative,
+    SliceOffsetMode_Loop
+  };
+
+  enum ImageWindowing
+  {
+    ImageWindowing_Default,
+    ImageWindowing_Bone,
+    ImageWindowing_Lung,
+    ImageWindowing_Custom
+  };
+
+  enum MouseButton
+  {
+    MouseButton_Left,
+    MouseButton_Right,
+    MouseButton_Middle
+  };
+
+  enum MouseWheelDirection
+  {
+    MouseWheelDirection_Up,
+    MouseWheelDirection_Down
+  };
+
+  enum VolumeProjection
+  {
+    VolumeProjection_Axial,
+    VolumeProjection_Coronal,
+    VolumeProjection_Sagittal
+  };
+
+  enum ImageInterpolation
+  {
+    ImageInterpolation_Nearest,
+    ImageInterpolation_Bilinear,
+    ImageInterpolation_Trilinear
+  };
+
+  enum KeyboardModifiers
+  {
+    KeyboardModifiers_None = 0,
+    KeyboardModifiers_Shift = (1 << 0),
+    KeyboardModifiers_Control = (1 << 1),
+    KeyboardModifiers_Alt = (1 << 2)
+  };
+
+  enum SliceImageQuality
+  {
+    SliceImageQuality_Full,
+    SliceImageQuality_Jpeg50,
+    SliceImageQuality_Jpeg90,
+    SliceImageQuality_Jpeg95
+  };
+
+  enum SopClassUid
+  {
+    SopClassUid_RTDose
+  };
+
+  bool StringToSopClassUid(SopClassUid& result,
+                           const std::string& source);
+
+  void ComputeWindowing(float& targetCenter,
+                        float& targetWidth,
+                        ImageWindowing windowing,
+                        float defaultCenter,
+                        float defaultWidth);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/CoordinateSystem3D.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,190 @@
+/**
+ * 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 "CoordinateSystem3D.h"
+
+#include "LinearAlgebra.h"
+#include "GeometryToolbox.h"
+
+#include <Core/Logging.h>
+#include <Core/Toolbox.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void CoordinateSystem3D::CheckAndComputeNormal()
+  {
+    // DICOM expects normal vectors to define the axes: "The row and
+    // column direction cosine vectors shall be normal, i.e., the dot
+    // product of each direction cosine vector with itself shall be
+    // unity."
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
+    if (!LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(axisX_), 1.0) ||
+        !LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(axisY_), 1.0))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    // The vectors within "Image Orientation Patient" must be
+    // orthogonal, according to the DICOM specification: "The row and
+    // column direction cosine vectors shall be orthogonal, i.e.,
+    // their dot product shall be zero."
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
+    if (!LinearAlgebra::IsCloseToZero(boost::numeric::ublas::inner_prod(axisX_, axisY_)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    LinearAlgebra::CrossProduct(normal_, axisX_, axisY_);
+
+    d_ = -(normal_[0] * origin_[0] + normal_[1] * origin_[1] + normal_[2] * origin_[2]);
+
+    // Just a sanity check, it should be useless by construction
+    assert(LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(normal_), 1.0));
+  }
+
+
+  void CoordinateSystem3D::SetupCanonical()
+  {
+    LinearAlgebra::AssignVector(origin_, 0, 0, 0);
+    LinearAlgebra::AssignVector(axisX_, 1, 0, 0);
+    LinearAlgebra::AssignVector(axisY_, 0, 1, 0);
+    CheckAndComputeNormal();
+  }
+
+
+  CoordinateSystem3D::CoordinateSystem3D(const Vector& origin,
+                                         const Vector& axisX,
+                                         const Vector& axisY) :
+    origin_(origin),
+    axisX_(axisX),
+    axisY_(axisY)
+  {
+    CheckAndComputeNormal();
+  }
+
+
+  void CoordinateSystem3D::Setup(const std::string& imagePositionPatient,
+                                 const std::string& imageOrientationPatient)
+  {
+    std::string tmpPosition = Orthanc::Toolbox::StripSpaces(imagePositionPatient);
+    std::string tmpOrientation = Orthanc::Toolbox::StripSpaces(imageOrientationPatient);
+
+    Vector orientation;
+    if (!LinearAlgebra::ParseVector(origin_, tmpPosition) ||
+        !LinearAlgebra::ParseVector(orientation, tmpOrientation) ||
+        origin_.size() != 3 ||
+        orientation.size() != 6)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    axisX_.resize(3);
+    axisX_[0] = orientation[0];
+    axisX_[1] = orientation[1];
+    axisX_[2] = orientation[2];
+
+    axisY_.resize(3);
+    axisY_[0] = orientation[3];
+    axisY_[1] = orientation[4];
+    axisY_[2] = orientation[5];
+
+    CheckAndComputeNormal();
+  }   
+
+
+  CoordinateSystem3D::CoordinateSystem3D(const OrthancPlugins::IDicomDataset& dicom)
+  {
+    std::string a, b;
+
+    if (dicom.GetStringValue(a, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) &&
+        dicom.GetStringValue(b, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT))
+    {
+      Setup(a, b);
+    }
+    else
+    {
+      SetupCanonical();
+    }
+  }
+
+
+  CoordinateSystem3D::CoordinateSystem3D(const Orthanc::DicomMap& dicom)
+  {
+    std::string a, b;
+
+    if (dicom.CopyToString(a, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
+        dicom.CopyToString(b, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
+    {
+      Setup(a, b);
+    }
+    else
+    {
+      SetupCanonical();
+    }    
+  }
+
+
+  Vector CoordinateSystem3D::MapSliceToWorldCoordinates(double x,
+                                                        double y) const
+  {
+    return origin_ + x * axisX_ + y * axisY_;
+  }
+
+
+  double CoordinateSystem3D::ProjectAlongNormal(const Vector& point) const
+  {
+    return boost::numeric::ublas::inner_prod(point, normal_);
+  }
+
+
+  void CoordinateSystem3D::ProjectPoint(double& offsetX,
+                                        double& offsetY,
+                                        const Vector& point) const
+  {
+    // Project the point onto the slice
+    Vector projection;
+    GeometryToolbox::ProjectPointOntoPlane(projection, point, normal_, origin_);
+
+    // As the axes are orthonormal vectors thanks to
+    // CheckAndComputeNormal(), the following dot products give the
+    // offset of the origin of the slice wrt. the origin of the
+    // reference plane https://en.wikipedia.org/wiki/Vector_projection
+    offsetX = boost::numeric::ublas::inner_prod(axisX_, projection - origin_);
+    offsetY = boost::numeric::ublas::inner_prod(axisY_, projection - origin_);
+  }
+
+
+  bool CoordinateSystem3D::IntersectSegment(Vector& p,
+                                            const Vector& edgeFrom,
+                                            const Vector& edgeTo) const
+  {
+    return GeometryToolbox::IntersectPlaneAndSegment(p, normal_, d_, edgeFrom, edgeTo);
+  }
+
+
+  bool CoordinateSystem3D::IntersectLine(Vector& p,
+                                         const Vector& origin,
+                                         const Vector& direction) const
+  {
+    return GeometryToolbox::IntersectPlaneAndLine(p, normal_, d_, origin, direction);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/CoordinateSystem3D.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,106 @@
+/**
+ * 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 "LinearAlgebra.h"
+
+#include <Plugins/Samples/Common/IDicomDataset.h>
+
+namespace OrthancStone
+{
+  // Geometry of a 3D plane
+  class CoordinateSystem3D
+  {
+  private:
+    Vector    origin_;
+    Vector    normal_;
+    Vector    axisX_;
+    Vector    axisY_;
+    double    d_;
+
+    void CheckAndComputeNormal();
+
+    void Setup(const std::string& imagePositionPatient,
+               const std::string& imageOrientationPatient);
+
+    void SetupCanonical();
+
+    double GetOffset() const;
+
+  public:
+    CoordinateSystem3D()
+    {
+      SetupCanonical();
+    }
+
+    CoordinateSystem3D(const Vector& origin,
+                       const Vector& axisX,
+                       const Vector& axisY);
+
+    CoordinateSystem3D(const OrthancPlugins::IDicomDataset& dicom);
+
+    CoordinateSystem3D(const std::string& imagePositionPatient,
+                       const std::string& imageOrientationPatient)
+    {
+      Setup(imagePositionPatient, imageOrientationPatient);
+    }
+
+    CoordinateSystem3D(const Orthanc::DicomMap& dicom);
+
+    const Vector& GetNormal() const
+    {
+      return normal_;
+    }
+
+    const Vector& GetOrigin() const
+    {
+      return origin_;
+    }
+
+    const Vector& GetAxisX() const
+    {
+      return axisX_;
+    }
+
+    const Vector& GetAxisY() const
+    {
+      return axisY_;
+    }
+
+    Vector MapSliceToWorldCoordinates(double x,
+                                      double y) const;
+    
+    double ProjectAlongNormal(const Vector& point) const;
+
+    void ProjectPoint(double& offsetX,
+                      double& offsetY,
+                      const Vector& point) const;
+
+    bool IntersectSegment(Vector& p,
+                          const Vector& edgeFrom,
+                          const Vector& edgeTo) const;
+
+    bool IntersectLine(Vector& p,
+                       const Vector& origin,
+                       const Vector& direction) const;
+  };
+}
--- a/Framework/Toolbox/DicomFrameConverter.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/DicomFrameConverter.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,12 +21,12 @@
 
 #include "DicomFrameConverter.h"
 
-#include "GeometryToolbox.h"
+#include "LinearAlgebra.h"
 
-#include "../../Resources/Orthanc/Core/Images/Image.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Toolbox.h"
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
 
 namespace OrthancStone
 {
@@ -39,36 +39,17 @@
     rescaleSlope_ = 1;
     defaultWindowCenter_ = 128;
     defaultWindowWidth_ = 256;
+    expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
   }
 
 
-  Orthanc::PixelFormat DicomFrameConverter::GetExpectedPixelFormat() const
-  {
-    // TODO Add more checks, e.g. on the number of bytes per value
-    // (cf. DicomImageInformation.h in Orthanc)
-
-    if (isColor_)
-    {
-      return Orthanc::PixelFormat_RGB24;
-    }
-    else if (isSigned_)
-    {
-      return Orthanc::PixelFormat_SignedGrayscale16;
-    }
-    else
-    {
-      return Orthanc::PixelFormat_Grayscale16;
-    }
-  }
-
-
-  void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom)
+  void DicomFrameConverter::ReadParameters(const Orthanc::DicomMap& dicom)
   {
     SetDefaultParameters();
 
     Vector c, w;
-    if (GeometryToolbox::ParseVector(c, dicom, OrthancPlugins::DICOM_TAG_WINDOW_CENTER) &&
-        GeometryToolbox::ParseVector(w, dicom, OrthancPlugins::DICOM_TAG_WINDOW_WIDTH) &&
+    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)
     {
@@ -76,10 +57,8 @@
       defaultWindowWidth_ = static_cast<float>(w[0]);
     }
 
-    OrthancPlugins::DicomDatasetReader reader(dicom);
-
-    int tmp;
-    if (!reader.GetIntegerValue(tmp, OrthancPlugins::DICOM_TAG_PIXEL_REPRESENTATION))
+    int32_t tmp;
+    if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION))
     {
       // Type 1 tag, must be present
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
@@ -87,17 +66,75 @@
 
     isSigned_ = (tmp == 1);
 
-    if (reader.GetFloatValue(rescaleIntercept_, OrthancPlugins::DICOM_TAG_RESCALE_INTERCEPT) &&
-        reader.GetFloatValue(rescaleSlope_, OrthancPlugins::DICOM_TAG_RESCALE_SLOPE))
+    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;
 
-    // Type 1 tag, must be present
-    std::string photometric = reader.GetMandatoryStringValue(OrthancPlugins::DICOM_TAG_PHOTOMETRIC_INTERPRETATION);
-    photometric = Orthanc::Toolbox::StripSpaces(photometric);
+      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);
+    }
+    
     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;
+      }
+    }
   }
 
 
@@ -124,6 +161,7 @@
     }
 
     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.
@@ -136,25 +174,51 @@
     source.reset(NULL);  // We don't need the source frame anymore
 
     // Correct rescale slope/intercept if need be
+    ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32);
+      
+    source = converted;
+  }
+
+
+  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 < converted->GetHeight(); y++)
+      for (unsigned int y = 0; y < image.GetHeight(); y++)
       {
-        float* p = reinterpret_cast<float*>(converted->GetRow(y));
-        for (unsigned int x = 0; x < converted->GetWidth(); x++, p++)
-        {
-          float value = *p;
+        float* p = reinterpret_cast<float*>(image.GetRow(y));
 
-          if (hasRescale_)
+        if (useDouble)
+        {
+          // Slower, accurate implementation using double
+          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
           {
-            value = value * rescaleSlope_ + rescaleIntercept_;
+            double value = static_cast<double>(*p);
+            *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
           }
-            
-          *p = value;
+        }
+        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_);
+          }
         }
       }
     }
-      
-    source = converted;
   }
+
+  
+  double DicomFrameConverter::Apply(double x) const
+  {
+    return x * rescaleSlope_ + rescaleIntercept_;
+  }
+
 }
--- a/Framework/Toolbox/DicomFrameConverter.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/DicomFrameConverter.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #pragma once
 
-#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h"
+#include <Core/DicomFormat/DicomMap.h>
+#include <Core/Images/ImageAccessor.h>
 
 #include <memory>
 
@@ -41,10 +41,12 @@
     bool    isSigned_;
     bool    isColor_;
     bool    hasRescale_;
-    float   rescaleIntercept_;
-    float   rescaleSlope_;
-    float   defaultWindowCenter_;
-    float   defaultWindowWidth_;
+    double  rescaleIntercept_;
+    double  rescaleSlope_;
+    double  defaultWindowCenter_;
+    double  defaultWindowWidth_;
+
+    Orthanc::PixelFormat  expectedPixelFormat_;
 
     void SetDefaultParameters();
 
@@ -54,20 +56,38 @@
       SetDefaultParameters();
     }
 
-    Orthanc::PixelFormat GetExpectedPixelFormat() const;
+    Orthanc::PixelFormat GetExpectedPixelFormat() const
+    {
+      return expectedPixelFormat_;
+    }
 
-    void ReadParameters(const OrthancPlugins::IDicomDataset& dicom);
+    void ReadParameters(const Orthanc::DicomMap& dicom);
 
-    float GetDefaultWindowCenter() const
+    double GetDefaultWindowCenter() const
     {
       return defaultWindowCenter_;
     }
     
-    float GetDefaultWindowWidth() const
+    double GetDefaultWindowWidth() const
     {
       return defaultWindowWidth_;
     }
 
-    void ConvertFrame(std::auto_ptr<Orthanc::ImageAccessor>& source) const;  
+    double GetRescaleIntercept() const
+    {
+      return rescaleIntercept_;
+    }
+      
+    double GetRescaleSlope() const
+    {
+      return rescaleSlope_;
+    }
+
+    void ConvertFrame(std::auto_ptr<Orthanc::ImageAccessor>& source) const;
+
+    void ApplyRescale(Orthanc::ImageAccessor& image,
+                      bool useDouble) const;
+
+    double Apply(double x) const;
   };
 }
--- a/Framework/Toolbox/DicomStructureSet.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,19 +21,77 @@
 
 #include "DicomStructureSet.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
+#include "../Toolbox/GeometryToolbox.h"
 #include "../Toolbox/MessagingToolbox.h"
 
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
+#include <Plugins/Samples/Common/DicomDatasetReader.h>
+
+#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>
+#include <boost/geometry/multi/geometries/multi_polygon.hpp>
+
+typedef boost::geometry::model::d2::point_xy<double> BoostPoint;
+typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon;
+typedef boost::geometry::model::multi_polygon<BoostPolygon>  BoostMultiPolygon;
+
+
+static void Union(BoostMultiPolygon& output,
+                  std::vector<BoostPolygon>& input)
+{
+  for (size_t i = 0; i < input.size(); i++)
+  {
+    boost::geometry::correct(input[i]);
+  }
+  
+  if (input.size() == 0)
+  {
+    output.clear();
+  }
+  else if (input.size() == 1)
+  {
+    output.resize(1);
+    output[0] = input[0];
+  }
+  else
+  {
+    boost::geometry::union_(input[0], input[1], output);
+
+    for (size_t i = 0; i < input.size(); i++)
+    {
+      BoostMultiPolygon tmp;
+      boost::geometry::union_(output, input[i], tmp);
+      output = tmp;
+    }
+  }
+}
+
+
+static BoostPolygon CreateRectangle(float x1, float y1,
+                                    float x2, float y2)
+{
+  BoostPolygon r;
+  boost::geometry::append(r, BoostPoint(x1, y1));
+  boost::geometry::append(r, BoostPoint(x1, y2));
+  boost::geometry::append(r, BoostPoint(x2, y2));
+  boost::geometry::append(r, BoostPoint(x2, y1));
+  return r;
+}
+
+
 
 namespace OrthancStone
 {
   static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
   static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016);
   static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040);
+  static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
   static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046);
   static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
   static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039);
@@ -61,121 +119,256 @@
   }
 
 
-  SliceGeometry DicomStructureSet::ExtractSliceGeometry(double& sliceThickness,
-                                                        OrthancPlugins::IOrthancConnection& orthanc,
-                                                        const OrthancPlugins::IDicomDataset& tags,
-                                                        size_t contourIndex,
-                                                        size_t sliceIndex)
+  static bool ParseVector(Vector& target,
+                          const OrthancPlugins::IDicomDataset& dataset,
+                          const OrthancPlugins::DicomPath& tag)
   {
-    using namespace OrthancPlugins;
-
-    size_t size;
-    if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex,
-                                              DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex,
-                                              DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) ||
-        size != 1)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
-    }
+    std::string value;
+    return (dataset.GetStringValue(value, tag) &&
+            LinearAlgebra::ParseVector(target, value));
+  }
 
-    DicomDatasetReader reader(tags);
-    std::string parentUid = reader.GetMandatoryStringValue(DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex,
-                                                                     DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex,
-                                                                     DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
-                                                                     DICOM_TAG_REFERENCED_SOP_INSTANCE_UID));
 
-    Json::Value parentLookup;
-    MessagingToolbox::RestApiPost(parentLookup, orthanc, "/tools/lookup", parentUid);
-
-    if (parentLookup.type() != Json::arrayValue ||
-        parentLookup.size() != 1 ||
-        !parentLookup[0].isMember("Type") ||
-        !parentLookup[0].isMember("Path") ||
-        parentLookup[0]["Type"].type() != Json::stringValue ||
-        parentLookup[0]["ID"].type() != Json::stringValue ||
-        parentLookup[0]["Type"].asString() != "Instance")
+  void DicomStructureSet::Polygon::CheckPoint(const Vector& v)
+  {
+    if (hasSlice_)
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
+      if (!LinearAlgebra::IsNear(GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal()),
+                                 projectionAlongNormal_, 
+                                 sliceThickness_ / 2.0 /* in mm */))
+      {
+        LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+      }
     }
-
-    Json::Value parentInstance;
-    MessagingToolbox::RestApiGet(parentInstance, orthanc, "/instances/" + parentLookup[0]["ID"].asString());
+  }
 
-    if (parentInstance.type() != Json::objectValue ||
-        !parentInstance.isMember("ParentSeries") ||
-        parentInstance["ParentSeries"].type() != Json::stringValue)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);          
-    }
-
-    std::string parentSeriesId = parentInstance["ParentSeries"].asString();
-    bool isFirst = parentSeriesId_.empty();
 
-    if (isFirst)
-    {
-      parentSeriesId_ = parentSeriesId;
-    }
-    else if (parentSeriesId_ != parentSeriesId)
+  void DicomStructureSet::Polygon::AddPoint(const Vector& v)
+  {
+    CheckPoint(v);
+    points_.push_back(v);
+  }
+
+
+  bool DicomStructureSet::Polygon::UpdateReferencedSlice(const ReferencedSlices& slices)
+  {
+    if (hasSlice_)
     {
-      LOG(ERROR) << "This RT-STRUCT refers to several different series";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    FullOrthancDataset parentTags(orthanc, "/instances/" + parentLookup[0]["ID"].asString() + "/tags");
-    SliceGeometry slice(parentTags);
-
-    Vector v;
-    if (GeometryToolbox::ParseVector(v, parentTags, DICOM_TAG_SLICE_THICKNESS) &&
-        v.size() > 0)
-    {
-      sliceThickness = v[0];
+      return true;
     }
     else
     {
-      sliceThickness = 1;  // 1 mm by default
-    }
+      ReferencedSlices::const_iterator it = slices.find(sopInstanceUid_);
+      
+      if (it == slices.end())
+      {
+        return false;
+      }
+      else
+      {
+        const CoordinateSystem3D& geometry = it->second.geometry_;
+        
+        hasSlice_ = true;
+        geometry_ = geometry;
+        projectionAlongNormal_ = GeometryToolbox::ProjectAlongNormal(geometry.GetOrigin(), geometry.GetNormal());
+        sliceThickness_ = it->second.thickness_;
 
-    if (isFirst)
-    {
-      normal_ = slice.GetNormal();
+        extent_.Reset();
+        
+        for (Points::const_iterator it = points_.begin(); it != points_.end(); ++it)
+        {
+          CheckPoint(*it);
+
+          double x, y;
+          geometry.ProjectPoint(x, y, *it);
+          extent_.AddPoint(x, y);
+        }
+
+        return true;
+      }
     }
-    else if (!GeometryToolbox::IsParallel(normal_, slice.GetNormal()))
-    {
-      LOG(ERROR) << "Incompatible orientation of slices in this RT-STRUCT";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    return slice;
   }
 
 
+  bool DicomStructureSet::Polygon::IsOnSlice(const CoordinateSystem3D& slice) const
+  {
+    bool isOpposite;
+
+    if (points_.empty() ||
+        !hasSlice_ ||
+        !GeometryToolbox::IsParallelOrOpposite(isOpposite, slice.GetNormal(), geometry_.GetNormal()))
+    {
+      return false;
+    }
+    
+    double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), geometry_.GetNormal());
+
+    return (LinearAlgebra::IsNear(d, projectionAlongNormal_,
+                                  sliceThickness_ / 2.0));
+  }
+
+  
+  bool DicomStructureSet::Polygon::Project(double& x1,
+                                           double& y1,
+                                           double& x2,
+                                           double& y2,
+                                           const CoordinateSystem3D& slice) const
+  {
+    // TODO Optimize this method using a sweep-line algorithm for polygons
+    
+    if (!hasSlice_ ||
+        points_.size() <= 1)
+    {
+      return false;
+    }
+
+    double x, y;
+    geometry_.ProjectPoint(x, y, slice.GetOrigin());
+      
+    bool isOpposite;
+    if (GeometryToolbox::IsParallelOrOpposite
+        (isOpposite, slice.GetNormal(), geometry_.GetAxisY()))
+    {
+      if (y < extent_.GetY1() ||
+          y > extent_.GetY2())
+      {
+        // The polygon does not intersect the input slice
+        return false;
+      }
+        
+      bool isFirst = true;
+      double xmin = std::numeric_limits<double>::infinity();
+      double xmax = -std::numeric_limits<double>::infinity();
+
+      double prevX, prevY;
+      geometry_.ProjectPoint(prevX, prevY, points_[points_.size() - 1]);
+        
+      for (size_t i = 0; i < points_.size(); i++)
+      {
+        // Reference: ../../Resources/Computations/IntersectSegmentAndHorizontalLine.py
+        double curX, curY;
+        geometry_.ProjectPoint(curX, curY, points_[i]);
+
+        if ((prevY < y && curY > y) ||
+            (prevY > y && curY < y))
+        {
+          double p = (curX * prevY - curY * prevX + y * (prevX - curX)) / (prevY - curY);
+          xmin = std::min(xmin, p);
+          xmax = std::max(xmax, p);
+          isFirst = false;
+        }
+
+        prevX = curX;
+        prevY = curY;
+      }
+        
+      if (isFirst)
+      {
+        return false;
+      }
+      else
+      {
+        Vector p1 = (geometry_.MapSliceToWorldCoordinates(xmin, y) +
+                     sliceThickness_ / 2.0 * geometry_.GetNormal());
+        Vector p2 = (geometry_.MapSliceToWorldCoordinates(xmax, y) -
+                     sliceThickness_ / 2.0 * geometry_.GetNormal());
+          
+        slice.ProjectPoint(x1, y1, p1);
+        slice.ProjectPoint(x2, y2, p2);
+        return true;
+      }
+    }
+    else if (GeometryToolbox::IsParallelOrOpposite
+             (isOpposite, slice.GetNormal(), geometry_.GetAxisX()))
+    {
+      if (x < extent_.GetX1() ||
+          x > extent_.GetX2())
+      {
+        return false;
+      }
+
+      bool isFirst = true;
+      double ymin = std::numeric_limits<double>::infinity();
+      double ymax = -std::numeric_limits<double>::infinity();
+
+      double prevX, prevY;
+      geometry_.ProjectPoint(prevX, prevY, points_[points_.size() - 1]);
+        
+      for (size_t i = 0; i < points_.size(); i++)
+      {
+        // Reference: ../../Resources/Computations/IntersectSegmentAndVerticalLine.py
+        double curX, curY;
+        geometry_.ProjectPoint(curX, curY, points_[i]);
+
+        if ((prevX < x && curX > x) ||
+            (prevX > x && curX < x))
+        {
+          double p = (curX * prevY - curY * prevX + x * (curY - prevY)) / (curX - prevX);
+          ymin = std::min(ymin, p);
+          ymax = std::max(ymax, p);
+          isFirst = false;
+        }
+
+        prevX = curX;
+        prevY = curY;
+      }
+        
+      if (isFirst)
+      {
+        return false;
+      }
+      else
+      {
+        Vector p1 = (geometry_.MapSliceToWorldCoordinates(x, ymin) +
+                     sliceThickness_ / 2.0 * geometry_.GetNormal());
+        Vector p2 = (geometry_.MapSliceToWorldCoordinates(x, ymax) -
+                     sliceThickness_ / 2.0 * geometry_.GetNormal());
+
+        slice.ProjectPoint(x1, y1, p1);
+        slice.ProjectPoint(x2, y2, p2);
+
+        // TODO WHY THIS???
+        y1 = -y1;
+        y2 = -y2;
+
+        return true;
+      }
+    }
+    else
+    {
+      // Should not happen
+      return false;
+    }
+  }
+
+  
   const DicomStructureSet::Structure& DicomStructureSet::GetStructure(size_t index) const
   {
     if (index >= structures_.size())
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
     return structures_[index];
   }
 
 
-  bool DicomStructureSet::IsPolygonOnSlice(const Polygon& polygon,
-                                           const SliceGeometry& geometry) const
+  DicomStructureSet::Structure& DicomStructureSet::GetStructure(size_t index)
   {
-    double d = boost::numeric::ublas::inner_prod(geometry.GetOrigin(), normal_);
+    if (index >= structures_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
 
-    return (GeometryToolbox::IsNear(d, polygon.projectionAlongNormal_, polygon.sliceThickness_ / 2.0) &&
-            !polygon.points_.empty());
+    return structures_[index];
   }
 
 
-  DicomStructureSet::DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc,
-                                       const std::string& instanceId)
+  DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags)
   {
     using namespace OrthancPlugins;
 
-    FullOrthancDataset tags(orthanc, "/instances/" + instanceId + "/tags");
     DicomDatasetReader reader(tags);
     
     size_t count, tmp;
@@ -191,17 +384,19 @@
     structures_.resize(count);
     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),
-                                                             "No interpretation");
+      structures_[i].interpretation_ = reader.GetStringValue
+        (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),
-                                                   "No interpretation");
+      structures_[i].name_ = reader.GetStringValue
+        (DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
+                   DICOM_TAG_ROI_NAME),
+         "No interpretation");
 
       Vector color;
-      if (GeometryToolbox::ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
-                                                              DICOM_TAG_ROI_DISPLAY_COLOR)) &&
+      if (ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                             DICOM_TAG_ROI_DISPLAY_COLOR)) &&
           color.size() == 3)
       {
         structures_[i].red_ = ConvertColor(color[0]);
@@ -219,7 +414,7 @@
       if (!tags.GetSequenceSize(countSlices, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
                                                        DICOM_TAG_CONTOUR_SEQUENCE)))
       {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        countSlices = 0;
       }
 
       LOG(WARNING) << "New RT structure: \"" << structures_[i].name_ 
@@ -233,42 +428,55 @@
       {
         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)))
+        if (!reader.GetUnsignedIntegerValue
+            (countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                    DICOM_TAG_CONTOUR_SEQUENCE, j,
+                                    DICOM_TAG_NUMBER_OF_CONTOUR_POINTS)))
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
         }
             
-        LOG(INFO) << "Parsing slice containing " << countPoints << " vertices";
+        //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));
+        std::string type = reader.GetMandatoryStringValue
+          (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                     DICOM_TAG_CONTOUR_SEQUENCE, j,
+                     DICOM_TAG_CONTOUR_GEOMETRIC_TYPE));
         if (type != "CLOSED_PLANAR")
         {
-          LOG(ERROR) << "Cannot handle contour with geometry type: " << type;
+          LOG(WARNING) << "Ignoring contour with geometry type: " << type;
+          continue;
+        }
+
+        size_t size;
+        if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+                                                  DICOM_TAG_CONTOUR_SEQUENCE, j,
+                                                  DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) ||
+            size != 1)
+        {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
         }
 
-        // The "CountourData" tag (3006,0050) is too large to be
-        // returned by the "/instances/{id}/tags" URI: Access it using
-        // the raw "/instances/{id}/content/{...}" endpoint
-        std::string slicesData;
-        orthanc.RestApiGet(slicesData, "/instances/" + instanceId + "/content/3006-0039/" +
-                           boost::lexical_cast<std::string>(i) + "/3006-0040/" +
-                           boost::lexical_cast<std::string>(j) + "/3006-0050");
+        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));
 
         Vector points;
-        if (!GeometryToolbox::ParseVector(points, slicesData) ||
+        if (!LinearAlgebra::ParseVector(points, slicesData) ||
             points.size() != 3 * countPoints)
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
         }
 
-        Polygon polygon;
-        SliceGeometry geometry = ExtractSliceGeometry(polygon.sliceThickness_, orthanc, tags, i, j);
-        polygon.projectionAlongNormal_ = geometry.ProjectAlongNormal(geometry.GetOrigin());
+        Polygon polygon(sopInstanceUid);
+        polygon.Reserve(countPoints);
 
         for (size_t k = 0; k < countPoints; k++)
         {
@@ -276,27 +484,12 @@
           v[0] = points[3 * k];
           v[1] = points[3 * k + 1];
           v[2] = points[3 * k + 2];
-
-          if (!GeometryToolbox::IsNear(geometry.ProjectAlongNormal(v), 
-                                       polygon.projectionAlongNormal_, 
-                                       polygon.sliceThickness_ / 2.0 /* in mm */))
-          {
-            LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance";
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
-          }
-
-          polygon.points_.push_back(v);
+          polygon.AddPoint(v);
         }
 
         structures_[i].polygons_.push_back(polygon);
       }
     }
-
-    if (parentSeriesId_.empty() ||
-        normal_.size() != 3)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
-    }
   }
 
 
@@ -305,7 +498,7 @@
     const Structure& structure = GetStructure(index);
 
     Vector center;
-    GeometryToolbox::AssignVector(center, 0, 0, 0);
+    LinearAlgebra::AssignVector(center, 0, 0, 0);
     if (structure.polygons_.empty())
     {
       return center;
@@ -316,9 +509,9 @@
     for (Polygons::const_iterator polygon = structure.polygons_.begin();
          polygon != structure.polygons_.end(); ++polygon)
     {
-      if (!polygon->points_.empty())
+      if (!polygon->GetPoints().empty())
       {
-        center += polygon->points_.front() / n;
+        center += polygon->GetPoints().front() / n;
       }
     }
 
@@ -350,41 +543,257 @@
   }
 
 
-  void DicomStructureSet::Render(CairoContext& context,
-                                 const SliceGeometry& slice) const
+  void DicomStructureSet::GetReferencedInstances(std::set<std::string>& instances)
   {
-    cairo_t* cr = context.GetObject();
-
     for (Structures::const_iterator structure = structures_.begin();
          structure != structures_.end(); ++structure)
     {
       for (Polygons::const_iterator polygon = structure->polygons_.begin();
            polygon != structure->polygons_.end(); ++polygon)
       {
-        if (IsPolygonOnSlice(*polygon, slice))
+        instances.insert(polygon->GetSopInstanceUid());
+      }
+    }
+  }
+
+
+  void DicomStructureSet::AddReferencedSlice(const std::string& sopInstanceUid,
+                                             const std::string& seriesInstanceUid,
+                                             const CoordinateSystem3D& geometry,
+                                             double thickness)
+  {
+    if (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end())
+    {
+      // This geometry is already known
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      if (thickness < 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+        
+      if (!referencedSlices_.empty())
+      {
+        const ReferencedSlice& reference = referencedSlices_.begin()->second;
+
+        if (reference.seriesInstanceUid_ != seriesInstanceUid)
         {
-          context.SetSourceColor(structure->red_, structure->green_, structure->blue_);
+          LOG(ERROR) << "This RT-STRUCT refers to several different series";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
 
-          Points::const_iterator p = polygon->points_.begin();
+        if (!GeometryToolbox::IsParallel(reference.geometry_.GetNormal(), geometry.GetNormal()))
+        {
+          LOG(ERROR) << "The slices in this RT-STRUCT are not parallel";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      }
+        
+      referencedSlices_[sopInstanceUid] = ReferencedSlice(seriesInstanceUid, geometry, thickness);
 
-          double x, y;
-          slice.ProjectPoint(x, y, *p);
-          cairo_move_to(cr, x, y);
-          ++p;
-            
-          while (p != polygon->points_.end())
-          {
-            slice.ProjectPoint(x, y, *p);
-            cairo_line_to(cr, x, y);
-            ++p;
-          }
+      for (Structures::iterator structure = structures_.begin();
+           structure != structures_.end(); ++structure)
+      {
+        for (Polygons::iterator polygon = structure->polygons_.begin();
+             polygon != structure->polygons_.end(); ++polygon)
+        {
+          polygon->UpdateReferencedSlice(referencedSlices_);
+        }
+      }
+    }
+  }
+
+
+  void DicomStructureSet::AddReferencedSlice(const Orthanc::DicomMap& dataset)
+  {
+    CoordinateSystem3D slice(dataset);
+
+    double thickness = 1;  // 1 mm by default
 
-          slice.ProjectPoint(x, y, *polygon->points_.begin());
-          cairo_line_to(cr, x, y);
+    std::string s;
+    Vector v;
+    if (dataset.CopyToString(s, Orthanc::DICOM_TAG_SLICE_THICKNESS, false) &&
+        LinearAlgebra::ParseVector(v, s) &&
+        v.size() > 0)
+    {
+      thickness = v[0];
+    }
+
+    std::string instance, series;
+    if (dataset.CopyToString(instance, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) &&
+        dataset.CopyToString(series, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
+    {
+      AddReferencedSlice(instance, series, slice, thickness);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void DicomStructureSet::CheckReferencedSlices()
+  {
+    for (Structures::iterator structure = structures_.begin();
+         structure != structures_.end(); ++structure)
+    {
+      for (Polygons::iterator polygon = structure->polygons_.begin();
+           polygon != structure->polygons_.end(); ++polygon)
+      {
+        if (!polygon->UpdateReferencedSlice(referencedSlices_))
+        {
+          LOG(ERROR) << "Missing information about referenced instance: "
+                     << polygon->GetSopInstanceUid();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
         }
       }
     }
+  }
 
-    cairo_stroke(cr);
+
+  Vector DicomStructureSet::GetNormal() const
+  {
+    if (referencedSlices_.empty())
+    {
+      Vector v;
+      LinearAlgebra::AssignVector(v, 0, 0, 1);
+      return v;
+    }
+    else
+    {
+      return referencedSlices_.begin()->second.geometry_.GetNormal();
+    }
+  }
+
+  
+  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)
+  {
+    polygons.clear();
+
+    Vector normal = GetNormal();
+    
+    bool isOpposite;    
+    if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, slice.GetNormal()))
+    {
+      // This is an axial projection
+
+      for (Polygons::iterator polygon = structure.polygons_.begin();
+           polygon != structure.polygons_.end(); ++polygon)
+      {
+        if (polygon->IsOnSlice(slice))
+        {
+          polygons.push_back(std::vector<PolygonPoint>());
+          
+          for (Points::const_iterator p = polygon->GetPoints().begin();
+               p != polygon->GetPoints().end(); ++p)
+          {
+            double x, y;
+            slice.ProjectPoint(x, y, *p);
+            polygons.back().push_back(std::make_pair(x, y));
+          }
+        }
+      }
+
+      return true;
+    }
+    else if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, slice.GetAxisX()) ||
+             GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, slice.GetAxisY()))
+    {
+#if 1
+      // Sagittal or coronal projection
+      std::vector<BoostPolygon> projected;
+  
+      for (Polygons::iterator polygon = structure.polygons_.begin();
+           polygon != structure.polygons_.end(); ++polygon)
+      {
+        double x1, y1, x2, y2;
+        if (polygon->Project(x1, y1, x2, y2, slice))
+        {
+          projected.push_back(CreateRectangle(x1, y1, x2, y2));
+        }
+      }
+
+      BoostMultiPolygon merged;
+      Union(merged, projected);
+
+      polygons.resize(merged.size());
+      for (size_t i = 0; i < merged.size(); i++)
+      {
+        const std::vector<BoostPoint>& outer = merged[i].outer();
+
+        polygons[i].resize(outer.size());
+        for (size_t j = 0; j < outer.size(); j++)
+        {
+          polygons[i][j] = std::make_pair(outer[j].x(), outer[j].y());
+        }
+      }  
+#else
+      for (Polygons::iterator polygon = structure.polygons_.begin();
+           polygon != structure.polygons_.end(); ++polygon)
+      {
+        double x1, y1, x2, y2;
+        if (polygon->Project(x1, y1, x2, y2, slice))
+        {
+          std::vector<PolygonPoint> p(4);
+          p[0] = std::make_pair(x1, y1);
+          p[1] = std::make_pair(x2, y1);
+          p[2] = std::make_pair(x2, y2);
+          p[3] = std::make_pair(x1, y2);
+          polygons.push_back(p);
+        }
+      }
+#endif
+      
+      return true;
+    }
+    else
+    {
+      return false;
+    }
   }
 }
--- a/Framework/Toolbox/DicomStructureSet.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/DicomStructureSet.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,24 +21,95 @@
 
 #pragma once
 
-#include "SliceGeometry.h"
-#include "../Viewport/CairoContext.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
+#include "CoordinateSystem3D.h"
+#include "Extent2D.h"
 
+#include <Plugins/Samples/Common/FullOrthancDataset.h>
 #include <list>
 
 namespace OrthancStone
 {
   class DicomStructureSet : public boost::noncopyable
   {
+  public:
+    typedef std::pair<double, double> PolygonPoint;
+    
   private:
-    typedef std::list<Vector>  Points;
+    struct ReferencedSlice
+    {
+      std::string          seriesInstanceUid_;
+      CoordinateSystem3D   geometry_;
+      double               thickness_;
+
+      ReferencedSlice()
+      {
+      }
+      
+      ReferencedSlice(const std::string& seriesInstanceUid,
+                      const CoordinateSystem3D& geometry,
+                      double thickness) :
+        seriesInstanceUid_(seriesInstanceUid),
+        geometry_(geometry),
+        thickness_(thickness)
+      {
+      }
+    };
+
+    typedef std::map<std::string, ReferencedSlice>  ReferencedSlices;
+    
+    typedef std::vector<Vector>  Points;
+
+    class Polygon
+    {
+    private:
+      std::string         sopInstanceUid_;
+      bool                hasSlice_;
+      CoordinateSystem3D  geometry_;
+      double              projectionAlongNormal_;
+      double              sliceThickness_;  // In millimeters
+      Points              points_;
+      Extent2D            extent_;
 
-    struct Polygon
-    {
-      double  projectionAlongNormal_;
-      double  sliceThickness_;  // In millimeters
-      Points  points_;
+      void CheckPoint(const Vector& v);
+
+    public:
+      Polygon(const std::string& sopInstanceUid) :
+        sopInstanceUid_(sopInstanceUid),
+        hasSlice_(false)
+      {
+      }
+
+      void Reserve(size_t n)
+      {
+        points_.reserve(n);
+      }
+
+      void AddPoint(const Vector& v);
+
+      bool UpdateReferencedSlice(const ReferencedSlices& slices);
+
+      bool IsOnSlice(const CoordinateSystem3D& geometry) const;
+
+      const std::string& GetSopInstanceUid() const
+      {
+        return sopInstanceUid_;
+      }
+
+      const Points& GetPoints() const
+      {
+        return points_;
+      }
+
+      double GetSliceThickness() const
+      {
+        return sliceThickness_;
+      }
+
+      bool Project(double& x1,
+                   double& y1,
+                   double& x2,
+                   double& y2,
+                   const CoordinateSystem3D& slice) const;
     };
 
     typedef std::list<Polygon>  Polygons;
@@ -55,25 +126,19 @@
 
     typedef std::vector<Structure>  Structures;
 
-    Structures   structures_;
-    std::string  parentSeriesId_;
-    Vector       normal_;
-
-    SliceGeometry ExtractSliceGeometry(double& sliceThickness,
-                                       OrthancPlugins::IOrthancConnection& orthanc,
-                                       const OrthancPlugins::IDicomDataset& tags,
-                                       size_t contourIndex,
-                                       size_t sliceIndex);
+    Structures        structures_;
+    ReferencedSlices  referencedSlices_;
 
     const Structure& GetStructure(size_t index) const;
 
-    bool IsPolygonOnSlice(const Polygon& polygon,
-                          const SliceGeometry& geometry) const;
-
+    Structure& GetStructure(size_t index);
+  
+    bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
+                          Structure& structure,
+                          const CoordinateSystem3D& slice);
 
   public:
-    DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc,
-                      const std::string& instanceId);
+    DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance);
 
     size_t GetStructureCount() const
     {
@@ -91,12 +156,27 @@
                            uint8_t& blue,
                            size_t index) const;
 
-    const Vector& GetNormal() const
+    void GetReferencedInstances(std::set<std::string>& instances);
+
+    void AddReferencedSlice(const std::string& sopInstanceUid,
+                            const std::string& seriesInstanceUid,
+                            const CoordinateSystem3D& geometry,
+                            double thickness);
+
+    void AddReferencedSlice(const Orthanc::DicomMap& dataset);
+
+    void CheckReferencedSlices();
+
+    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)
     {
-      return normal_;
+      return ProjectStructure(polygons, GetStructure(index), slice);
     }
-
-    void Render(CairoContext& context,
-                const SliceGeometry& slice) const;
   };
 }
--- a/Framework/Toolbox/DownloadStack.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/DownloadStack.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,7 @@
 
 #include "DownloadStack.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/OrthancException.h>
 
 #include <cassert>
 
@@ -99,8 +99,6 @@
 
   bool DownloadStack::Pop(unsigned int& value)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     assert(CheckInvariants());
 
     if (firstNode_ == NIL)
@@ -176,23 +174,23 @@
   }
 
   
-  void DownloadStack::Writer::SetTopNode(unsigned int value)
+  void DownloadStack::SetTopNode(unsigned int value)
   {
-    if (value >= that_.nodes_.size())
+    if (value >= nodes_.size())
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    that_.SetTopNodeInternal(value);
+    SetTopNodeInternal(value);
   }
 
 
-  void DownloadStack::Writer::SetTopNodePermissive(int value)
+  void DownloadStack::SetTopNodePermissive(int value)
   {
     if (value >= 0 &&
-        value < static_cast<int>(that_.nodes_.size()))
+        value < static_cast<int>(nodes_.size()))
     {
-      that_.SetTopNodeInternal(value);
+      SetTopNodeInternal(value);
     }
   }
 }
--- a/Framework/Toolbox/DownloadStack.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/DownloadStack.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,7 +23,6 @@
 
 #include <vector>
 #include <boost/noncopyable.hpp>
-#include <boost/thread/mutex.hpp>
 
 namespace OrthancStone
 {
@@ -40,7 +39,6 @@
       bool  dequeued_;
     };
 
-    boost::mutex        mutex_;
     std::vector<Node>   nodes_;
     int                 firstNode_;
 
@@ -55,22 +53,8 @@
 
     bool Pop(unsigned int& value);
 
-    class Writer : public boost::noncopyable
-    {
-    private:
-      DownloadStack&              that_;
-      boost::mutex::scoped_lock   lock_;
-      
-    public:
-      Writer(DownloadStack& that) :
-        that_(that),
-        lock_(that.mutex_)
-      {
-      }
-      
-      void SetTopNode(unsigned int value);  
+    void SetTopNode(unsigned int value);  
 
-      void SetTopNodePermissive(int value);
-    };
+    void SetTopNodePermissive(int value);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Extent2D.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,139 @@
+/**
+ * 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 "Extent2D.h"
+
+#include <algorithm>
+#include <cassert>
+#include <limits>
+
+namespace OrthancStone
+{
+  Extent2D::Extent2D(double x1,
+                     double y1,
+                     double x2,
+                     double y2) :
+    empty_(false),
+    x1_(x1),
+    y1_(y1),
+    x2_(x2),
+    y2_(y2)
+  {
+    if (x1_ > x2_)
+    {
+      std::swap(x1_, x2_);
+    }
+
+    if (y1_ > y2_)
+    {
+      std::swap(y1_, y2_);
+    }
+  }
+
+
+  void Extent2D::Reset()
+  {
+    empty_ = true;
+    x1_ = 0;
+    y1_ = 0;
+    x2_ = 0;
+    y2_ = 0;      
+  }
+
+  void Extent2D::AddPoint(double x,
+                          double y)
+  {
+    if (empty_)
+    {
+      x1_ = x;
+      y1_ = y;
+      x2_ = x;
+      y2_ = y;
+      empty_ = false;
+    }
+    else
+    {
+      x1_ = std::min(x1_, x);
+      y1_ = std::min(y1_, y);
+      x2_ = std::max(x2_, x);
+      y2_ = std::max(y2_, y);
+    }
+
+    assert(x1_ <= x2_ &&
+           y1_ <= y2_);    // This is the invariant of the structure
+  }
+
+
+  void Extent2D::Union(const Extent2D& other)
+  {
+    if (other.empty_)
+    {
+      return;
+    }
+
+    if (empty_)
+    {
+      *this = other;
+      return;
+    }
+
+    assert(!empty_);
+
+    x1_ = std::min(x1_, other.x1_);
+    y1_ = std::min(y1_, other.y1_);
+    x2_ = std::max(x2_, other.x2_);
+    y2_ = std::max(y2_, other.y2_);
+
+    assert(x1_ <= x2_ &&
+           y1_ <= y2_);    // This is the invariant of the structure
+  }
+
+
+  bool Extent2D::IsEmpty() const
+  {
+    if (empty_)
+    {
+      return true;
+    }
+    else
+    {
+      assert(x1_ <= x2_ &&
+             y1_ <= y2_);
+      return (x2_ <= x1_ + 10 * std::numeric_limits<double>::epsilon() ||
+              y2_ <= y1_ + 10 * std::numeric_limits<double>::epsilon());
+    }
+  }
+
+
+  bool Extent2D::Contains(double x,
+                          double y) const
+  {
+    if (empty_)
+    {
+      return false;
+    }
+    else
+    {
+      return (x >= x1_ && x <= x2_ &&
+              y >= y1_ && y <= y2_);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Extent2D.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,98 @@
+/**
+ * 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
+
+namespace OrthancStone
+{
+  class Extent2D
+  {
+  private:
+    bool    empty_;
+    double  x1_;
+    double  y1_;
+    double  x2_;
+    double  y2_;
+
+  public:
+    Extent2D()
+    {
+      Reset();
+    }
+
+    Extent2D(double x1,
+             double y1,
+             double x2,
+             double y2);
+
+    void Reset();
+
+    void AddPoint(double x,
+                  double y);
+
+    void Union(const Extent2D& other);
+
+    bool IsEmpty() const;
+
+    double GetX1() const
+    {
+      return x1_;
+    }
+
+    double GetY1() const
+    {
+      return y1_;
+    }
+
+    double GetX2() const
+    {
+      return x2_;
+    }
+
+    double GetY2() const
+    {
+      return y2_;
+    }
+
+    double GetWidth() const
+    {
+      return x2_ - x1_;
+    }
+
+    double GetHeight() const
+    {
+      return y2_ - y1_;
+    }
+
+    double GetCenterX() const
+    {
+      return (x1_ + x2_) / 2.0;
+    }
+
+    double GetCenterY() const
+    {
+      return (y1_ + y2_) / 2.0;
+    }
+
+    bool Contains(double x,
+                  double y) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,458 @@
+/**
+ * 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 "FiniteProjectiveCamera.h"
+
+#include "GeometryToolbox.h"
+#include "SubpixelReader.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+
+namespace OrthancStone
+{
+  void FiniteProjectiveCamera::ComputeMInverse()
+  {
+    using namespace boost::numeric::ublas;
+
+    // inv(M) = inv(K * R) = inv(R) * inv(K) = R' * inv(K). This
+    // matrix is always invertible, by definition of finite
+    // projective cameras (page 157).
+    Matrix kinv;
+    LinearAlgebra::InvertUpperTriangularMatrix(kinv, k_);
+    minv_ = prod(trans(r_), kinv);
+  }
+
+    
+  void FiniteProjectiveCamera::Setup(const Matrix& k,
+                                     const Matrix& r,
+                                     const Vector& c)
+  {
+    if (k.size1() != 3 ||
+        k.size2() != 3 ||
+        !LinearAlgebra::IsCloseToZero(k(1, 0)) ||
+        !LinearAlgebra::IsCloseToZero(k(2, 0)) ||
+        !LinearAlgebra::IsCloseToZero(k(2, 1)))
+    {
+      LOG(ERROR) << "Invalid intrinsic parameters";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (r.size1() != 3 ||
+        r.size2() != 3)
+    {
+      LOG(ERROR) << "Invalid size for a 3D rotation matrix";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!LinearAlgebra::IsRotationMatrix(r, 100.0 * std::numeric_limits<float>::epsilon()))
+    {
+      LOG(ERROR) << "Invalid rotation matrix";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (c.size() != 3)
+    {
+      LOG(ERROR) << "Invalid camera center";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    k_ = k;
+    r_ = r;
+    c_ = c;
+
+    ComputeMInverse();
+      
+    Matrix tmp = LinearAlgebra::IdentityMatrix(3);
+    tmp.resize(3, 4);
+    tmp(0, 3) = -c[0];
+    tmp(1, 3) = -c[1];
+    tmp(2, 3) = -c[2];
+
+    p_ = LinearAlgebra::Product(k, r, tmp);
+
+    assert(p_.size1() == 3 &&
+           p_.size2() == 4);
+
+  }
+
+    
+  void FiniteProjectiveCamera::Setup(const Matrix& p)
+  {
+    if (p.size1() != 3 ||
+        p.size2() != 4)
+    {
+      LOG(ERROR) << "Invalid camera matrix";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    p_ = p;
+
+    // M is the left 3x3 submatrix of "P"
+    Matrix m = p;
+    m.resize(3, 3);
+
+    // p4 is the last column of "P"
+    Vector p4(3);
+    p4[0] = p(0, 3);
+    p4[1] = p(1, 3);
+    p4[2] = p(2, 3);
+
+    // The RQ decomposition is explained on page 157
+    LinearAlgebra::RQDecomposition3x3(k_, r_, m);
+    ComputeMInverse();
+
+    c_ = LinearAlgebra::Product(-minv_, p4);
+  }
+
+
+  FiniteProjectiveCamera::FiniteProjectiveCamera(const double k[9],
+                                                 const double r[9],
+                                                 const double c[3])
+  {
+    Matrix kk, rr;
+    Vector cc;
+
+    LinearAlgebra::FillMatrix(kk, 3, 3, k);
+    LinearAlgebra::FillMatrix(rr, 3, 3, r);
+    LinearAlgebra::FillVector(cc, 3, c);
+
+    Setup(kk, rr, cc);
+  }
+
+
+  FiniteProjectiveCamera::FiniteProjectiveCamera(const double p[12])
+  {
+    Matrix pp;
+    LinearAlgebra::FillMatrix(pp, 3, 4, p);
+    Setup(pp);
+  }
+
+
+  Vector FiniteProjectiveCamera::GetRayDirection(double x,
+                                                 double y) const
+  {
+    // This derives from Equation (6.14) on page 162, taking "mu =
+    // 1" and noticing that "-inv(M)*p4" corresponds to the camera
+    // center in finite projective cameras
+
+    // The (x,y) coordinates on the imaged plane, as an homogeneous vector
+    Vector xx(3);
+    xx[0] = x;
+    xx[1] = y;
+    xx[2] = 1.0;
+
+    return boost::numeric::ublas::prod(minv_, xx);
+  }
+
+
+
+  static Vector SetupApply(const Vector& v,
+                           bool infinityAllowed)
+  {
+    if (v.size() == 3)
+    {
+      // Vector "v" in non-homogeneous coordinates, add the homogeneous component
+      Vector vv;
+      LinearAlgebra::AssignVector(vv, v[0], v[1], v[2], 1.0);
+      return vv;
+    }
+    else if (v.size() == 4)
+    {
+      // Vector "v" is already in homogeneous coordinates
+
+      if (!infinityAllowed &&
+          LinearAlgebra::IsCloseToZero(v[3]))
+      {
+        LOG(ERROR) << "Cannot apply a finite projective camera to a "
+                   << "point at infinity with this method";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      return v;
+    }
+    else
+    {
+      LOG(ERROR) << "The input vector must represent a point in 3D";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  void FiniteProjectiveCamera::ApplyFinite(double& x,
+                                           double& y,
+                                           const Vector& v) const
+  {
+    Vector p = boost::numeric::ublas::prod(p_, SetupApply(v, false));    
+
+    if (LinearAlgebra::IsCloseToZero(p[2]))
+    {
+      // Point at infinity: Should not happen with a finite input point
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      x = p[0] / p[2];
+      y = p[1] / p[2];
+    }
+  }
+
+  
+  Vector FiniteProjectiveCamera::ApplyGeneral(const Vector& v) const
+  {
+    return boost::numeric::ublas::prod(p_, SetupApply(v, true));
+  }
+
+
+  static Vector AddHomogeneousCoordinate(const Vector& p)
+  {
+    assert(p.size() == 3);
+    return LinearAlgebra::CreateVector(p[0], p[1], p[2], 1);
+  }
+
+
+  FiniteProjectiveCamera::FiniteProjectiveCamera(const Vector& camera,
+                                                 const Vector& principalPoint,
+                                                 double angle,
+                                                 unsigned int imageWidth,
+                                                 unsigned int imageHeight,
+                                                 double pixelSpacingX,
+                                                 double pixelSpacingY)
+  {
+    if (camera.size() != 3 ||
+        principalPoint.size() != 3 ||
+        LinearAlgebra::IsCloseToZero(pixelSpacingX) ||
+        LinearAlgebra::IsCloseToZero(pixelSpacingY))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    const double focal = boost::numeric::ublas::norm_2(camera - principalPoint);
+
+    if (LinearAlgebra::IsCloseToZero(focal))
+    {
+      LOG(ERROR) << "Camera lies on the image plane";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+      
+    Matrix a;
+    GeometryToolbox::AlignVectorsWithRotation(a, camera - principalPoint,
+                                              LinearAlgebra::CreateVector(0, 0, -1));
+
+    Matrix r = LinearAlgebra::Product(GeometryToolbox::CreateRotationMatrixAlongZ(angle), a);
+
+    Matrix k = LinearAlgebra::ZeroMatrix(3, 3);
+    k(0,0) = focal / pixelSpacingX;
+    k(1,1) = focal / pixelSpacingY;
+    k(0,2) = static_cast<double>(imageWidth) / 2.0;
+    k(1,2) = static_cast<double>(imageHeight) / 2.0;
+    k(2,2) = 1;
+
+    Setup(k, r, camera);
+ 
+    {
+      // Sanity checks
+      Vector v1 = LinearAlgebra::Product(p_, AddHomogeneousCoordinate(camera));
+      Vector v2 = LinearAlgebra::Product(p_, AddHomogeneousCoordinate(principalPoint));
+
+      if (!LinearAlgebra::IsCloseToZero(v1[2]) ||   // Camera is mapped to singularity
+          LinearAlgebra::IsCloseToZero(v2[2]))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      // The principal point must be mapped to the center of the image
+      v2 /= v2[2];
+
+      if (!LinearAlgebra::IsNear(v2[0], static_cast<double>(imageWidth) / 2.0) ||
+          !LinearAlgebra::IsNear(v2[1], static_cast<double>(imageHeight) / 2.0))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  template <Orthanc::PixelFormat TargetFormat,
+            Orthanc::PixelFormat SourceFormat,
+            bool MIP>
+  static void ApplyRaytracerInternal(Orthanc::ImageAccessor& target,
+                                     const FiniteProjectiveCamera& camera,
+                                     const ImageBuffer3D& source,
+                                     VolumeProjection projection)
+  {
+    if (source.GetFormat() != SourceFormat ||
+        target.GetFormat() != TargetFormat ||
+        !std::numeric_limits<float>::is_iec559 ||
+        sizeof(float) != 4)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    LOG(WARNING) << "Input volume size: " << source.GetWidth() << "x"
+                 << source.GetHeight() << "x" << source.GetDepth();
+    LOG(WARNING) << "Input pixel format: " << Orthanc::EnumerationToString(source.GetFormat());
+    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.GetVoxelDimensions(projection);
+    const unsigned int targetWidth = target.GetWidth();
+    const unsigned int targetHeight = target.GetHeight();
+
+    Orthanc::Image accumulator(Orthanc::PixelFormat_Float32, targetWidth, targetHeight, false);
+    Orthanc::Image counter(Orthanc::PixelFormat_Grayscale16, targetWidth, targetHeight, false);
+    Orthanc::ImageProcessing::Set(accumulator, 0);
+    Orthanc::ImageProcessing::Set(counter, 0);
+
+    typedef SubpixelReader<SourceFormat, ImageInterpolation_Nearest>  SourceReader;
+    
+    for (size_t z = 0; z < slices->GetSliceCount(); z++)
+    {
+      LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount();
+      
+      const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z);
+      OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, z);
+
+      SourceReader pixelReader(sliceReader.GetAccessor());
+      
+      for (unsigned int y = 0; y < targetHeight; y++)
+      {
+        float *qacc = reinterpret_cast<float*>(accumulator.GetRow(y));
+        uint16_t *qcount = reinterpret_cast<uint16_t*>(counter.GetRow(y));
+
+        for (unsigned int x = 0; x < targetWidth; x++)
+        {
+          // Backproject the ray originating from the center of the target pixel
+          OrthancStone::Vector direction = camera.GetRayDirection(static_cast<double>(x + 0.5),
+                                                                  static_cast<double>(y + 0.5));
+
+          // Compute the 3D intersection of the ray with the slice plane
+          OrthancStone::Vector p;
+          if (slice.IntersectLine(p, camera.GetCenter(), direction))
+          {
+            // Compute the 2D coordinates of the intersections, in slice coordinates
+            double ix, iy;
+            slice.ProjectPoint(ix, iy, p);
+
+            ix /= pixelSpacing[0];
+            iy /= pixelSpacing[1];
+
+            // Read and accumulate the value of the pixel
+            float pixel;
+            if (pixelReader.GetFloatValue(pixel, ix, iy))
+            {
+              if (MIP)
+              {
+                // MIP rendering
+                if (*qcount == 0)
+                {
+                  (*qacc) = pixel;
+                  (*qcount) = 1;
+                }
+                else if (pixel > *qacc)
+                {
+                  (*qacc) = pixel;
+                }
+              }
+              else
+              {
+                // Mean intensity
+                (*qacc) += pixel;
+                (*qcount) ++;
+              }
+            }
+          }
+
+          qacc++;
+          qcount++;
+        }
+      }
+    }
+
+
+    typedef Orthanc::PixelTraits<TargetFormat>  TargetTraits;
+
+    // "Flatten" the accumulator image to create the target image
+    for (unsigned int y = 0; y < targetHeight; y++)
+    {
+      const float *qacc = reinterpret_cast<const float*>(accumulator.GetConstRow(y));
+      const uint16_t *qcount = reinterpret_cast<const uint16_t*>(counter.GetConstRow(y));
+      typename TargetTraits::PixelType *p = reinterpret_cast<typename TargetTraits::PixelType*>(target.GetRow(y));
+
+      for (unsigned int x = 0; x < targetWidth; x++)
+      {
+        if (*qcount == 0)
+        {
+          TargetTraits::SetZero(*p);
+        }
+        else
+        {
+          TargetTraits::FloatToPixel(*p, *qacc / static_cast<float>(*qcount));
+        }
+        
+        p++;
+        qacc++;
+        qcount++;
+      }
+    }
+  }
+
+
+  Orthanc::ImageAccessor*
+  FiniteProjectiveCamera::ApplyRaytracer(const ImageBuffer3D& source,
+                                         Orthanc::PixelFormat targetFormat,
+                                         unsigned int targetWidth,
+                                         unsigned int targetHeight,
+                                         bool mip) const
+  {
+    // TODO - We consider the axial projection of the volume, but we
+    // should choose the projection that is the "most perpendicular"
+    // to the line joining the camera center and the principal point
+    const VolumeProjection projection = VolumeProjection_Axial;
+
+    std::auto_ptr<Orthanc::ImageAccessor> target
+      (new Orthanc::Image(targetFormat, targetWidth, targetHeight, false));
+    
+    if (targetFormat == Orthanc::PixelFormat_Grayscale16 &&
+        source.GetFormat() == Orthanc::PixelFormat_Grayscale16 && mip)
+    {
+      ApplyRaytracerInternal<Orthanc::PixelFormat_Grayscale16,
+                             Orthanc::PixelFormat_Grayscale16, true>
+        (*target, *this, source, 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);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    return target.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/FiniteProjectiveCamera.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,117 @@
+/**
+ * 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 "LinearAlgebra.h"
+#include "../Volumes/ImageBuffer3D.h"
+
+namespace OrthancStone
+{
+  // Reference: "Multiple View Geometry in Computer Vision (2nd Edition)"
+  class FiniteProjectiveCamera : public boost::noncopyable
+  {
+  private:
+    Matrix  p_;     // 3x4 matrix - Equation (6.11) - page 157
+    Matrix  k_;     // 3x3 matrix of intrinsic parameters - Equation (6.10) - page 157
+    Matrix  r_;     // 3x3 rotation matrix in 3D space
+    Vector  c_;     // 3x1 vector in 3D space corresponding to camera center
+    Matrix  minv_;  // Inverse of the M = P(1:3,1:3) submatrix
+
+    void ComputeMInverse();
+
+    void Setup(const Matrix& k,
+               const Matrix& r,
+               const Vector& c);
+
+    void Setup(const Matrix& p);
+
+  public:
+    FiniteProjectiveCamera(const Matrix& k,
+                           const Matrix& r,
+                           const Vector& c)
+    {
+      Setup(k, r, c);
+    }
+
+    FiniteProjectiveCamera(const Matrix& p)
+    {
+      Setup(p);
+    }
+
+    FiniteProjectiveCamera(const double k[9],
+                           const double r[9],
+                           const double c[3]);
+
+    FiniteProjectiveCamera(const double p[12]);
+
+    // Constructor that implements camera calibration
+    FiniteProjectiveCamera(const Vector& camera,
+                           const Vector& principalPoint,
+                           double angle,
+                           unsigned int imageWidth,
+                           unsigned int imageHeight,
+                           double pixelSpacingX,
+                           double pixelSpacingY);
+
+    const Matrix& GetMatrix() const
+    {
+      return p_;
+    }
+
+    const Matrix& GetRotation() const
+    {
+      return r_;
+    }
+
+    const Vector& GetCenter() const
+    {
+      return c_;
+    }
+
+    const Matrix& GetIntrinsicParameters() const
+    {
+      return k_;
+    }
+
+    // Computes the 3D vector that represents the direction from the
+    // camera center to the (x,y) imaged point
+    Vector GetRayDirection(double x,
+                           double y) const;
+
+    // Apply the camera to a 3D point "v" that is not at infinity. "v"
+    // can be encoded either as a non-homogeneous vector (3
+    // components), or as a homogeneous vector (4 components).
+    void ApplyFinite(double& x,
+                     double& y,
+                     const Vector& v) const;
+
+    // Apply the camera to a 3D point "v" that is possibly at
+    // infinity. The result is a 2D point in homogeneous coordinates.
+    Vector ApplyGeneral(const Vector& v) const;
+
+    Orthanc::ImageAccessor* ApplyRaytracer(const ImageBuffer3D& source,
+                                           Orthanc::PixelFormat targetFormat,
+                                           unsigned int targetWidth,
+                                           unsigned int targetHeight,
+                                           bool mip) const;
+  };
+}
--- a/Framework/Toolbox/GeometryToolbox.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/GeometryToolbox.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,128 +21,22 @@
 
 #include "GeometryToolbox.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Toolbox.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
-#include <stdio.h>
-#include <boost/lexical_cast.hpp>
+#include <cassert>
 
 namespace OrthancStone
 {
   namespace GeometryToolbox
   {
-    void Print(const Vector& v)
-    {
-      for (size_t i = 0; i < v.size(); i++)
-      {
-        printf("%8.2f\n", v[i]);
-      }
-      printf("\n");
-    }
-
-
-    bool ParseVector(Vector& target,
-                     const std::string& value)
-    {
-      std::vector<std::string> items;
-      Orthanc::Toolbox::TokenizeString(items, value, '\\');
-
-      target.resize(items.size());
-
-      for (size_t i = 0; i < items.size(); i++)
-      {
-        try
-        {
-          target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i]));
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-          target.clear();
-          return false;
-        }
-      }
-
-      return true;
-    }
-
-
-    bool ParseVector(Vector& target,
-                     const OrthancPlugins::IDicomDataset& dataset,
-                     const OrthancPlugins::DicomPath& tag)
-    {
-      std::string value;
-      return (dataset.GetStringValue(value, tag) &&
-              ParseVector(target, value));
-    }
-
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2)
-    {
-      v.resize(2);
-      v[0] = v1;
-      v[1] = v2;
-    }
-
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2,
-                      double v3)
-    {
-      v.resize(3);
-      v[0] = v1;
-      v[1] = v2;
-      v[2] = v3;
-    }
-
-
-    bool IsNear(double x,
-                double y)
-    {
-      // As most input is read as single-precision numbers, we take the
-      // epsilon machine for float32 into consideration to compare numbers
-      return IsNear(x, y, 10.0 * std::numeric_limits<float>::epsilon());
-    }
-
-
-    void NormalizeVector(Vector& u)
-    {
-      double norm = boost::numeric::ublas::norm_2(u);
-      if (!IsCloseToZero(norm))
-      {
-        u = u / norm;
-      }
-    }
-
-
-    void CrossProduct(Vector& result,
-                      const Vector& u,
-                      const Vector& v)
-    {
-      if (u.size() != 3 ||
-          v.size() != 3)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      result.resize(3);
-
-      result[0] = u[1] * v[2] - u[2] * v[1];
-      result[1] = u[2] * v[0] - u[0] * v[2];
-      result[2] = u[0] * v[1] - u[1] * v[0];
-    }
-
-
     void ProjectPointOntoPlane(Vector& result,
                                const Vector& point,
                                const Vector& planeNormal,
                                const Vector& planeOrigin)
     {
       double norm =  boost::numeric::ublas::norm_2(planeNormal);
-      if (IsCloseToZero(norm))
+      if (LinearAlgebra::IsCloseToZero(norm))
       {
         // Division by zero
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
@@ -171,8 +65,8 @@
       double normU = boost::numeric::ublas::norm_2(u);
       double normV = boost::numeric::ublas::norm_2(v);
 
-      if (IsCloseToZero(normU) ||
-          IsCloseToZero(normV))
+      if (LinearAlgebra::IsCloseToZero(normU) ||
+          LinearAlgebra::IsCloseToZero(normV))
       {
         return false;
       }
@@ -182,12 +76,12 @@
       // The angle must be zero, so the cosine must be almost equal to
       // cos(0) == 1 (or cos(180) == -1 if allowOppositeDirection == true)
 
-      if (IsCloseToZero(cosAngle - 1.0))
+      if (LinearAlgebra::IsCloseToZero(cosAngle - 1.0))
       {
         isOpposite = false;
         return true;
       }
-      else if (IsCloseToZero(fabs(cosAngle) - 1.0))
+      else if (LinearAlgebra::IsCloseToZero(fabs(cosAngle) - 1.0))
       {
         isOpposite = true;
         return true;
@@ -221,10 +115,10 @@
 
       // The direction of the line of intersection is orthogonal to the
       // normal of both planes
-      CrossProduct(direction, normal1, normal2);
+      LinearAlgebra::CrossProduct(direction, normal1, normal2);
 
       double norm = boost::numeric::ublas::norm_2(direction);
-      if (IsCloseToZero(norm))
+      if (LinearAlgebra::IsCloseToZero(norm))
       {
         // The two planes are parallel or coincident
         return false;
@@ -234,7 +128,7 @@
       double d2 = -boost::numeric::ublas::inner_prod(normal2, origin2);
       Vector tmp = d2 * normal1 - d1 * normal2;
 
-      CrossProduct(p, tmp, direction);
+      LinearAlgebra::CrossProduct(p, tmp, direction);
       p /= norm;
 
       return true;
@@ -259,56 +153,56 @@
       // (2005). This is a direct, non-optimized translation of Algorithm
       // 2 in the paper.
 
-      static uint8_t tab1[16] = { 255 /* none */,
-                                  0,
-                                  0,
-                                  1,
-                                  1,
-                                  255 /* na */,
-                                  0,
-                                  2,
-                                  2,
-                                  0,
-                                  255 /* na */,
-                                  1,
-                                  1,
-                                  0,
-                                  0,
-                                  255 /* none */ };
+      static const uint8_t tab1[16] = { 255 /* none */,
+                                        0,
+                                        0,
+                                        1,
+                                        1,
+                                        255 /* na */,
+                                        0,
+                                        2,
+                                        2,
+                                        0,
+                                        255 /* na */,
+                                        1,
+                                        1,
+                                        0,
+                                        0,
+                                        255 /* none */ };
 
 
-      static uint8_t tab2[16] = { 255 /* none */,
-                                  3,
-                                  1,
-                                  3,
-                                  2,
-                                  255 /* na */,
-                                  2,
-                                  3,
-                                  3,
-                                  2,
-                                  255 /* na */,
-                                  2,
-                                  3,
-                                  1,
-                                  3,
-                                  255 /* none */ };
+      static const uint8_t tab2[16] = { 255 /* none */,
+                                        3,
+                                        1,
+                                        3,
+                                        2,
+                                        255 /* na */,
+                                        2,
+                                        3,
+                                        3,
+                                        2,
+                                        255 /* na */,
+                                        2,
+                                        3,
+                                        1,
+                                        3,
+                                        255 /* none */ };
 
       // Create the coordinates of the rectangle
       Vector x[4];
-      AssignVector(x[0], xmin, ymin, 1.0);
-      AssignVector(x[1], xmax, ymin, 1.0);
-      AssignVector(x[2], xmax, ymax, 1.0);
-      AssignVector(x[3], xmin, ymax, 1.0);
+      LinearAlgebra::AssignVector(x[0], xmin, ymin, 1.0);
+      LinearAlgebra::AssignVector(x[1], xmax, ymin, 1.0);
+      LinearAlgebra::AssignVector(x[2], xmax, ymax, 1.0);
+      LinearAlgebra::AssignVector(x[3], xmin, ymax, 1.0);
 
       // Move to homogoneous coordinates in 2D
       Vector p;
 
       {
         Vector a, b;
-        AssignVector(a, ax, ay, 1.0);
-        AssignVector(b, bx, by, 1.0);
-        CrossProduct(p, a, b);
+        LinearAlgebra::AssignVector(a, ax, ay, 1.0);
+        LinearAlgebra::AssignVector(b, bx, by, 1.0);
+        LinearAlgebra::CrossProduct(p, a, b);
       }
 
       uint8_t c = 0;
@@ -333,10 +227,10 @@
       else
       {
         Vector a, b, e;
-        CrossProduct(e, x[i], x[(i + 1) % 4]);
-        CrossProduct(a, p, e);
-        CrossProduct(e, x[j], x[(j + 1) % 4]);
-        CrossProduct(b, p, e);
+        LinearAlgebra::CrossProduct(e, x[i], x[(i + 1) % 4]);
+        LinearAlgebra::CrossProduct(a, p, e);
+        LinearAlgebra::CrossProduct(e, x[j], x[(j + 1) % 4]);
+        LinearAlgebra::CrossProduct(b, p, e);
 
         // Go back to non-homogeneous coordinates
         x1 = a[0] / a[2];
@@ -351,11 +245,11 @@
 
     void GetPixelSpacing(double& spacingX, 
                          double& spacingY,
-                         const OrthancPlugins::IDicomDataset& dicom)
+                         const Orthanc::DicomMap& dicom)
     {
       Vector v;
 
-      if (ParseVector(v, dicom, OrthancPlugins::DICOM_TAG_PIXEL_SPACING))
+      if (LinearAlgebra::ParseVector(v, dicom, Orthanc::DICOM_TAG_PIXEL_SPACING))
       {
         if (v.size() != 2 ||
             v[0] <= 0 ||
@@ -378,5 +272,206 @@
         spacingY = 1;
       }
     }
+
+    
+    Matrix CreateRotationMatrixAlongX(double a)
+    {
+      // Rotate along X axis (R_x)
+      // https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
+      Matrix r(3, 3);
+      r(0,0) = 1;
+      r(0,1) = 0;
+      r(0,2) = 0;
+      r(1,0) = 0;
+      r(1,1) = cos(a);
+      r(1,2) = -sin(a);
+      r(2,0) = 0;
+      r(2,1) = sin(a);
+      r(2,2) = cos(a);
+      return r;
+    }
+    
+
+    Matrix CreateRotationMatrixAlongY(double a)
+    {
+      // Rotate along Y axis (R_y)
+      // https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
+      Matrix r(3, 3);
+      r(0,0) = cos(a);
+      r(0,1) = 0;
+      r(0,2) = sin(a);
+      r(1,0) = 0;
+      r(1,1) = 1;
+      r(1,2) = 0;
+      r(2,0) = -sin(a);
+      r(2,1) = 0;
+      r(2,2) = cos(a);
+      return r;
+    }
+
+
+    Matrix CreateRotationMatrixAlongZ(double a)
+    {
+      // Rotate along Z axis (R_z)
+      // https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
+      Matrix r(3, 3);
+      r(0,0) = cos(a);
+      r(0,1) = -sin(a);
+      r(0,2) = 0;
+      r(1,0) = sin(a);
+      r(1,1) = cos(a);
+      r(1,2) = 0;
+      r(2,0) = 0;
+      r(2,1) = 0;
+      r(2,2) = 1;
+      return r;
+    }    
+
+
+    Matrix CreateTranslationMatrix(double dx,
+                                   double dy,
+                                   double dz)
+    {
+      Matrix m = LinearAlgebra::IdentityMatrix(4);
+      m(0,3) = dx;
+      m(1,3) = dy;
+      m(2,3) = dz;
+      return m;    
+    }
+
+
+    Matrix CreateScalingMatrix(double sx,
+                               double sy,
+                               double sz)
+    {
+      Matrix m = LinearAlgebra::IdentityMatrix(4);
+      m(0,0) = sx;
+      m(1,1) = sy;
+      m(2,2) = sz;
+      return m;    
+    }
+
+
+    bool IntersectPlaneAndSegment(Vector& p,
+                                  const Vector& normal,
+                                  double d,
+                                  const Vector& edgeFrom,
+                                  const Vector& edgeTo)
+    {
+      // http://geomalgorithms.com/a05-_intersect-1.html#Line-Plane-Intersection
+
+      // Check for parallel line and plane
+      Vector direction = edgeTo - edgeFrom;
+      double denominator = boost::numeric::ublas::inner_prod(direction, normal);
+
+      if (fabs(denominator) < 100.0 * std::numeric_limits<double>::epsilon())
+      {
+        return false;
+      }
+      else
+      {
+        // Compute intersection
+        double t = -(normal[0] * edgeFrom[0] + 
+                     normal[1] * edgeFrom[1] + 
+                     normal[2] * edgeFrom[2] + d) / denominator;
+
+        if (t >= 0 && t <= 1)
+        {
+          // The intersection lies inside edge segment
+          p = edgeFrom + t * direction;
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+    }
+
+
+    bool IntersectPlaneAndLine(Vector& p,
+                               const Vector& normal,
+                               double d,
+                               const Vector& origin,
+                               const Vector& direction)
+    {
+      // http://geomalgorithms.com/a05-_intersect-1.html#Line-Plane-Intersection
+
+      // Check for parallel line and plane
+      double denominator = boost::numeric::ublas::inner_prod(direction, normal);
+
+      if (fabs(denominator) < 100.0 * std::numeric_limits<double>::epsilon())
+      {
+        return false;
+      }
+      else
+      {
+        // Compute intersection
+        double t = -(normal[0] * origin[0] + 
+                     normal[1] * origin[1] + 
+                     normal[2] * origin[2] + d) / denominator;
+
+        p = origin + t * direction;
+        return true;
+      }
+    }
+
+
+    void AlignVectorsWithRotation(Matrix& r,
+                                  const Vector& a,
+                                  const Vector& b)
+    {
+      // This is Rodrigues' rotation formula:
+      // https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula#Matrix_notation
+
+      // Check also result A4.6 from "Multiple View Geometry in Computer
+      // Vision - 2nd edition" (p. 584)
+  
+      if (a.size() != 3 ||
+          b.size() != 3)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      double aNorm = boost::numeric::ublas::norm_2(a);
+      double bNorm = boost::numeric::ublas::norm_2(b);
+
+      if (LinearAlgebra::IsCloseToZero(aNorm) ||
+          LinearAlgebra::IsCloseToZero(bNorm))
+      {
+        LOG(ERROR) << "Vector with zero norm";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);      
+      }
+
+      Vector aUnit, bUnit;
+      aUnit = a / aNorm;
+      bUnit = b / bNorm;
+
+      Vector v;
+      LinearAlgebra::CrossProduct(v, aUnit, bUnit);
+
+      double cosine = boost::numeric::ublas::inner_prod(aUnit, bUnit);
+
+      if (LinearAlgebra::IsCloseToZero(1 + cosine))
+      {
+        // "a == -b": TODO
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      Matrix k;
+      LinearAlgebra::CreateSkewSymmetric(k, v);
+
+#if 0
+      double sine = boost::numeric::ublas::norm_2(v);
+
+      r = (boost::numeric::ublas::identity_matrix<double>(3) +
+           sine * k + 
+           (1 - cosine) * boost::numeric::ublas::prod(k, k));
+#else
+      r = (boost::numeric::ublas::identity_matrix<double>(3) +
+           k + 
+           boost::numeric::ublas::prod(k, k) / (1 + cosine));
+#endif
+    }
   }
 }
--- a/Framework/Toolbox/GeometryToolbox.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/GeometryToolbox.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,55 +21,12 @@
 
 #pragma once
 
-#include <boost/numeric/ublas/vector.hpp>
-
-#include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
+#include "LinearAlgebra.h"
 
 namespace OrthancStone
 {
-  typedef boost::numeric::ublas::vector<double>   Vector;
-
   namespace GeometryToolbox
   {
-    void Print(const Vector& v);
-
-    bool ParseVector(Vector& target,
-                     const std::string& s);
-
-    bool ParseVector(Vector& target,
-                     const OrthancPlugins::IDicomDataset& dataset,
-                     const OrthancPlugins::DicomPath& tag);
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2);
-
-    void AssignVector(Vector& v,
-                      double v1,
-                      double v2,
-                      double v3);
-
-    inline bool IsNear(double x,
-                       double y,
-                       double threshold)
-    {
-      return fabs(x - y) < threshold;
-    }
-
-    bool IsNear(double x,
-                double y);
-
-    inline bool IsCloseToZero(double x)
-    {
-      return IsNear(x, 0.0);
-    }
-
-    void NormalizeVector(Vector& u);
-
-    void CrossProduct(Vector& result,
-                      const Vector& u,
-                      const Vector& v);
-
     void ProjectPointOntoPlane(Vector& result,
                                const Vector& point,
                                const Vector& planeNormal,
@@ -104,6 +61,101 @@
 
     void GetPixelSpacing(double& spacingX, 
                          double& spacingY,
-                         const OrthancPlugins::IDicomDataset& dicom);
+                         const Orthanc::DicomMap& dicom);
+
+    inline double ProjectAlongNormal(const Vector& point,
+                                     const Vector& normal)
+    {
+      return boost::numeric::ublas::inner_prod(point, normal);
+    }
+
+    Matrix CreateRotationMatrixAlongX(double a);
+
+    Matrix CreateRotationMatrixAlongY(double a);
+
+    Matrix CreateRotationMatrixAlongZ(double a);
+
+    Matrix CreateTranslationMatrix(double dx,
+                                   double dy,
+                                   double dz);
+
+    Matrix CreateScalingMatrix(double sx,
+                               double sy,
+                               double sz);
+    
+    bool IntersectPlaneAndSegment(Vector& p,
+                                  const Vector& normal,
+                                  double d,
+                                  const Vector& edgeFrom,
+                                  const Vector& edgeTo);
+
+    bool IntersectPlaneAndLine(Vector& p,
+                               const Vector& normal,
+                               double d,
+                               const Vector& origin,
+                               const Vector& direction);
+
+    void AlignVectorsWithRotation(Matrix& r,
+                                  const Vector& a,
+                                  const Vector& b);
+
+    inline float ComputeBilinearInterpolationUnitSquare(float x,
+                                                        float y,
+                                                        float f00,    // source(0, 0)
+                                                        float f01,    // source(1, 0)
+                                                        float f10,    // source(0, 1)
+                                                        float f11);   // source(1, 1)
+
+    inline float ComputeTrilinearInterpolationUnitSquare(float x,
+                                                         float y,
+                                                         float z,
+                                                         float f000,   // source(0, 0, 0)
+                                                         float f001,   // source(1, 0, 0)
+                                                         float f010,   // source(0, 1, 0)
+                                                         float f011,   // source(1, 1, 0)
+                                                         float f100,   // source(0, 0, 1)
+                                                         float f101,   // source(1, 0, 1)
+                                                         float f110,   // source(0, 1, 1)
+                                                         float f111);  // source(1, 1, 1)
   };
 }
+
+
+float OrthancStone::GeometryToolbox::ComputeBilinearInterpolationUnitSquare(float x,
+                                                                            float y,
+                                                                            float f00,
+                                                                            float f01,
+                                                                            float f10,
+                                                                            float f11)
+{
+  // This function only works within the unit square
+  assert(x >= 0 && y >= 0 && x <= 1 && y <= 1);
+
+  // https://en.wikipedia.org/wiki/Bilinear_interpolation#Unit_square
+  return (f00 * (1.0f - x) * (1.0f - y) +
+          f01 * x * (1.0f - y) +
+          f10 * (1.0f - x) * y +
+          f11 * x * y);
+}
+
+
+float OrthancStone::GeometryToolbox::ComputeTrilinearInterpolationUnitSquare(float x,
+                                                                             float y,
+                                                                             float z,
+                                                                             float f000,
+                                                                             float f001,
+                                                                             float f010,
+                                                                             float f011,
+                                                                             float f100,
+                                                                             float f101,
+                                                                             float f110,
+                                                                             float f111)
+{
+  // "In practice, a trilinear interpolation is identical to two
+  // bilinear interpolation combined with a linear interpolation"
+  // https://en.wikipedia.org/wiki/Trilinear_interpolation#Method
+  float a = ComputeBilinearInterpolationUnitSquare(x, y, f000, f001, f010, f011);
+  float b = ComputeBilinearInterpolationUnitSquare(x, y, f100, f101, f110, f111);
+
+  return (1.0f - z) * a + z * b;
+}
--- a/Framework/Toolbox/ISeriesLoader.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ISeriesLoader.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,16 +23,18 @@
 
 #include "ParallelSlices.h"
 
-#include "IThreadSafety.h"
-#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h"
+#include <Core/Images/ImageAccessor.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
 
 namespace OrthancStone
 {
-  // This class is NOT thread-safe
-  class ISeriesLoader : public IThreadUnsafe
+  class ISeriesLoader : public boost::noncopyable
   {
   public:
+    virtual ~ISeriesLoader()
+    {
+    }
+    
     virtual ParallelSlices& GetGeometry() = 0;
 
     virtual Orthanc::PixelFormat GetPixelFormat() = 0;
--- a/Framework/Toolbox/IThreadSafety.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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/noncopyable.hpp>
-
-namespace OrthancStone
-{
-  /**
-   * Dummy interface to explicitely tag the interfaces whose derived
-   * class must be thread-safe. The different methods of such classes
-   * might be simlultaneously invoked by several threads, and should
-   * be properly protected by mutexes.
-   **/
-  class IThreadSafe : public boost::noncopyable
-  {
-  public:
-    virtual ~IThreadSafe()
-    {
-    }
-  };
-
-
-  /**
-   * Dummy interface to explicitely tag the interfaces that are NOT
-   * expected to be thread-safe. The Orthanc Stone framework ensures
-   * that at most one method of such classes will be invoked at a
-   * given time. Such classes are automatically protected by the
-   * Orthanc Stone framework wherever required.
-   **/
-  class IThreadUnsafe : public boost::noncopyable
-  {
-  public:
-    virtual ~IThreadUnsafe()
-    {
-    }
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/IWebService.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,62 @@
+/**
+ * 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 <Core/IDynamicObject.h>
+
+#include <string>
+
+namespace OrthancStone
+{
+  class IWebService : public boost::noncopyable
+  {
+  public:
+    class ICallback : public boost::noncopyable
+    {
+    public:
+      virtual ~ICallback()
+      {
+      }
+
+      virtual void NotifyError(const std::string& uri,
+                               Orthanc::IDynamicObject* payload) = 0;
+
+      virtual void NotifySuccess(const std::string& uri,
+                                 const void* answer,
+                                 size_t answerSize,
+                                 Orthanc::IDynamicObject* payload) = 0;
+    };
+    
+    virtual ~IWebService()
+    {
+    }
+
+    virtual void ScheduleGetRequest(ICallback& callback,
+                                    const std::string& uri,
+                                    Orthanc::IDynamicObject* payload) = 0;
+
+    virtual void SchedulePostRequest(ICallback& callback,
+                                     const std::string& uri,
+                                     const std::string& body,
+                                     Orthanc::IDynamicObject* payload) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ImageGeometry.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,558 @@
+/**
+ * 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 "ImageGeometry.h"
+
+#include "Extent2D.h"
+#include "SubpixelReader.h"
+
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
+
+
+namespace OrthancStone
+{
+  static void AddTransformedPoint(Extent2D& extent,
+                                  const Matrix& a,
+                                  double x,
+                                  double y)
+  {
+    assert(a.size1() == 3 &&
+           a.size2() == 3);
+
+    Vector p = LinearAlgebra::Product(a, LinearAlgebra::CreateVector(x, y, 1));
+
+    if (!LinearAlgebra::IsCloseToZero(p[2]))
+    {
+      extent.AddPoint(p[0] / p[2], p[1] / p[2]);
+    }
+  }
+  
+  
+  bool GetProjectiveTransformExtent(unsigned int& x1,
+                                    unsigned int& y1,
+                                    unsigned int& x2,
+                                    unsigned int& y2,
+                                    const Matrix& a,
+                                    unsigned int sourceWidth,
+                                    unsigned int sourceHeight,
+                                    unsigned int targetWidth,
+                                    unsigned int targetHeight)
+  {
+    if (targetWidth == 0 ||
+        targetHeight == 0)
+    {
+      return false;
+    }
+    
+    Extent2D extent;
+    AddTransformedPoint(extent, a, 0, 0);
+    AddTransformedPoint(extent, a, sourceWidth, 0);
+    AddTransformedPoint(extent, a, 0, sourceHeight);
+    AddTransformedPoint(extent, a, sourceWidth, sourceHeight);
+
+    if (extent.IsEmpty())
+    {
+      return false;
+    }
+    else
+    {
+      int tmp;
+
+      tmp = std::floor(extent.GetX1());
+      if (tmp < 0)
+      {
+        x1 = 0;
+      }
+      else
+      {
+        x1 = static_cast<unsigned int>(tmp);
+      }
+
+      tmp = std::floor(extent.GetY1());
+      if (tmp < 0)
+      {
+        y1 = 0;
+      }
+      else
+      {
+        y1 = static_cast<unsigned int>(tmp);
+      }
+
+      tmp = std::ceil(extent.GetX2());
+      if (tmp < 0)
+      {
+        return false;
+      }
+      else if (static_cast<unsigned int>(tmp) >= targetWidth)
+      {
+        x2 = targetWidth - 1;
+      }
+      else
+      {
+        x2 = static_cast<unsigned int>(tmp);
+      }
+
+      tmp = std::ceil(extent.GetY2());
+      if (tmp < 0)
+      {
+        return false;
+      }
+      else if (static_cast<unsigned int>(tmp) >= targetHeight)
+      {
+        y2 = targetHeight - 1;
+      }
+      else
+      {
+        y2 = static_cast<unsigned int>(tmp);
+      }
+
+      return (x1 <= x2 &&
+              y1 <= y2);
+    }
+  }
+
+
+  template <typename Reader,
+            bool HasOffsetX,
+            bool HasOffsetY>
+  static void ApplyAffineTransformToRow(typename Reader::PixelType* p,
+                                        Reader& reader,
+                                        unsigned int x1,
+                                        unsigned int x2,
+                                        float positionX,
+                                        float positionY,
+                                        float offsetX,
+                                        float offsetY)
+  {
+    typename Reader::PixelType value;
+
+    for (unsigned int x = x1; x <= x2; x++, p++)
+    {     
+      if (reader.GetValue(value, positionX, positionY))
+      {
+        *p = value;
+      }
+      else
+      {
+        Reader::Traits::SetZero(*p);
+      }
+
+      if (HasOffsetX)
+      {
+        positionX += offsetX;
+      }
+
+      if (HasOffsetY)
+      {
+        positionY += offsetY;
+      }
+    }
+  }
+  
+
+  template <Orthanc::PixelFormat Format,
+            ImageInterpolation Interpolation>
+  static void ApplyAffineInternal(Orthanc::ImageAccessor& target,
+                                  const Orthanc::ImageAccessor& source,
+                                  const Matrix& a)
+  {
+    assert(target.GetFormat() == Format &&
+           source.GetFormat() == Format);
+    
+    typedef SubpixelReader<Format, Interpolation>  Reader;
+    typedef typename Reader::PixelType             PixelType;
+
+    if (Format == Orthanc::PixelFormat_RGB24)
+    {
+      Orthanc::ImageProcessing::Set(target, 0, 0, 0, 255);
+    }
+    else
+    {
+      Orthanc::ImageProcessing::Set(target, 0);
+    }
+
+    Matrix inva;
+    if (!LinearAlgebra::InvertMatrixUnsafe(inva, a))
+    {
+      // Singular matrix
+      return;
+    }
+
+    Reader reader(source);
+
+    unsigned int x1, y1, x2, y2;
+
+    if (GetProjectiveTransformExtent(x1, y1, x2, y2, a,
+                                     source.GetWidth(), source.GetHeight(),
+                                     target.GetWidth(), target.GetHeight()))
+    {
+      const size_t targetPitch = target.GetPitch();
+      uint8_t *targetRow = reinterpret_cast<uint8_t*>
+        (reinterpret_cast<PixelType*>(target.GetRow(y1)) + x1);
+
+      for (unsigned int y = y1; y <= y2; y++)
+      {
+        Vector start;
+        LinearAlgebra::AssignVector(start, static_cast<double>(x1) + 0.5,
+                                    static_cast<double>(y) + 0.5, 1);
+        start = boost::numeric::ublas::prod(inva, start);
+        assert(LinearAlgebra::IsNear(1.0, start(2)));
+
+        Vector offset;
+        LinearAlgebra::AssignVector(offset, static_cast<double>(x1) + 1.5,
+                                    static_cast<double>(y) + 0.5, 1);
+        offset = boost::numeric::ublas::prod(inva, offset) - start;
+        assert(LinearAlgebra::IsNear(0.0, offset(2)));
+
+        float startX = static_cast<float>(start[0]);
+        float startY = static_cast<float>(start[1]);
+        float offsetX = static_cast<float>(offset[0]);
+        float offsetY = static_cast<float>(offset[1]);
+
+        PixelType* pixel = reinterpret_cast<PixelType*>(targetRow);
+        if (LinearAlgebra::IsCloseToZero(offsetX))
+        {
+          ApplyAffineTransformToRow<Reader, false, true>
+            (pixel, reader, x1, x2, startX, startY, offsetX, offsetY);
+        }
+        else if (LinearAlgebra::IsCloseToZero(offsetY))
+        {
+          ApplyAffineTransformToRow<Reader, true, false>
+            (pixel, reader, x1, x2, startX, startY, offsetX, offsetY);
+        }
+        else
+        {
+          ApplyAffineTransformToRow<Reader, true, true>
+            (pixel, reader, x1, x2, startX, startY, offsetX, offsetY);
+        }
+
+        targetRow += targetPitch;
+      }
+    }    
+  }
+
+
+  void ApplyAffineTransform(Orthanc::ImageAccessor& target,
+                            const Orthanc::ImageAccessor& source,
+                            double a11,
+                            double a12,
+                            double b1,
+                            double a21,
+                            double a22,
+                            double b2,
+                            ImageInterpolation interpolation)
+  {
+    if (source.GetFormat() != target.GetFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (interpolation != ImageInterpolation_Nearest &&
+        interpolation != ImageInterpolation_Bilinear)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    Matrix a;
+    a.resize(3, 3);
+    a(0, 0) = a11;
+    a(0, 1) = a12;
+    a(0, 2) = b1;
+    a(1, 0) = a21;
+    a(1, 1) = a22;
+    a(1, 2) = b2;
+    a(2, 0) = 0;
+    a(2, 1) = 0;
+    a(2, 2) = 1;
+
+    switch (source.GetFormat())
+    {
+      case Orthanc::PixelFormat_Grayscale8:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyAffineInternal<Orthanc::PixelFormat_Grayscale8, 
+                                ImageInterpolation_Nearest>(target, source, a);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyAffineInternal<Orthanc::PixelFormat_Grayscale8, 
+                                ImageInterpolation_Bilinear>(target, source, a);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      case Orthanc::PixelFormat_Grayscale16:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyAffineInternal<Orthanc::PixelFormat_Grayscale16, 
+                                ImageInterpolation_Nearest>(target, source, a);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyAffineInternal<Orthanc::PixelFormat_Grayscale16, 
+                                ImageInterpolation_Bilinear>(target, source, a);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyAffineInternal<Orthanc::PixelFormat_SignedGrayscale16, 
+                                ImageInterpolation_Nearest>(target, source, a);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyAffineInternal<Orthanc::PixelFormat_SignedGrayscale16, 
+                                ImageInterpolation_Bilinear>(target, source, a);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      case Orthanc::PixelFormat_RGB24:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyAffineInternal<Orthanc::PixelFormat_RGB24, 
+                                ImageInterpolation_Nearest>(target, source, a);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+  template <Orthanc::PixelFormat Format,
+            ImageInterpolation Interpolation>
+  static void ApplyProjectiveInternal(Orthanc::ImageAccessor& target,
+                                      const Orthanc::ImageAccessor& source,
+                                      const Matrix& a,
+                                      const Matrix& inva)
+  {
+    assert(target.GetFormat() == Format &&
+           source.GetFormat() == Format);
+    
+    typedef SubpixelReader<Format, Interpolation> Reader;
+    typedef typename Reader::PixelType            PixelType;
+
+    Reader reader(source);
+    unsigned int x1, y1, x2, y2;
+
+    const float floatWidth = source.GetWidth();
+    const float floatHeight = source.GetHeight();
+
+    if (GetProjectiveTransformExtent(x1, y1, x2, y2, a,
+                                     source.GetWidth(), source.GetHeight(),
+                                     target.GetWidth(), target.GetHeight()))
+    {
+      const size_t targetPitch = target.GetPitch();
+      uint8_t *targetRow = reinterpret_cast<uint8_t*>
+        (reinterpret_cast<PixelType*>(target.GetRow(y1)) + x1);
+
+      for (unsigned int y = y1; y <= y2; y++)
+      {
+        PixelType *p = reinterpret_cast<PixelType*>(targetRow);
+
+        for (unsigned int x = x1; x <= x2; x++)
+        {
+          Vector v;
+          LinearAlgebra::AssignVector(v, static_cast<double>(x) + 0.5, 
+                                      static_cast<double>(y) + 0.5, 1);
+
+          Vector vv = LinearAlgebra::Product(inva, v);
+
+          assert(!LinearAlgebra::IsCloseToZero(vv[2]));
+          const double w = 1.0 / vv[2];
+          const float sourceX = static_cast<float>(vv[0] * w);
+          const float sourceY = static_cast<float>(vv[1] * w);
+          
+          // Make sure no integer overflow will occur after truncation
+          // (the static_cast<unsigned int> could otherwise throw an
+          // exception in WebAssembly if strong projective effects)
+          if (sourceX < floatWidth &&
+              sourceY < floatHeight)
+          { 
+            reader.GetValue(*p, sourceX, sourceY);
+          }
+          else
+          {
+            Reader::Traits::SetZero(*p);
+          }
+
+          p++;
+        }
+
+        targetRow += targetPitch;
+      }
+    }
+  }
+
+    
+  void ApplyProjectiveTransform(Orthanc::ImageAccessor& target,
+                                const Orthanc::ImageAccessor& source,
+                                const Matrix& a,
+                                ImageInterpolation interpolation)
+  {
+    if (source.GetFormat() != target.GetFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (a.size1() != 3 ||
+        a.size2() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
+    }
+
+    if (interpolation != ImageInterpolation_Nearest &&
+        interpolation != ImageInterpolation_Bilinear)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    // Check whether we are dealing with an affine transform
+    if (LinearAlgebra::IsCloseToZero(a(2, 0)) &&
+        LinearAlgebra::IsCloseToZero(a(2, 1)))
+    {
+      double w = a(2, 2);
+      if (LinearAlgebra::IsCloseToZero(w))
+      {
+        LOG(ERROR) << "Singular projective matrix";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        ApplyAffineTransform(target, source, 
+                             a(0, 0) / w, a(0, 1) / w, a(0, 2) / w,
+                             a(1, 0) / w, a(1, 1) / w, a(1, 2) / w,
+                             interpolation);
+        return;
+      }
+    }
+
+    if (target.GetFormat() == Orthanc::PixelFormat_RGB24)
+    {
+      Orthanc::ImageProcessing::Set(target, 0, 0, 0, 255);
+    }
+    else
+    {
+      Orthanc::ImageProcessing::Set(target, 0);
+    }
+
+    Matrix inva;
+    if (!LinearAlgebra::InvertMatrixUnsafe(inva, a))
+    {
+      return;
+    }
+
+    switch (source.GetFormat())
+    {
+      case Orthanc::PixelFormat_Grayscale8:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_Grayscale8, 
+                                    ImageInterpolation_Nearest>(target, source, a, inva);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_Grayscale8, 
+                                    ImageInterpolation_Bilinear>(target, source, a, inva);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      case Orthanc::PixelFormat_Grayscale16:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_Grayscale16, 
+                                    ImageInterpolation_Nearest>(target, source, a, inva);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_Grayscale16, 
+                                    ImageInterpolation_Bilinear>(target, source, a, inva);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_SignedGrayscale16, 
+                                    ImageInterpolation_Nearest>(target, source, a, inva);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_SignedGrayscale16, 
+                                    ImageInterpolation_Bilinear>(target, source, a, inva);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      case Orthanc::PixelFormat_RGB24:
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ApplyProjectiveInternal<Orthanc::PixelFormat_RGB24, 
+                                    ImageInterpolation_Nearest>(target, source, a, inva);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ImageGeometry.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,59 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../StoneEnumerations.h"
+#include "LinearAlgebra.h"
+
+#include <Core/Images/ImageAccessor.h>
+
+
+namespace OrthancStone
+{
+  // Returns the "useful" portion of the target image when applying a
+  // 3x3 perspective transform "a" (i.e. the bounding box where points
+  // of the source image are mapped to)
+  bool GetProjectiveTransformExtent(unsigned int& x1,
+                                    unsigned int& y1,
+                                    unsigned int& x2,
+                                    unsigned int& y2,
+                                    const Matrix& a,
+                                    unsigned int sourceWidth,
+                                    unsigned int sourceHeight,
+                                    unsigned int targetWidth,
+                                    unsigned int targetHeight);
+
+  void ApplyAffineTransform(Orthanc::ImageAccessor& target,
+                            const Orthanc::ImageAccessor& source,
+                            double a11,
+                            double a12,
+                            double b1,
+                            double a21,
+                            double a22,
+                            double b2,
+                            ImageInterpolation interpolation);
+
+  void ApplyProjectiveTransform(Orthanc::ImageAccessor& target,
+                                const Orthanc::ImageAccessor& source,
+                                const Matrix& a,
+                                ImageInterpolation interpolation);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/LinearAlgebra.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,627 @@
+/**
+ * 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 "LinearAlgebra.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/numeric/ublas/lu.hpp>
+
+namespace OrthancStone
+{
+  namespace LinearAlgebra
+  {
+    void Print(const Vector& v)
+    {
+      for (size_t i = 0; i < v.size(); i++)
+      {
+        printf("%g\n", v[i]);
+        //printf("%8.2f\n", v[i]);
+      }
+      printf("\n");
+    }
+
+
+    void Print(const Matrix& m)
+    {
+      for (size_t i = 0; i < m.size1(); i++)
+      {
+        for (size_t j = 0; j < m.size2(); j++)
+        {
+          printf("%g  ", m(i,j));
+          //printf("%8.2f  ", m(i,j));
+        }
+        printf("\n");        
+      }
+      printf("\n");        
+    }
+
+
+    bool ParseVector(Vector& target,
+                     const std::string& value)
+    {
+      std::vector<std::string> items;
+      Orthanc::Toolbox::TokenizeString(items, value, '\\');
+
+      target.resize(items.size());
+
+      for (size_t i = 0; i < items.size(); i++)
+      {
+        try
+        {
+          target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i]));
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          target.clear();
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    bool ParseVector(Vector& target,
+                     const Orthanc::DicomMap& dataset,
+                     const Orthanc::DicomTag& tag)
+    {
+      std::string value;
+      return (dataset.CopyToString(value, tag, false) &&
+              ParseVector(target, value));
+    }
+
+
+    void NormalizeVector(Vector& u)
+    {
+      double norm = boost::numeric::ublas::norm_2(u);
+      if (!IsCloseToZero(norm))
+      {
+        u = u / norm;
+      }
+    }
+
+
+    void CrossProduct(Vector& result,
+                      const Vector& u,
+                      const Vector& v)
+    {
+      if (u.size() != 3 ||
+          v.size() != 3)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      result.resize(3);
+
+      result[0] = u[1] * v[2] - u[2] * v[1];
+      result[1] = u[2] * v[0] - u[0] * v[2];
+      result[2] = u[0] * v[1] - u[1] * v[0];
+    }
+
+
+    void FillMatrix(Matrix& target,
+                    size_t rows,
+                    size_t columns,
+                    const double values[])
+    {
+      target.resize(rows, columns);
+
+      size_t index = 0;
+
+      for (size_t y = 0; y < rows; y++)
+      {
+        for (size_t x = 0; x < columns; x++, index++)
+        {
+          target(y, x) = values[index];
+        }
+      }
+    }
+
+
+    void FillVector(Vector& target,
+                    size_t size,
+                    const double values[])
+    {
+      target.resize(size);
+
+      for (size_t i = 0; i < size; i++)
+      {
+        target[i] = values[i];
+      }
+    }
+
+
+    void Convert(Matrix& target,
+                 const Vector& source)
+    {
+      const size_t n = source.size();
+
+      target.resize(n, 1);
+
+      for (size_t i = 0; i < n; i++)
+      {
+        target(i, 0) = source[i];
+      }      
+    }
+
+    
+    double ComputeDeterminant(const Matrix& a)
+    {
+      if (a.size1() != a.size2())
+      {
+        LOG(ERROR) << "Determinant only exists for square matrices";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    
+      // https://en.wikipedia.org/wiki/Rule_of_Sarrus
+      if (a.size1() == 1)
+      {
+        return a(0,0);
+      }
+      else if (a.size1() == 2)
+      {
+        return a(0,0) * a(1,1) - a(0,1) * a(1,0);
+      }
+      else if (a.size1() == 3)
+      {
+        return (a(0,0) * a(1,1) * a(2,2) + 
+                a(0,1) * a(1,2) * a(2,0) +
+                a(0,2) * a(1,0) * a(2,1) -
+                a(2,0) * a(1,1) * a(0,2) -
+                a(2,1) * a(1,2) * a(0,0) -
+                a(2,2) * a(1,0) * a(0,1));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+    
+
+    bool IsOrthogonalMatrix(const Matrix& q,
+                            double threshold)
+    {
+      // https://en.wikipedia.org/wiki/Orthogonal_matrix
+
+      using namespace boost::numeric::ublas;
+
+      const Matrix check = prod(trans(q), q) - identity_matrix<double>(3);
+
+      type_traits<double>::real_type norm = norm_inf(check);
+
+      return (norm <= threshold);
+    }
+
+
+    bool IsOrthogonalMatrix(const Matrix& q)
+    {
+      return IsOrthogonalMatrix(q, 10.0 * std::numeric_limits<float>::epsilon());
+    }
+
+
+    bool IsRotationMatrix(const Matrix& r,
+                          double threshold)
+    {
+      return (IsOrthogonalMatrix(r, threshold) &&
+              IsNear(ComputeDeterminant(r), 1.0, threshold));
+    }
+
+
+    bool IsRotationMatrix(const Matrix& r)
+    {
+      return IsRotationMatrix(r, 10.0 * std::numeric_limits<float>::epsilon());
+    }
+
+
+    void InvertUpperTriangularMatrix(Matrix& output,
+                                     const Matrix& k)
+    {
+      if (k.size1() != k.size2())
+      {
+        LOG(ERROR) << "Determinant only exists for square matrices";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      output.resize(k.size1(), k.size2());
+
+      for (size_t i = 1; i < k.size1(); i++)
+      {
+        for (size_t j = 0; j < i; j++)
+        {
+          if (!IsCloseToZero(k(i, j)))
+          {
+            LOG(ERROR) << "Not an upper triangular matrix";
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          }
+
+          output(i, j) = 0;  // The output is also upper triangular
+        }
+      }
+
+      if (k.size1() == 3)
+      {
+        // https://math.stackexchange.com/a/1004181
+        double a = k(0, 0);
+        double b = k(0, 1);
+        double c = k(0, 2);
+        double d = k(1, 1);
+        double e = k(1, 2);
+        double f = k(2, 2);
+
+        if (IsCloseToZero(a) ||
+            IsCloseToZero(d) ||
+            IsCloseToZero(f))
+        {
+          LOG(ERROR) << "Singular upper triangular matrix";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+        else
+        {
+          output(0, 0) = 1.0 / a;
+          output(0, 1) = -b / (a * d);
+          output(0, 2) = (b * e - c * d) / (a * f * d);
+          output(1, 1) = 1.0 / d;
+          output(1, 2) = -e / (f * d);
+          output(2, 2) = 1.0 / f;
+        }
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+
+
+    static void GetGivensComponent(double& c,
+                                   double& s,
+                                   const Matrix& a,
+                                   size_t i,
+                                   size_t j)
+    {
+      assert(i < 3 && j < 3);
+
+      double x = a(i, i);
+      double y = a(i, j);
+      double n = sqrt(x * x + y * y);
+
+      if (IsCloseToZero(n))
+      {
+        c = 1;
+        s = 0;
+      }
+      else
+      {
+        c = x / n;
+        s = -y / n;
+      }
+    }
+
+
+    /**
+     * This function computes the RQ decomposition of a 3x3 matrix,
+     * using Givens rotations. Reference: Algorithm A4.1 (page 579) of
+     * "Multiple View Geometry in Computer Vision" (2nd edition).  The
+     * output matrix "Q" is a rotation matrix, and "R" is upper
+     * triangular.
+     **/
+    void RQDecomposition3x3(Matrix& r,
+                            Matrix& q,
+                            const Matrix& a)
+    {
+      using namespace boost::numeric::ublas;
+      
+      if (a.size1() != 3 ||
+          a.size2() != 3)
+      {
+        LOG(ERROR) << "Only applicable to a 3x3 matrix";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      r.resize(3, 3);
+      q.resize(3, 3);
+
+      r = a;
+      q = identity_matrix<double>(3);
+
+      {
+        // Set A(2,1) to zero
+        double c, s;
+        GetGivensComponent(c, s, r, 2, 1);
+
+        double v[9] = { 1, 0, 0, 
+                        0, c, -s,
+                        0, s, c };
+
+        Matrix g;
+        FillMatrix(g, 3, 3, v);
+
+        r = prod(r, g);
+        q = prod(trans(g), q);
+      }
+
+
+      {
+        // Set A(2,0) to zero
+        double c, s;
+        GetGivensComponent(c, s, r, 2, 0);
+
+        double v[9] = { c, 0, -s, 
+                        0, 1, 0,
+                        s, 0, c };
+
+        Matrix g;
+        FillMatrix(g, 3, 3, v);
+
+        r = prod(r, g);
+        q = prod(trans(g), q);
+      }
+
+
+      {
+        // Set A(1,0) to zero
+        double c, s;
+        GetGivensComponent(c, s, r, 1, 0);
+
+        double v[9] = { c, -s, 0, 
+                        s, c, 0,
+                        0, 0, 1 };
+
+        Matrix g;
+        FillMatrix(g, 3, 3, v);
+
+        r = prod(r, g);
+        q = prod(trans(g), q);
+      }
+
+      if (!IsCloseToZero(norm_inf(prod(r, q) - a)) ||
+          !IsRotationMatrix(q) ||
+          !IsCloseToZero(r(1, 0)) ||
+          !IsCloseToZero(r(2, 0)) ||
+          !IsCloseToZero(r(2, 1)))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+
+    bool InvertMatrixUnsafe(Matrix& target,
+                            const Matrix& source)
+    {
+      if (source.size1() != source.size2())
+      {
+        LOG(ERROR) << "Inverse only exists for square matrices";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      if (source.size1() < 4)
+      {
+        // For matrices with size below 4, use direct computations
+        // instead of LU decomposition
+
+        if (source.size1() == 0)
+        {
+          // By convention, the inverse of the empty matrix, is itself the empty matrix
+          target.resize(0, 0);
+          return true;
+        }
+
+        double determinant = ComputeDeterminant(source);
+
+        if (IsCloseToZero(determinant))
+        {
+          return false;
+        }
+
+        double denominator = 1.0 / determinant;
+
+        target.resize(source.size1(), source.size2());
+
+        if (source.size1() == 1)
+        {
+          target(0, 0) = denominator;
+
+          return true;
+        }
+        else if (source.size1() == 2)
+        {
+          // https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_2_%C3%97_2_matrices
+          target(0, 0) =  source(1, 1) * denominator;
+          target(0, 1) = -source(0, 1) * denominator;
+          target(1, 0) = -source(1, 0) * denominator;
+          target(1, 1) =  source(0, 0) * denominator;
+
+          return true;
+        }
+        else if (source.size1() == 3)
+        {
+          // https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices
+          const double a = source(0, 0);
+          const double b = source(0, 1);
+          const double c = source(0, 2);
+          const double d = source(1, 0);
+          const double e = source(1, 1);
+          const double f = source(1, 2);
+          const double g = source(2, 0);
+          const double h = source(2, 1);
+          const double i = source(2, 2);
+        
+          target(0, 0) =  (e * i - f * h) * denominator;
+          target(0, 1) = -(b * i - c * h) * denominator;
+          target(0, 2) =  (b * f - c * e) * denominator;
+          target(1, 0) = -(d * i - f * g) * denominator;
+          target(1, 1) =  (a * i - c * g) * denominator;
+          target(1, 2) = -(a * f - c * d) * denominator;
+          target(2, 0) =  (d * h - e * g) * denominator;
+          target(2, 1) = -(a * h - b * g) * denominator;
+          target(2, 2) =  (a * e - b * d) * denominator;
+
+          return true;
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+      }
+      else
+      {
+        // General case, using LU decomposition
+
+        Matrix a = source;  // Copy the source matrix, as "lu_factorize()" modifies it
+
+        boost::numeric::ublas::permutation_matrix<size_t>  permutation(source.size1());
+        
+        if (boost::numeric::ublas::lu_factorize(a, permutation) != 0)
+        {
+          return false;
+        }
+        else
+        {
+          target = boost::numeric::ublas::identity_matrix<double>(source.size1());
+          lu_substitute(a, permutation, target);
+          return true;
+        }
+      }
+    }
+    
+
+    
+    void InvertMatrix(Matrix& target,
+                      const Matrix& source)
+    {
+      if (!InvertMatrixUnsafe(target, source))
+      {
+        LOG(ERROR) << "Cannot invert singular matrix";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    
+    void CreateSkewSymmetric(Matrix& s,
+                             const Vector& v)
+    {
+      assert(v.size() == 3);
+
+      s.resize(3, 3);
+      s(0,0) = 0;
+      s(0,1) = -v[2];
+      s(0,2) = v[1];
+      s(1,0) = v[2];
+      s(1,1) = 0;
+      s(1,2) = -v[0];
+      s(2,0) = -v[1];
+      s(2,1) = v[0];
+      s(2,2) = 0;
+    }
+
+  
+    Matrix InvertScalingTranslationMatrix(const Matrix& t)
+    {
+      if (t.size1() != 4 ||
+          t.size2() != 4 ||
+          !LinearAlgebra::IsCloseToZero(t(0,1)) ||
+          !LinearAlgebra::IsCloseToZero(t(0,2)) ||
+          !LinearAlgebra::IsCloseToZero(t(1,0)) ||
+          !LinearAlgebra::IsCloseToZero(t(1,2)) ||
+          !LinearAlgebra::IsCloseToZero(t(2,0)) ||
+          !LinearAlgebra::IsCloseToZero(t(2,1)) ||
+          !LinearAlgebra::IsCloseToZero(t(3,0)) ||
+          !LinearAlgebra::IsCloseToZero(t(3,1)) ||
+          !LinearAlgebra::IsCloseToZero(t(3,2)))
+      {
+        LOG(ERROR) << "This matrix is more than a zoom/translate transform";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      const double sx = t(0,0);
+      const double sy = t(1,1);
+      const double sz = t(2,2);
+      const double w = t(3,3);
+
+      if (LinearAlgebra::IsCloseToZero(sx) ||
+          LinearAlgebra::IsCloseToZero(sy) ||
+          LinearAlgebra::IsCloseToZero(sz) ||
+          LinearAlgebra::IsCloseToZero(w))
+      {
+        LOG(ERROR) << "Singular transform";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    
+      const double tx = t(0,3);
+      const double ty = t(1,3);
+      const double tz = t(2,3);
+    
+      Matrix m = IdentityMatrix(4);
+
+      m(0,0) = 1.0 / sx;
+      m(1,1) = 1.0 / sy;
+      m(2,2) = 1.0 / sz;
+      m(3,3) = 1.0 / w;
+
+      m(0,3) = -tx / (sx * w);
+      m(1,3) = -ty / (sy * w);
+      m(2,3) = -tz / (sz * w);
+
+      return m;
+    }
+
+
+    bool IsShearMatrix(const Matrix& shear)
+    {
+      return (shear.size1() == 4 &&
+              shear.size2() == 4 &&
+              LinearAlgebra::IsNear(1.0, shear(0,0)) &&
+              LinearAlgebra::IsNear(0.0, shear(0,1)) &&
+              LinearAlgebra::IsNear(0.0, shear(0,3)) &&
+              LinearAlgebra::IsNear(0.0, shear(1,0)) &&
+              LinearAlgebra::IsNear(1.0, shear(1,1)) &&
+              LinearAlgebra::IsNear(0.0, shear(1,3)) &&
+              LinearAlgebra::IsNear(0.0, shear(2,0)) &&
+              LinearAlgebra::IsNear(0.0, shear(2,1)) &&
+              LinearAlgebra::IsNear(1.0, shear(2,2)) &&
+              LinearAlgebra::IsNear(0.0, shear(2,3)) &&
+              LinearAlgebra::IsNear(0.0, shear(3,0)) &&
+              LinearAlgebra::IsNear(0.0, shear(3,1)) &&
+              LinearAlgebra::IsNear(1.0, shear(3,3)));
+    }
+  
+
+    Matrix InvertShearMatrix(const Matrix& shear)
+    {
+      if (!IsShearMatrix(shear))
+      {
+        LOG(ERROR) << "Not a valid shear matrix";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      Matrix m = IdentityMatrix(4);
+      m(0,2) = -shear(0,2);
+      m(1,2) = -shear(1,2);
+      m(3,2) = -shear(3,2);
+
+      return m;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/LinearAlgebra.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,293 @@
+/**
+ * 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
+
+// Patch for ublas in Boost 1.64.0
+// https://github.com/dealii/dealii/issues/4302
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106300  // or 64, need to check
+#  include <boost/serialization/array_wrapper.hpp>
+#endif
+
+#include <Core/DicomFormat/DicomMap.h>
+
+#include <boost/numeric/ublas/matrix.hpp>
+#include <boost/numeric/ublas/vector.hpp>
+
+namespace OrthancStone
+{
+  typedef boost::numeric::ublas::matrix<double>   Matrix;
+  typedef boost::numeric::ublas::vector<double>   Vector;
+
+  namespace LinearAlgebra
+  {
+    void Print(const Vector& v);
+
+    void Print(const Matrix& m);
+
+    bool ParseVector(Vector& target,
+                     const std::string& s);
+
+    bool ParseVector(Vector& target,
+                     const Orthanc::DicomMap& dataset,
+                     const Orthanc::DicomTag& tag);
+
+    inline void AssignVector(Vector& v,
+                             double v1,
+                             double v2)
+    {
+      v.resize(2);
+      v[0] = v1;
+      v[1] = v2;
+    }
+
+
+    inline void AssignVector(Vector& v,
+                             double v1)
+    {
+      v.resize(1);
+      v[0] = v1;
+    }
+
+
+    inline void AssignVector(Vector& v,
+                             double v1,
+                             double v2,
+                             double v3)
+    {
+      v.resize(3);
+      v[0] = v1;
+      v[1] = v2;
+      v[2] = v3;
+    }
+
+
+    inline void AssignVector(Vector& v,
+                             double v1,
+                             double v2,
+                             double v3,
+                             double v4)
+    {
+      v.resize(4);
+      v[0] = v1;
+      v[1] = v2;
+      v[2] = v3;
+      v[3] = v4;
+    }
+
+
+    inline Vector CreateVector(double v1)
+    {
+      Vector v;
+      AssignVector(v, v1);
+      return v;
+    }
+
+    
+    inline Vector CreateVector(double v1,
+                               double v2)
+    {
+      Vector v;
+      AssignVector(v, v1, v2);
+      return v;
+    }
+
+    
+    inline Vector CreateVector(double v1,
+                               double v2,
+                               double v3)
+    {
+      Vector v;
+      AssignVector(v, v1, v2, v3);
+      return v;
+    }
+
+    
+    inline Vector CreateVector(double v1,
+                               double v2,
+                               double v3,
+                               double v4)
+    {
+      Vector v;
+      AssignVector(v, v1, v2, v3, v4);
+      return v;
+    }
+
+
+    inline bool IsNear(double x,
+                       double y,
+                       double threshold)
+    {
+      return fabs(x - y) < threshold;
+    }
+
+    inline bool IsNear(double x,
+                       double y)
+    {
+      // As most input is read as single-precision numbers, we take the
+      // epsilon machine for float32 into consideration to compare numbers
+      return IsNear(x, y, 10.0 * std::numeric_limits<float>::epsilon());
+    }
+
+    inline bool IsCloseToZero(double x)
+    {
+      return IsNear(x, 0.0);
+    }
+
+    void NormalizeVector(Vector& u);
+
+    void CrossProduct(Vector& result,
+                      const Vector& u,
+                      const Vector& v);
+    
+    void FillMatrix(Matrix& target,
+                    size_t rows,
+                    size_t columns,
+                    const double values[]);
+
+    void FillVector(Vector& target,
+                    size_t size,
+                    const double values[]);
+
+    void Convert(Matrix& target,
+                 const Vector& source);
+
+    inline Matrix Transpose(const Matrix& a)
+    {
+      return boost::numeric::ublas::trans(a);
+    }
+
+
+    inline Matrix IdentityMatrix(size_t size)
+    {
+      return boost::numeric::ublas::identity_matrix<double>(size);
+    }
+
+
+    inline Matrix ZeroMatrix(size_t size1,
+                             size_t size2)
+    {
+      return boost::numeric::ublas::zero_matrix<double>(size1, size2);
+    }
+
+
+    inline Matrix Product(const Matrix& a,
+                          const Matrix& b)
+    {
+      return boost::numeric::ublas::prod(a, b);
+    }
+
+
+    inline Vector Product(const Matrix& a,
+                          const Vector& b)
+    {
+      return boost::numeric::ublas::prod(a, b);
+    }
+
+
+    inline Matrix Product(const Matrix& a,
+                          const Matrix& b,
+                          const Matrix& c)
+    {
+      return Product(a, Product(b, c));
+    }
+
+
+    inline Matrix Product(const Matrix& a,
+                          const Matrix& b,
+                          const Matrix& c,
+                          const Matrix& d)
+    {
+      return Product(a, Product(b, c, d));
+    }
+
+
+    inline Matrix Product(const Matrix& a,
+                          const Matrix& b,
+                          const Matrix& c,
+                          const Matrix& d,
+                          const Matrix& e)
+    {
+      return Product(a, Product(b, c, d, e));
+    }
+
+
+    inline Vector Product(const Matrix& a,
+                          const Matrix& b,
+                          const Vector& c)
+    {
+      return Product(Product(a, b), c);
+    }
+
+
+    inline Vector Product(const Matrix& a,
+                          const Matrix& b,
+                          const Matrix& c,
+                          const Vector& d)
+    {
+      return Product(Product(a, b, c), d);
+    }
+
+
+    double ComputeDeterminant(const Matrix& a);
+
+    bool IsOrthogonalMatrix(const Matrix& q,
+                            double threshold);
+
+    bool IsOrthogonalMatrix(const Matrix& q);
+
+    bool IsRotationMatrix(const Matrix& r,
+                          double threshold);
+
+    bool IsRotationMatrix(const Matrix& r);
+
+    void InvertUpperTriangularMatrix(Matrix& output,
+                                     const Matrix& k); 
+
+    /**
+     * This function computes the RQ decomposition of a 3x3 matrix,
+     * using Givens rotations. Reference: Algorithm A4.1 (page 579) of
+     * "Multiple View Geometry in Computer Vision" (2nd edition).  The
+     * output matrix "Q" is a rotation matrix, and "R" is upper
+     * triangular.
+     **/
+    void RQDecomposition3x3(Matrix& r,
+                            Matrix& q,
+                            const Matrix& a);
+
+    void InvertMatrix(Matrix& target,
+                      const Matrix& source);
+
+    // This is the same as "InvertMatrix()", but without exception
+    bool InvertMatrixUnsafe(Matrix& target,
+                            const Matrix& source);
+
+    void CreateSkewSymmetric(Matrix& s,
+                             const Vector& v);
+  
+    Matrix InvertScalingTranslationMatrix(const Matrix& t);
+
+    bool IsShearMatrix(const Matrix& shear);  
+
+    Matrix InvertShearMatrix(const Matrix& shear);
+  };
+}
--- a/Framework/Toolbox/MessagingToolbox.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/MessagingToolbox.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,69 +21,21 @@
 
 #include "MessagingToolbox.h"
 
-#include "../../Resources/Orthanc/Core/Images/Image.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/Images/JpegReader.h"
-#include "../../Resources/Orthanc/Core/Images/PngReader.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Toolbox.h"
-#include "../../Resources/Orthanc/Core/Logging.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>
 
-#if defined(__native_client__)
-#  include <boost/math/special_functions/round.hpp>
-#else
-#  include <boost/date_time/posix_time/posix_time.hpp>
-#  include <boost/date_time/microsec_time_clock.hpp>
-#endif
-
 namespace OrthancStone
 {
   namespace MessagingToolbox
   {
-#if defined(__native_client__)
-    static pp::Core* core_ = NULL;
-
-    void Timestamp::Initialize(pp::Core* core)
-    {
-      if (core == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      core_ = core;
-    }
-
-    Timestamp::Timestamp()
-    {
-      if (core_ == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      time_ = core_->GetTimeTicks();
-    }
-
-    int Timestamp::GetMillisecondsSince(const Timestamp& other)
-    {
-      double difference = time_ - other.time_;
-      return static_cast<int>(boost::math::iround(difference * 1000.0));
-    }
-#else
-    Timestamp::Timestamp()
-    {
-      time_ = boost::posix_time::microsec_clock::local_time();
-    }
-
-    int Timestamp::GetMillisecondsSince(const Timestamp& other)
-    {
-      boost::posix_time::time_duration difference = time_ - other.time_;
-      return static_cast<int>(difference.total_milliseconds());
-    }
-#endif
-
     static bool ParseVersion(std::string& version,
                              unsigned int& major,
                              unsigned int& minor,
@@ -152,8 +104,19 @@
     }
 
 
-    static void ParseJson(Json::Value& target,
-                          const std::string& source)
+    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);
+    }
+
+
+    static void ParseJsonException(Json::Value& target,
+                                   const std::string& source)
     {
       Json::Reader reader;
       if (!reader.parse(source, target))
@@ -169,7 +132,7 @@
     {
       std::string tmp;
       orthanc.RestApiGet(tmp, uri);
-      ParseJson(target, tmp);
+      ParseJsonException(target, tmp);
     }
 
 
@@ -180,7 +143,7 @@
     {
       std::string tmp;
       orthanc.RestApiPost(tmp, uri, body);
-      ParseJson(target, tmp);
+      ParseJsonException(target, tmp);
     }
 
 
@@ -220,11 +183,12 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
 
-      LOG(WARNING) << "Version of the Orthanc core (must be above 1.1.0): " << version;
+      LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version;
 
-      // Stone is only compatible with Orthanc >= 1.1.0, otherwise deadlocks might occur
+      // Stone is only compatible with Orthanc >= 1.3.1
       if (major < 1 ||
-          (major == 1 && minor < 1))
+          (major == 1 && minor < 3) ||
+          (major == 1 && minor == 3 && patch < 1))
       {
         return false;
       }
@@ -422,7 +386,7 @@
       float offset = static_cast<float>(stretchLow) / scaling;
       
       Orthanc::ImageProcessing::Convert(*image, *reader);
-      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling);
+      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
 
 #if 0
       /*info.removeMember("PixelData");
@@ -435,5 +399,51 @@
 
       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	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/MessagingToolbox.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,42 +21,22 @@
 
 #pragma once
 
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
+#include "../StoneEnumerations.h"
 
-#include "../Enumerations.h"
-#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
+#include <Plugins/Samples/Common/IOrthancConnection.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
+#include <Core/Images/ImageAccessor.h>
+#include <Core/DicomFormat/DicomMap.h>
 
 #include <json/value.h>
 
-#if defined(__native_client__)
-#  include <ppapi/cpp/core.h>
-#else
-#  include <boost/date_time/posix_time/ptime.hpp>
-#endif
-
 namespace OrthancStone
 {
   namespace MessagingToolbox
   {
-    class Timestamp
-    {
-    private:
-#if defined(__native_client__)
-      PP_TimeTicks   time_;
-#else
-      boost::posix_time::ptime   time_;
-#endif
-
-    public:
-      Timestamp();
-
-#if defined(__native_client__)
-      static void Initialize(pp::Core* core);
-#endif
-
-      int GetMillisecondsSince(const Timestamp& other);
-    };
-
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size);
 
     void RestApiGet(Json::Value& target,
                     OrthancPlugins::IOrthancConnection& orthanc,
@@ -84,5 +64,8 @@
                                             unsigned int frame,
                                             unsigned int quality,
                                             Orthanc::PixelFormat targetFormat);
+
+    void ConvertDataset(Orthanc::DicomMap& target,
+                        const OrthancPlugins::IDicomDataset& source);
   }
 }
--- a/Framework/Toolbox/ObserversRegistry.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ObserversRegistry.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,105 +21,85 @@
 
 #pragma once
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/OrthancException.h>
 
 #include <boost/noncopyable.hpp>
-#include <boost/thread/mutex.hpp>
 #include <set>
 
 namespace OrthancStone
 {
   template <
     typename Source,
-    typename Observer = typename Source::IChangeObserver
+    typename Observer = typename Source::IObserver
     >
   class ObserversRegistry : public boost::noncopyable
   {
   private:
-    struct ChangeFunctor : public boost::noncopyable
-    {
-      void operator() (Observer& observer,
-                       const Source& source)
-      {
-        observer.NotifyChange(source);
-      }
-    };
-
     typedef std::set<Observer*>  Observers;
 
-    boost::mutex  mutex_;
-    Observers     observers_;
-    bool          empty_;
+    Observers  observers_;
 
   public:
-    ObserversRegistry() : empty_(true)
-    {
-    }
-
     template <typename Functor>
-    void Notify(const Source* source,
+    void Notify(const Source& source,
                 Functor& functor)
     {
-      if (empty_)
-      {
-        // Optimization to avoid the unnecessary locking of the mutex
-        return;
-      }
-
-      if (source == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      boost::mutex::scoped_lock lock(mutex_);
-
       for (typename Observers::const_iterator observer = observers_.begin();
            observer != observers_.end(); ++observer)
       {
-        functor(**observer, *source);
+        functor(**observer, source);
       }
     }
 
-    template <typename Functor>
-    void Notify(const Source* source)
-    {
-      // Use the default functor
-      Functor functor;
-      Notify(source, functor);
-    }
-
-    void NotifyChange(const Source* source)
-    {
-      Notify<ChangeFunctor>(source);
-    }
-
     void Register(Observer& observer)
     {
-      empty_ = false;
-
-      boost::mutex::scoped_lock lock(mutex_);
       observers_.insert(&observer);
     }
 
     void Unregister(Observer& observer)
     {
-      boost::mutex::scoped_lock lock(mutex_);
       observers_.erase(&observer);
-
-      if (observers_.empty())
-      {
-        empty_ = true;
-      }
     }
 
     bool IsEmpty()
     {
-#if 0
-      boost::mutex::scoped_lock lock(mutex_);
       return observers_.empty();
-#else
-      return empty_;
-#endif
+    }
+
+    void Apply(const Source& source,
+               void (Observer::*method) (const Source&))
+    {
+      for (typename Observers::const_iterator it = observers_.begin();
+           it != observers_.end(); ++it)
+      {
+        ((*it)->*method) (source);
+      }
+    }
+
+    template <typename Argument0>
+    void Apply(const Source& source,
+               void (Observer::*method) (const Source&, const Argument0&),
+               const Argument0& argument0)
+    {
+      for (typename Observers::const_iterator it = observers_.begin();
+           it != observers_.end(); ++it)
+      {
+        ((*it)->*method) (source, argument0);
+      }
+    }
+
+    template <typename Argument0,
+              typename Argument1>
+    void Apply(const Source& source,
+               void (Observer::*method) (const Source&, const Argument0&, const Argument1&),
+               const Argument0& argument0,
+               const Argument1& argument1)
+    {
+      for (typename Observers::const_iterator it = observers_.begin();
+           it != observers_.end(); ++it)
+      {
+        ((*it)->*method) (source, argument0, argument1);
+      }
     }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrientedBoundingBox.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,268 @@
+/**
+ * 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 "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.GetAxialGeometry();
+    Vector dim = image.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;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrientedBoundingBox.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,73 @@
+/**
+ * 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 "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/OrthancSeriesLoader.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,439 +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 "OrthancSeriesLoader.h"
-
-#include "../Toolbox/MessagingToolbox.h"
-#include "../../Resources/Orthanc/Core/Images/Image.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
-#include "DicomFrameConverter.h"
-
-namespace OrthancStone
-{
-  class OrthancSeriesLoader::Slice : public boost::noncopyable
-  {
-  private:
-    std::string     instanceId_;
-    SliceGeometry   geometry_;
-    double          projectionAlongNormal_;
-
-  public:
-    Slice(const std::string& instanceId,
-          const std::string& imagePositionPatient,
-          const std::string& imageOrientationPatient) :
-      instanceId_(instanceId),
-      geometry_(imagePositionPatient, imageOrientationPatient)
-    {
-    }
-
-    const std::string GetInstanceId() const
-    {
-      return instanceId_;
-    }
-
-    const SliceGeometry& GetGeometry() const
-    {
-      return geometry_;
-    }
-
-    void SetNormal(const Vector& normal)
-    {
-      projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal);
-    }
-
-    double GetProjectionAlongNormal() const
-    {
-      return projectionAlongNormal_;
-    }
-  };
-
-
-  class OrthancSeriesLoader::SetOfSlices : public boost::noncopyable
-  {
-  private:
-    std::vector<Slice*>  slices_;
-
-    struct Comparator
-    {
-      bool operator() (const Slice* const a,
-                       const Slice* const b) const
-      {
-        return a->GetProjectionAlongNormal() < b->GetProjectionAlongNormal();
-      }
-    };
-
-  public:
-    ~SetOfSlices()
-    {
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        assert(slices_[i] != NULL);
-        delete slices_[i];
-      }
-    }
-
-    void Reserve(size_t size)
-    {
-      slices_.reserve(size);
-    }
-
-    void AddSlice(const std::string& instanceId,
-                  const std::string& imagePositionPatient,
-                  const std::string& imageOrientationPatient)
-    {
-      slices_.push_back(new Slice(instanceId, imagePositionPatient, imageOrientationPatient));
-    }
-
-    size_t GetSliceCount() const
-    {
-      return slices_.size();
-    }
-
-    const Slice& GetSlice(size_t index) const
-    {
-      assert(slices_[index] != NULL);
-      return *slices_[index];
-    }
-      
-    void Sort(const Vector& normal)
-    {
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        slices_[i]->SetNormal(normal);
-      }
-
-      Comparator comparator;
-      std::sort(slices_.begin(), slices_.end(), comparator);
-    }
-
-    void LoadSeriesFast(OrthancPlugins::IOrthancConnection&  orthanc,
-                        const std::string& series)
-    {
-      // Retrieve the orientation of this series
-      Json::Value info;
-      MessagingToolbox::RestApiGet(info, orthanc, "/series/" + series);
-
-      if (info.type() != Json::objectValue ||
-          !info.isMember("MainDicomTags") ||
-          info["MainDicomTags"].type() != Json::objectValue ||
-          !info["MainDicomTags"].isMember("ImageOrientationPatient") ||
-          info["MainDicomTags"]["ImageOrientationPatient"].type() != Json::stringValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      std::string imageOrientationPatient = info["MainDicomTags"]["ImageOrientationPatient"].asString();
-
-
-      // Retrieve the Orthanc ID of all the instances of this series
-      Json::Value instances;
-      MessagingToolbox::RestApiGet(instances, orthanc, "/series/" + series + "/instances");
-
-      if (instances.type() != Json::arrayValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      if (instances.size() == 0)
-      {
-        LOG(ERROR) << "This series is empty";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-      }
-
-
-      // Retrieve the DICOM tags of all the instances
-      std::vector<std::string> instancesId;
-
-      instancesId.resize(instances.size());
-      Reserve(instances.size());
-
-      for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
-      {
-        if (instances[i].type() != Json::objectValue ||
-            !instances[i].isMember("ID") ||
-            !instances[i].isMember("MainDicomTags") ||
-            instances[i]["ID"].type() != Json::stringValue ||
-            instances[i]["MainDicomTags"].type() != Json::objectValue ||
-            !instances[i]["MainDicomTags"].isMember("ImagePositionPatient") ||
-            instances[i]["MainDicomTags"]["ImagePositionPatient"].type() != Json::stringValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          instancesId[i] = instances[i]["ID"].asString();
-          AddSlice(instancesId[i], 
-                   instances[i]["MainDicomTags"]["ImagePositionPatient"].asString(),
-                   imageOrientationPatient);
-        }
-      }
-
-      assert(GetSliceCount() == instances.size());
-    }
-
-      
-    void LoadSeriesSafe(OrthancPlugins::IOrthancConnection& orthanc,
-                        const std::string& seriesId)
-    {
-      Json::Value series;
-      MessagingToolbox::RestApiGet(series, orthanc, "/series/" + seriesId + "/instances-tags?simplify");
-
-      if (series.type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      if (series.size() == 0)
-      {
-        LOG(ERROR) << "This series is empty";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
-      }
-
-      Json::Value::Members instances = series.getMemberNames();
-
-      Reserve(instances.size());
-
-      for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
-      {
-        const Json::Value& tags = series[instances[i]];
-
-        if (tags.type() != Json::objectValue ||
-            !tags.isMember("ImagePositionPatient") ||
-            !tags.isMember("ImageOrientationPatient") ||
-            tags["ImagePositionPatient"].type() != Json::stringValue ||
-            tags["ImageOrientationPatient"].type() != Json::stringValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          AddSlice(instances[i], 
-                   tags["ImagePositionPatient"].asString(),
-                   tags["ImageOrientationPatient"].asString());
-        }
-      }
-
-      assert(GetSliceCount() == instances.size());
-    }
-
-      
-    void SelectNormal(Vector& normal) const
-    {
-      std::vector<Vector>  normalCandidates;
-      std::vector<unsigned int>  normalCount;
-
-      bool found = false;
-
-      for (size_t i = 0; !found && i < GetSliceCount(); i++)
-      {
-        const Vector& normal = GetSlice(i).GetGeometry().GetNormal();
-
-        bool add = true;
-        for (size_t j = 0; add && j < normalCandidates.size(); j++)  // (*)
-        {
-          if (GeometryToolbox::IsParallel(normal, normalCandidates[j]))
-          {
-            normalCount[j] += 1;
-            add = false;
-          }
-        }
-
-        if (add)
-        {
-          if (normalCount.size() > 2)
-          {
-            // To get linear-time complexity in (*). This heuristics
-            // allows the series to have one single frame that is
-            // not parallel to the others (such a frame could be a
-            // generated preview)
-            found = false;
-          }
-          else
-          {
-            normalCandidates.push_back(normal);
-            normalCount.push_back(1);
-          }
-        }
-      }
-
-      for (size_t i = 0; !found && i < normalCandidates.size(); i++)
-      {
-        unsigned int count = normalCount[i];
-        if (count == GetSliceCount() ||
-            count + 1 == GetSliceCount())
-        {
-          normal = normalCandidates[i];
-          found = true;
-        }
-      }
-
-      if (!found)
-      {
-        LOG(ERROR) << "Cannot select a normal that is shared by most of the slices of this series";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
-      }
-    }
-
-
-    void FilterNormal(const Vector& normal)
-    {
-      size_t pos = 0;
-
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal()))
-        {
-          // This slice is compatible with the selected normal
-          slices_[pos] = slices_[i];
-          pos += 1;
-        }
-        else
-        {
-          delete slices_[i];
-          slices_[i] = NULL;
-        }
-      }
-
-      slices_.resize(pos);
-    }
-  };
-
-
-
-  OrthancSeriesLoader::OrthancSeriesLoader(OrthancPlugins::IOrthancConnection& orthanc,
-                                           const std::string& series) :
-    orthanc_(orthanc),
-    slices_(new SetOfSlices)
-  {
-    /**
-     * The function "LoadSeriesFast()" might now behave properly if
-     * some slice has some outsider value for its normal, which
-     * happens sometimes on reprojected series (e.g. coronal and
-     * sagittal of Delphine). Don't use it.
-     **/
-
-    slices_->LoadSeriesSafe(orthanc, series);
-    
-    Vector normal;
-    slices_->SelectNormal(normal);
-    slices_->FilterNormal(normal);
-    slices_->Sort(normal);
-
-    if (slices_->GetSliceCount() == 0)  // Sanity check
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    for (size_t i = 0; i < slices_->GetSliceCount(); i++)
-    {
-      assert(GeometryToolbox::IsParallel(normal, slices_->GetSlice(i).GetGeometry().GetNormal()));
-      geometry_.AddSlice(slices_->GetSlice(i).GetGeometry());
-    }
-
-    std::string uri = "/instances/" + slices_->GetSlice(0).GetInstanceId() + "/tags";
-
-    OrthancPlugins::FullOrthancDataset dataset(orthanc_, uri);
-    OrthancPlugins::DicomDatasetReader reader(dataset);
-
-    if (!reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) ||
-        !reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
-    }
-
-    DicomFrameConverter converter;
-    converter.ReadParameters(dataset);
-    format_ = converter.GetExpectedPixelFormat();
-  }
-    
-
-  OrthancPlugins::IDicomDataset* OrthancSeriesLoader::DownloadDicom(size_t index)
-  {
-    std::string uri = "/instances/" + slices_->GetSlice(index).GetInstanceId() + "/tags";
-
-    std::auto_ptr<OrthancPlugins::IDicomDataset> dataset(new OrthancPlugins::FullOrthancDataset(orthanc_, uri));
-    OrthancPlugins::DicomDatasetReader reader(*dataset);
-
-    unsigned int frames;
-    if (reader.GetUnsignedIntegerValue(frames, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES) &&
-        frames != 1)
-    {
-      LOG(ERROR) << "One instance in this series has more than 1 frame";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
-    }
-
-    return dataset.release();
-  }
-
-
-  void OrthancSeriesLoader::CheckFrame(const Orthanc::ImageAccessor& frame) const
-  {
-    if (frame.GetFormat() != format_ ||
-        frame.GetWidth() != width_ ||
-        frame.GetHeight() != height_)
-    {
-      LOG(ERROR) << "The parameters of this series vary accross its slices";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
-    }
-  }
-
-
-  Orthanc::ImageAccessor* OrthancSeriesLoader::DownloadFrame(size_t index)
-  {
-    const Slice& slice = slices_->GetSlice(index);
-
-    std::auto_ptr<Orthanc::ImageAccessor> frame
-      (MessagingToolbox::DecodeFrame(orthanc_, slice.GetInstanceId(), 0, format_));
-
-    if (frame.get() != NULL)
-    {
-      CheckFrame(*frame);
-    }
-
-    return frame.release();
-  }
-
-
-  Orthanc::ImageAccessor* OrthancSeriesLoader::DownloadJpegFrame(size_t index,
-                                                                 unsigned int quality)
-  {
-    const Slice& slice = slices_->GetSlice(index);
-
-    std::auto_ptr<Orthanc::ImageAccessor> frame
-      (MessagingToolbox::DecodeJpegFrame(orthanc_, slice.GetInstanceId(), 0, quality, format_));
-
-    if (frame.get() != NULL)
-    {
-      CheckFrame(*frame);
-    }
-
-    return frame.release();
-  }
-
-
-  bool OrthancSeriesLoader::IsJpegAvailable()
-  {
-    return MessagingToolbox::HasWebViewerInstalled(orthanc_);
-  }
-}
--- a/Framework/Toolbox/OrthancSeriesLoader.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +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 "ISeriesLoader.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
-
-#include <boost/shared_ptr.hpp>
-
-namespace OrthancStone
-{
-  // This class is NOT thread-safe
-  // It sorts the slices from a given series, give access to their
-  // geometry and individual frames, making the assumption that there
-  // is a single frame in each instance of the series
-  class OrthancSeriesLoader : public ISeriesLoader
-  {
-  private:
-    class Slice;
-    class SetOfSlices;
-
-    OrthancPlugins::IOrthancConnection&  orthanc_;
-    boost::shared_ptr<SetOfSlices>       slices_;
-    ParallelSlices                       geometry_;
-    Orthanc::PixelFormat                 format_;
-    unsigned int                         width_;
-    unsigned int                         height_;
-
-    void CheckFrame(const Orthanc::ImageAccessor& frame) const;
-
-  public:
-    OrthancSeriesLoader(OrthancPlugins::IOrthancConnection& orthanc,
-                        const std::string& series);
-    
-    virtual Orthanc::PixelFormat GetPixelFormat()
-    {
-      return format_;
-    }
-
-    virtual ParallelSlices& GetGeometry()
-    {
-      return geometry_;
-    }
-
-    virtual unsigned int GetWidth()
-    {
-      return width_;
-    }
-
-    virtual unsigned int GetHeight()
-    {
-      return height_;
-    }
-
-    virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index);
-
-    virtual Orthanc::ImageAccessor* DownloadFrame(size_t index);
-
-    virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index,
-                                                      unsigned int quality);
-
-    virtual bool IsJpegAvailable();
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,910 @@
+/**
+ * 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 "OrthancSlicesLoader.h"
+
+#include "MessagingToolbox.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/Compression/GzipCompressor.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 OrthancStone
+{
+  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* DownloadSeriesGeometry()
+    {
+      return new Operation(Mode_SeriesGeometry);
+    }
+
+    static Operation* DownloadInstanceGeometry(const std::string& instanceId)
+    {
+      std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
+      operation->instanceId_ = instanceId;
+      return operation.release();
+    }
+
+    static Operation* DownloadFrameGeometry(const std::string& instanceId,
+                                            unsigned int frame)
+    {
+      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_Full;
+      return tmp.release();
+    }
+  };
+    
+
+  class OrthancSlicesLoader::WebCallback : public IWebService::ICallback
+  {
+  private:
+    OrthancSlicesLoader&  that_;
+
+  public:
+    WebCallback(OrthancSlicesLoader&  that) :
+      that_(that)
+    {
+    }
+
+    virtual void NotifySuccess(const std::string& uri,
+                               const void* answer,
+                               size_t answerSize,
+                               Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
+
+      switch (operation->GetMode())
+      {
+        case Mode_SeriesGeometry:
+          that_.ParseSeriesGeometry(answer, answerSize);
+          break;
+
+        case Mode_InstanceGeometry:
+          that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
+          break;
+
+        case Mode_FrameGeometry:
+          that_.ParseFrameGeometry(operation->GetInstanceId(),
+                                   operation->GetFrame(), answer, answerSize);
+          break;
+
+        case Mode_LoadImage:
+          switch (operation->GetQuality())
+          {
+            case SliceImageQuality_Full:
+              that_.ParseSliceImagePng(*operation, answer, answerSize);
+              break;
+
+            case SliceImageQuality_Jpeg50:
+            case SliceImageQuality_Jpeg90:
+            case SliceImageQuality_Jpeg95:
+              that_.ParseSliceImageJpeg(*operation, answer, answerSize);
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+                
+          break;
+
+        case Mode_LoadRawImage:
+          that_.ParseSliceRawImage(*operation, answer, answerSize);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    virtual void NotifyError(const std::string& uri,
+                             Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
+      LOG(ERROR) << "Cannot download " << uri;
+
+      switch (operation->GetMode())
+      {
+        case Mode_FrameGeometry:
+        case Mode_SeriesGeometry:
+          that_.userCallback_.NotifyGeometryError(that_);
+          that_.state_ = State_Error;
+          break;
+
+        case Mode_LoadImage:
+          that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
+                                                    operation->GetSlice(),
+                                                    operation->GetQuality());
+          break;
+          
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }     
+  };
+
+
+  
+  void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
+                                                    std::auto_ptr<Orthanc::ImageAccessor>& image) const
+  {
+    if (image.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      userCallback_.NotifySliceImageReady
+        (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+    }
+  }
+
+  
+  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
+  {
+    userCallback_.NotifySliceImageError
+      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+  }
+
+
+  void OrthancSlicesLoader::SortAndFinalizeSlices()
+  {
+    bool ok = false;
+    
+    if (slices_.GetSliceCount() > 0)
+    {
+      Vector normal;
+      if (slices_.SelectNormal(normal))
+      {
+        slices_.FilterNormal(normal);
+        slices_.SetNormal(normal);
+        slices_.Sort();
+        ok = true;
+      }
+    }
+
+    state_ = State_GeometryReady;
+
+    if (ok)
+    {
+      LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
+      userCallback_.NotifyGeometryReady(*this);
+    }
+    else
+    {
+      LOG(ERROR) << "This series is empty";
+      userCallback_.NotifyGeometryError(*this);
+    }
+  }
+
+  
+  void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
+                                                size_t size)
+  {
+    Json::Value series;
+    if (!MessagingToolbox::ParseJson(series, answer, size) ||
+        series.type() != Json::objectValue)
+    {
+      userCallback_.NotifyGeometryError(*this);
+      return;
+    }
+
+    Json::Value::Members instances = series.getMemberNames();
+
+    slices_.Reserve(instances.size());
+
+    for (size_t i = 0; i < instances.size(); i++)
+    {
+      OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
+
+      Orthanc::DicomMap dicom;
+      MessagingToolbox::ConvertDataset(dicom, dataset);
+      
+      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))
+        {
+          slices_.AddSlice(slice.release());
+        }
+        else
+        {
+          LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i];
+        }
+      }
+    }
+
+    SortAndFinalizeSlices();
+  }
+
+
+  void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
+                                                  const void* answer,
+                                                  size_t size)
+  {
+    Json::Value tags;
+    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
+        tags.type() != Json::objectValue)
+    {
+      userCallback_.NotifyGeometryError(*this);
+      return;
+    }
+
+    OrthancPlugins::FullOrthancDataset dataset(tags);
+
+    Orthanc::DicomMap dicom;
+    MessagingToolbox::ConvertDataset(dicom, dataset);
+      
+    unsigned int frames;
+    if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
+    {
+      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))
+      {
+        slices_.AddSlice(slice.release());
+      }
+      else
+      {
+        LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
+        userCallback_.NotifyGeometryError(*this);
+        return;
+      }
+    }
+
+    SortAndFinalizeSlices();
+  }
+
+
+  void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId,
+                                               unsigned int frame,
+                                               const void* answer,
+                                               size_t size)
+  {
+    Json::Value tags;
+    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
+        tags.type() != Json::objectValue)
+    {
+      userCallback_.NotifyGeometryError(*this);
+      return;
+    }
+
+    OrthancPlugins::FullOrthancDataset dataset(tags);
+
+    state_ = State_GeometryReady;
+
+    Orthanc::DicomMap dicom;
+    MessagingToolbox::ConvertDataset(dicom, dataset);
+
+    std::auto_ptr<Slice> slice(new Slice);
+    if (slice->ParseOrthancFrame(dicom, instanceId, frame))
+    {
+      LOG(INFO) << "Loaded instance " << instanceId;
+      slices_.AddSlice(slice.release());
+      userCallback_.NotifyGeometryReady(*this);
+    }
+    else
+    {
+      LOG(WARNING) << "Skipping invalid instance " << instanceId;
+      userCallback_.NotifyGeometryError(*this);
+    }
+  }
+
+
+  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
+                                               const void* answer,
+                                               size_t size)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor>  image;
+
+    try
+    {
+      image.reset(new Orthanc::PngReader);
+      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size);
+    }
+    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 Operation& operation,
+                                                const void* answer,
+                                                size_t size)
+  {
+    Json::Value encoded;
+    if (!MessagingToolbox::ParseJson(encoded, answer, size) ||
+        encoded.type() != Json::objectValue ||
+        !encoded.isMember("Orthanc") ||
+        encoded["Orthanc"].type() != Json::objectValue)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    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));
+
+    float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    float offset = static_cast<float>(stretchLow) / scaling;
+      
+    Orthanc::ImageProcessing::Convert(*image, *reader);
+    reader.reset(NULL);
+    
+    Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+
+    NotifySliceImageSuccess(operation, image);
+  }
+
+
+  class StringImage :
+    public Orthanc::ImageAccessor,
+    public boost::noncopyable
+  {
+  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 Operation& operation,
+                                               const void* answer,
+                                               size_t size)
+  {
+    Orthanc::GzipCompressor compressor;
+
+    std::string raw;
+    compressor.Uncompress(raw, answer, size);
+    
+    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(ICallback& callback,
+                                           IWebService& orthanc) :
+    webCallback_(new WebCallback(*this)),
+    userCallback_(callback),
+    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;
+      std::string uri = "/series/" + seriesId + "/instances-tags";
+      orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry());
+    }
+  }
+
+
+  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
+      std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c";
+      orthanc_.ScheduleGetRequest
+        (*webCallback_, uri, 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;
+      std::string uri = "/instances/" + instanceId + "/tags";
+      orthanc_.ScheduleGetRequest
+        (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame));
+    }
+  }
+  
+
+  bool OrthancSlicesLoader::IsGeometryReady() const
+  {
+    return state_ == State_GeometryReady;
+  }
+
+
+  size_t OrthancSlicesLoader::GetSliceCount() const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return slices_.GetSliceCount();
+  }
+
+  
+  const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return slices_.GetSlice(index);
+  }
+  
+
+  bool OrthancSlicesLoader::LookupSlice(size_t& index,
+                                        const CoordinateSystem3D& plane) const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return slices_.LookupSlice(index, plane);
+  }
+  
+
+  void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
+                                                  size_t index)
+  {
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+                       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_.ScheduleGetRequest(*webCallback_, uri,
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
+  }
+
+
+  void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
+                                                   size_t index,
+                                                   SliceImageQuality quality)
+  {
+    unsigned int value;
+
+    switch (quality)
+    {
+      case SliceImageQuality_Jpeg50:
+        value = 50;
+        break;
+    
+      case SliceImageQuality_Jpeg90:
+        value = 90;
+        break;
+    
+      case SliceImageQuality_Jpeg95:
+        value = 95;
+        break;
+
+      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_.ScheduleGetRequest(*webCallback_, uri,
+                                Operation::DownloadSliceImage(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())
+    {
+      if (quality == SliceImageQuality_Full)
+      {
+        ScheduleSliceImagePng(slice, index);
+      }
+      else
+      {
+        ScheduleSliceImageJpeg(slice, index, quality);
+      }
+    }
+    else
+    {
+      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+                         boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
+      orthanc_.ScheduleGetRequest(*webCallback_, uri,
+                                  Operation::DownloadSliceRawImage(index, slice));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,147 @@
+/**
+ * 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 "SlicesSorter.h"
+#include "../StoneEnumerations.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  class OrthancSlicesLoader : public boost::noncopyable
+  {
+  public:
+    class ICallback : public boost::noncopyable
+    {
+    public:
+      virtual ~ICallback()
+      {
+      }
+
+      virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0;
+
+      virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0;
+
+      virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                         unsigned int sliceIndex,
+                                         const Slice& slice,
+                                         std::auto_ptr<Orthanc::ImageAccessor>& image,
+                                         SliceImageQuality effectiveQuality) = 0;
+
+      virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                         unsigned int sliceIndex,
+                                         const Slice& slice,
+                                         SliceImageQuality quality) = 0;
+    };
+    
+  private:
+    enum State
+    {
+      State_Error,
+      State_Initialization,
+      State_LoadingGeometry,
+      State_GeometryReady
+    };
+    
+    enum Mode
+    {
+      Mode_SeriesGeometry,
+      Mode_InstanceGeometry,
+      Mode_FrameGeometry,
+      Mode_LoadImage,
+      Mode_LoadRawImage
+    };
+
+    class Operation;
+    class WebCallback;
+
+    boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
+
+    ICallback&    userCallback_;
+    IWebService&  orthanc_;
+    State         state_;
+    SlicesSorter  slices_;
+
+    void NotifySliceImageSuccess(const Operation& operation,
+                                 std::auto_ptr<Orthanc::ImageAccessor>& image) const;
+  
+    void NotifySliceImageError(const Operation& operation) const;
+    
+    void ParseSeriesGeometry(const void* answer,
+                             size_t size);
+
+    void ParseInstanceGeometry(const std::string& instanceId,
+                               const void* answer,
+                               size_t size);
+
+    void ParseFrameGeometry(const std::string& instanceId,
+                            unsigned int frame,
+                            const void* answer,
+                            size_t size);
+
+    void ParseSliceImagePng(const Operation& operation,
+                            const void* answer,
+                            size_t size);
+
+    void ParseSliceImageJpeg(const Operation& operation,
+                             const void* answer,
+                             size_t size);
+
+    void ParseSliceRawImage(const Operation& operation,
+                            const void* answer,
+                            size_t size);
+
+    void ScheduleSliceImagePng(const Slice& slice,
+                               size_t index);
+    
+    void ScheduleSliceImageJpeg(const Slice& slice,
+                                size_t index,
+                                SliceImageQuality quality);
+
+    void SortAndFinalizeSlices();
+    
+  public:
+    OrthancSlicesLoader(ICallback& callback,
+                        IWebService& orthanc);
+
+    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 GetSliceCount() const;
+
+    const Slice& GetSlice(size_t index) const;
+
+    bool LookupSlice(size_t& index,
+                     const CoordinateSystem3D& plane) const;
+
+    void ScheduleLoadSliceImage(size_t index,
+                                SliceImageQuality requestedQuality);
+  };
+}
--- a/Framework/Toolbox/ParallelSlices.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ParallelSlices.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,14 +21,16 @@
 
 #include "ParallelSlices.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "GeometryToolbox.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
   ParallelSlices::ParallelSlices()
   {
-    GeometryToolbox::AssignVector(normal_, 0, 0, 1);
+    LinearAlgebra::AssignVector(normal_, 0, 0, 1);
   }
 
 
@@ -41,7 +43,7 @@
     for (size_t i = 0; i < slices_.size(); i++)
     {
       assert(other.slices_[i] != NULL);
-      slices_[i] = new SliceGeometry(*other.slices_[i]);
+      slices_[i] = new CoordinateSystem3D(*other.slices_[i]);
     }
   }
 
@@ -59,16 +61,16 @@
   }
 
 
-  void ParallelSlices::AddSlice(const SliceGeometry& slice)
+  void ParallelSlices::AddSlice(const CoordinateSystem3D& slice)
   {
     if (slices_.empty())
     {
       normal_ = slice.GetNormal();
-      slices_.push_back(new SliceGeometry(slice));
+      slices_.push_back(new CoordinateSystem3D(slice));
     }
     else if (GeometryToolbox::IsParallel(slice.GetNormal(), normal_))
     {
-      slices_.push_back(new SliceGeometry(slice));
+      slices_.push_back(new CoordinateSystem3D(slice));
     }
     else
     {
@@ -82,12 +84,12 @@
                                 const Vector& axisX,
                                 const Vector& axisY)
   {
-    SliceGeometry slice(origin, axisX, axisY);
+    CoordinateSystem3D slice(origin, axisX, axisY);
     AddSlice(slice);
   }
 
 
-  const SliceGeometry& ParallelSlices::GetSlice(size_t index) const
+  const CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const
   {
     if (index >= slices_.size())
     {
@@ -135,7 +137,7 @@
 
     for (size_t i = slices_.size(); i > 0; i--)
     {
-      const SliceGeometry& slice = *slices_[i - 1];
+      const CoordinateSystem3D& slice = *slices_[i - 1];
 
       reversed->AddSlice(slice.GetOrigin(),
                          -slice.GetAxisX(),
--- a/Framework/Toolbox/ParallelSlices.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ParallelSlices.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,16 +21,15 @@
 
 #pragma once
 
-#include "SliceGeometry.h"
+#include "CoordinateSystem3D.h"
 
 namespace OrthancStone
 {
-  // This class is NOT thread-safe
-  class ParallelSlices
+  class ParallelSlices : public boost::noncopyable
   {
   private:
-    Vector                       normal_;
-    std::vector<SliceGeometry*>  slices_;
+    Vector                            normal_;
+    std::vector<CoordinateSystem3D*>  slices_;
     
     ParallelSlices& operator= (const ParallelSlices& other);  // Forbidden
 
@@ -46,7 +45,7 @@
       return normal_;
     }
 
-    void AddSlice(const SliceGeometry& slice);
+    void AddSlice(const CoordinateSystem3D& slice);
 
     void AddSlice(const Vector& origin,
                   const Vector& axisX,
@@ -57,7 +56,7 @@
       return slices_.size();
     }
 
-    const SliceGeometry& GetSlice(size_t index) const;
+    const CoordinateSystem3D& GetSlice(size_t index) const;
 
     bool ComputeClosestSlice(size_t& closestSlice,
                              double& closestDistance,
--- a/Framework/Toolbox/ParallelSlicesCursor.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ParallelSlicesCursor.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,7 @@
 
 #include "ParallelSlicesCursor.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
@@ -40,8 +40,6 @@
 
   size_t ParallelSlicesCursor::GetSliceCount()
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     if (slices_.get() == NULL)
     {
       return 0;
@@ -53,13 +51,11 @@
   }
 
 
-  SliceGeometry ParallelSlicesCursor::GetSlice(size_t slice)
+  CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     if (slices_.get() == NULL)
     {
-      return SliceGeometry();
+      return CoordinateSystem3D();
     }
     else
     {
@@ -70,18 +66,14 @@
 
   void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     slices_.reset(new ParallelSlices(slices));
 
     currentSlice_ = GetDefaultSlice();
   }
 
 
-  SliceGeometry ParallelSlicesCursor::GetCurrentSlice()
+  CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice()
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     if (slices_.get() != NULL &&
         currentSlice_ < slices_->GetSliceCount())
     {
@@ -89,15 +81,13 @@
     }
     else
     {
-      return SliceGeometry();  // No slice is available, return the canonical geometry
+      return CoordinateSystem3D();  // No slice is available, return the canonical geometry
     }
   }
 
 
   bool ParallelSlicesCursor::SetDefaultSlice()
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     size_t slice = GetDefaultSlice();
 
     if (currentSlice_ != slice)
@@ -115,8 +105,6 @@
   bool ParallelSlicesCursor::ApplyOffset(SliceOffsetMode mode,
                                          int offset)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     if (slices_.get() == NULL)
     {
       return false;
@@ -215,8 +203,6 @@
 
   bool ParallelSlicesCursor::LookupSliceContainingPoint(const Vector& p)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     size_t slice;
     double distance;
 
--- a/Framework/Toolbox/ParallelSlicesCursor.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ParallelSlicesCursor.h	Tue Mar 20 20:03:02 2018 +0100
@@ -22,17 +22,13 @@
 #pragma once
 
 #include "ParallelSlices.h"
-#include "../Enumerations.h"
-
-#include <boost/thread/mutex.hpp>
+#include "../StoneEnumerations.h"
 
 namespace OrthancStone
 {
-  // This class is thread-safe
   class ParallelSlicesCursor : public boost::noncopyable
   {
   private:
-    boost::mutex                   mutex_;
     std::auto_ptr<ParallelSlices>  slices_;
     size_t                         currentSlice_;
 
@@ -48,9 +44,9 @@
 
     size_t GetSliceCount();
 
-    SliceGeometry GetSlice(size_t slice);
+    CoordinateSystem3D GetSlice(size_t slice);
 
-    SliceGeometry GetCurrentSlice();
+    CoordinateSystem3D GetCurrentSlice();
 
     // Returns "true" iff. the slice has actually changed
     bool SetDefaultSlice();
--- a/Framework/Toolbox/SharedValue.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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 <boost/noncopyable.hpp>
-#include <boost/thread/mutex.hpp>
-
-namespace OrthancStone
-{
-  // A value that is protected by a mutex, in order to be shared by
-  // multiple threads
-  template <typename T>
-  class SharedValue : public boost::noncopyable
-  {
-  private:
-    boost::mutex   mutex_;
-    T              value_;
-    
-  public:
-    class Locker : public boost::noncopyable
-    {
-    private:
-      boost::mutex::scoped_lock  lock_;
-      T&                         value_;
-
-    public:
-      Locker(SharedValue& shared) :
-        lock_(shared.mutex_),
-        value_(shared.value_)
-      {
-      }
-
-      T& GetValue() const
-      {
-        return value_;
-      }
-    };
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,647 @@
+/**
+ * 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 "ShearWarpProjectiveTransform.h"
+
+#include "ImageGeometry.h"
+#include "Extent2D.h"
+#include "FiniteProjectiveCamera.h"
+#include "GeometryToolbox.h"
+
+#include <Core/Images/PixelTraits.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+#include <Core/Logging.h>
+
+#include <boost/numeric/ublas/matrix_proxy.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <cassert>
+
+
+namespace OrthancStone
+{
+  static bool IsValidShear(const Matrix& M_shear)
+  {
+    return (LinearAlgebra::IsCloseToZero(M_shear(0, 1)) &&
+            LinearAlgebra::IsCloseToZero(M_shear(1, 0)) &&
+            LinearAlgebra::IsCloseToZero(M_shear(2, 0)) &&
+            LinearAlgebra::IsCloseToZero(M_shear(2, 1)) &&
+            LinearAlgebra::IsNear(1.0,   M_shear(2, 2)) &&
+            LinearAlgebra::IsCloseToZero(M_shear(2, 3)) &&
+            LinearAlgebra::IsCloseToZero(M_shear(3, 0)) &&
+            LinearAlgebra::IsCloseToZero(M_shear(3, 1)) &&
+            LinearAlgebra::IsNear(1.0,   M_shear(3, 3)));
+  }
+
+
+  static void ComputeShearParameters(double& scaling,
+                                     double& offsetX,
+                                     double& offsetY,
+                                     const Matrix& shear,
+                                     double z)
+  {
+    // Check out: ../../Resources/Computations/ComputeShearParameters.py
+    
+    if (!LinearAlgebra::IsShearMatrix(shear))
+    {
+      LOG(ERROR) << "Not a valid shear matrix";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    
+    scaling = 1.0 / (shear(3,2) * z + 1.0);
+    offsetX = shear(0,2) * z * scaling;
+    offsetY = shear(1,2) * z * scaling;
+  }  
+
+
+  ShearWarpProjectiveTransform::
+  ShearWarpProjectiveTransform(const Matrix& M_view,
+                               //const Matrix& P,           // Permutation applied to the volume
+                               unsigned int volumeWidth,
+                               unsigned int volumeHeight,
+                               unsigned int volumeDepth,
+                               double pixelSpacingX,
+                               double pixelSpacingY,
+                               unsigned int imageWidth,
+                               unsigned int imageHeight)
+  {
+    eye_o.resize(4);
+
+    {
+      // Find back the camera center given the "M_view" matrix
+      const double m11 = M_view(0, 0);
+      const double m12 = M_view(0, 1);
+      const double m13 = M_view(0, 2);
+      const double m14 = M_view(0, 3);
+      const double m21 = M_view(1, 0);
+      const double m22 = M_view(1, 1);
+      const double m23 = M_view(1, 2);
+      const double m24 = M_view(1, 3);
+      const double m41 = M_view(3, 0);
+      const double m42 = M_view(3, 1);
+      const double m43 = M_view(3, 2);
+      const double m44 = M_view(3, 3);
+
+      // Equations (A.8) to (A.11) on page 203. Also check out
+      // "Finding the camera center" in "Multiple View Geometry in
+      // Computer Vision - 2nd edition", page 163.
+      const double vx[9] = { m12, m13, m14, m22, m23, m24, m42, m43, m44 };
+      const double vy[9] = { m11, m13, m14, m21, m23, m24, m41, m43, m44 };
+      const double vz[9] = { m11, m12, m14, m21, m22, m24, m41, m42, m44 };
+      const double vw[9] = { m11, m12, m13, m21, m22, m23, m41, m42, m43 };
+
+      Matrix m;
+
+      LinearAlgebra::FillMatrix(m, 3, 3, vx);
+      eye_o[0] = -LinearAlgebra::ComputeDeterminant(m);
+
+      LinearAlgebra::FillMatrix(m, 3, 3, vy);
+      eye_o[1] = LinearAlgebra::ComputeDeterminant(m);
+
+      LinearAlgebra::FillMatrix(m, 3, 3, vz);
+      eye_o[2] = -LinearAlgebra::ComputeDeterminant(m);
+
+      LinearAlgebra::FillMatrix(m, 3, 3, vw);
+      eye_o[3] = LinearAlgebra::ComputeDeterminant(m);
+
+      if (LinearAlgebra::IsCloseToZero(eye_o[3]))
+      {
+        LOG(ERROR) << "The shear-warp projective transform is not applicable to affine cameras";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+#if 0
+    // Assume "T_shift = I" (the eye does not lie on plane k = 0)
+    const Matrix T_shift = LinearAlgebra::IdentityMatrix(4);
+    
+    // Equation (A.13) on page 204, given that the inverse of a
+    // permutation matrix is its transpose (TODO CHECK). If no T_shift
+    // or permutation P is applied, M'_view == M_view
+    const Matrix MM_view = LinearAlgebra::Product(
+      M_view,
+      LinearAlgebra::Transpose(P),
+      LinearAlgebra::InvertScalingTranslationMatrix(T_shift));
+#else
+    // This is a shortcut, as we take "T_shift = I" and "P = I"
+    const Matrix MM_view = M_view;
+#endif
+
+    // Equation (A.14) on page 207
+    Matrix MM_shear = LinearAlgebra::IdentityMatrix(4);
+    MM_shear(0, 2) = -eye_o[0] / eye_o[2];
+    MM_shear(1, 2) = -eye_o[1] / eye_o[2];
+    MM_shear(3, 2) = -eye_o[3] / eye_o[2];
+
+
+    // Compute the extent of the intermediate image
+    Extent2D extent;
+    double maxScaling = 1;
+
+    {
+      // Compute the shearing factors of the two extreme planes of the
+      // volume (z=0 and z=volumeDepth)
+      double scaling, offsetX, offsetY;
+      ComputeShearParameters(scaling, offsetX, offsetY, MM_shear, 0);
+
+      if (scaling > 0)
+      {
+        extent.AddPoint(offsetX, offsetY);
+        extent.AddPoint(offsetX + static_cast<double>(volumeWidth) * scaling,
+                        offsetY + static_cast<double>(volumeHeight) * scaling);
+
+        if (scaling > maxScaling)
+        {
+          maxScaling = scaling;
+        }
+      }
+
+      ComputeShearParameters(scaling, offsetX, offsetY, MM_shear, volumeDepth);
+
+      if (scaling > 0)
+      {
+        extent.AddPoint(offsetX, offsetY);
+        extent.AddPoint(offsetX + static_cast<double>(volumeWidth) * scaling,
+                        offsetY + static_cast<double>(volumeHeight) * scaling);
+
+        if (scaling > maxScaling)
+        {
+          maxScaling = scaling;
+        }
+      }
+    }
+      
+    if (LinearAlgebra::IsCloseToZero(extent.GetWidth()) ||
+        LinearAlgebra::IsCloseToZero(extent.GetHeight()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    intermediateWidth_ = std::ceil(extent.GetWidth() / maxScaling);
+    intermediateHeight_ = std::ceil(extent.GetHeight() / maxScaling);
+
+    // This is the product "T * S" in Equation (A.16) on page 209
+    Matrix TS = LinearAlgebra::Product(
+      GeometryToolbox::CreateTranslationMatrix(static_cast<double>(intermediateWidth_) / 2.0,
+                                               static_cast<double>(intermediateHeight_) / 2.0, 0),
+      GeometryToolbox::CreateScalingMatrix(1.0 / maxScaling, 1.0 / maxScaling, 1),
+      GeometryToolbox::CreateTranslationMatrix(-extent.GetCenterX(), -extent.GetCenterY(), 0));
+    
+    // This is Equation (A.16) on page 209. WARNING: There is an
+    // error in Lacroute's thesis: "inv(MM_shear)" is used instead
+    // of "MM_shear".
+    M_shear = LinearAlgebra::Product(TS, MM_shear);
+
+    if (!IsValidShear(M_shear))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    // This is Equation (A.17) on page 209
+    Matrix tmp;
+    LinearAlgebra::InvertMatrix(tmp, M_shear);
+    M_warp = LinearAlgebra::Product(MM_view, tmp);
+
+    // Intrinsic parameters of the camera
+    k_ = LinearAlgebra::ZeroMatrix(3, 4);
+    k_(0, 0) = 1.0 / pixelSpacingX;
+    k_(0, 3) = static_cast<double>(imageWidth) / 2.0;
+    k_(1, 1) = 1.0 / pixelSpacingY;
+    k_(1, 3) = static_cast<double>(imageHeight) / 2.0;
+    k_(2, 3) = 1.0;
+  }
+
+
+  FiniteProjectiveCamera *ShearWarpProjectiveTransform::CreateCamera() const
+  {
+    Matrix p = LinearAlgebra::Product(k_, M_warp, M_shear);
+    return new FiniteProjectiveCamera(p);
+  }
+  
+
+  void ShearWarpProjectiveTransform::ComputeShearOnSlice(double& a11,
+                                                         double& b1,
+                                                         double& a22,
+                                                         double& b2,
+                                                         double& shearedZ,
+                                                         const double sourceZ)
+  {
+    // Check out: ../../Resources/Computations/ComputeShearOnSlice.py
+    assert(IsValidShear(M_shear));
+
+    const double s11 = M_shear(0, 0);
+    const double s13 = M_shear(0, 2);
+    const double s14 = M_shear(0, 3);
+    const double s22 = M_shear(1, 1);
+    const double s23 = M_shear(1, 2);
+    const double s24 = M_shear(1, 3);
+    const double s43 = M_shear(3, 2);
+
+    double scaling = 1.0 / (s43 * sourceZ + 1.0);
+    shearedZ = sourceZ * scaling;
+
+    a11 = s11 * scaling;
+    a22 = s22 * scaling;
+
+    b1 = (s13 * sourceZ + s14) * scaling;
+    b2 = (s23 * sourceZ + s24) * scaling;
+  }
+
+
+  Matrix ShearWarpProjectiveTransform::CalibrateView(const Vector& camera,
+                                                     const Vector& principalPoint,
+                                                     double angle)
+  {
+    if (camera.size() != 3 ||
+        principalPoint.size() != 3)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const double sid = boost::numeric::ublas::norm_2(camera - principalPoint);
+      
+    Matrix a;
+    GeometryToolbox::AlignVectorsWithRotation(a, camera - principalPoint,
+                                              LinearAlgebra::CreateVector(0, 0, -1));
+
+    Matrix r = LinearAlgebra::Product(GeometryToolbox::CreateRotationMatrixAlongZ(angle), a);
+
+    a = LinearAlgebra::ZeroMatrix(4, 4);
+    boost::numeric::ublas::subrange(a, 0, 3, 0, 3) = r;
+
+    const Vector v = LinearAlgebra::Product(r, -camera);
+    a(0, 3) = v[0];
+    a(1, 3) = v[1];
+    a(2, 3) = v[2];
+    a(3, 3) = 1;
+
+    Matrix perspective = LinearAlgebra::ZeroMatrix(4, 4);
+    // https://stackoverflow.com/questions/5267866/calculation-of-a-perspective-transformation-matrix
+    perspective(0, 0) = sid;
+    perspective(1, 1) = sid;
+    perspective(2, 2) = sid;
+    perspective(3, 2) = 1;
+
+    Matrix M_view = LinearAlgebra::Product(perspective, a);
+    assert(M_view.size1() == 4 &&
+           M_view.size2() == 4);
+
+    {
+      // Sanity checks
+      Vector p1 = LinearAlgebra::CreateVector(camera[0], camera[1], camera[2], 1.0);
+      Vector p2 = LinearAlgebra::CreateVector(principalPoint[0], principalPoint[1], principalPoint[2], 1.0);
+      
+      Vector v1 = LinearAlgebra::Product(M_view, p1);
+      Vector v2 = LinearAlgebra::Product(M_view, p2);
+
+      if (!LinearAlgebra::IsCloseToZero(v1[3]) ||  // Must be mapped to singularity (w=0)
+          LinearAlgebra::IsCloseToZero(v2[3]))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      // The principal point must be mapped to (0,0,z,1)
+      v2 /= v2[3];
+      if (!LinearAlgebra::IsCloseToZero(v2[0]) ||
+          !LinearAlgebra::IsCloseToZero(v2[1]))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }      
+    }
+
+    return M_view;
+  }
+
+
+  template <Orthanc::PixelFormat SourceFormat,
+            Orthanc::PixelFormat TargetFormat,
+            bool MIP>
+  static void ApplyAxialInternal(Orthanc::ImageAccessor& target,
+                                 float& maxValue,
+                                 const Matrix& M_view,
+                                 const ImageBuffer3D& source,
+                                 double pixelSpacing,
+                                 unsigned int countSlices,
+                                 ImageInterpolation shearInterpolation,
+                                 ImageInterpolation warpInterpolation)
+  {
+    typedef Orthanc::PixelTraits<SourceFormat> SourceTraits;
+    typedef Orthanc::PixelTraits<TargetFormat> TargetTraits;
+
+    /**
+     * Step 1: Precompute some information.
+     **/
+       
+    if (target.GetFormat() != TargetFormat ||
+        source.GetFormat() != SourceFormat ||
+        !std::numeric_limits<float>::is_iec559 ||
+        sizeof(float) != 4)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    if (countSlices > source.GetDepth())
+    {
+      countSlices = source.GetDepth();
+    }
+
+    if (countSlices == 0)
+    {
+      maxValue = 0;
+      Orthanc::ImageProcessing::Set(target, 0);
+      return;
+    }
+    
+    LOG(INFO) << "Number of rendered slices: " << countSlices;
+
+
+    /**
+     * Step 2: Extract the shear-warp transform corresponding to
+     * M_view.
+     **/
+    
+    // Compute the "world" matrix that maps the source volume to the
+    // (0,0,0)->(1,1,1) unit cube
+    Vector origin = source.GetCoordinates(0, 0, 0);
+    Vector ps = source.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]));
+
+    Matrix worldInv;
+    LinearAlgebra::InvertMatrix(worldInv, world);
+
+    ShearWarpProjectiveTransform shearWarp(LinearAlgebra::Product(M_view, worldInv),
+                                           /*LinearAlgebra::IdentityMatrix(4),*/
+                                           source.GetWidth(),
+                                           source.GetHeight(),
+                                           source.GetDepth(),
+                                           pixelSpacing, pixelSpacing,
+                                           target.GetWidth(), target.GetHeight());
+    
+    const unsigned int intermediateWidth = shearWarp.GetIntermediateWidth();
+    const unsigned int intermediateHeight = shearWarp.GetIntermediateHeight();
+
+
+    /**
+     * Step 3: Apply the "shear" part of the transform to form the
+     * intermediate image. The sheared images are accumulated into the
+     * Float32 image "accumulator". The number of samples available
+     * for each pixel is stored in the "counter" image.
+     **/
+
+    std::auto_ptr<Orthanc::ImageAccessor> accumulator, counter, intermediate;
+
+    accumulator.reset(new Orthanc::Image(Orthanc::PixelFormat_Float32,
+                                         intermediateWidth, intermediateHeight, false));
+    counter.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16,
+                                     intermediateWidth, intermediateHeight, false)); 
+    intermediate.reset(new Orthanc::Image(SourceFormat, intermediateWidth, intermediateHeight, false));
+
+    Orthanc::ImageProcessing::Set(*accumulator, 0);
+    Orthanc::ImageProcessing::Set(*counter, 0);
+
+    // Loop around the slices of the volume
+    for (unsigned int i = 0; i <= countSlices; i++)
+    {
+      // (3.a) Compute the shear for this specific slice
+      unsigned int z = static_cast<unsigned int>(
+        boost::math::iround(static_cast<double>(i) /
+                            static_cast<double>(countSlices) *
+                            static_cast<double>(source.GetDepth() - 1)));
+      
+      double a11, b1, a22, b2, vz;
+      shearWarp.ComputeShearOnSlice(a11, b1, a22, b2, vz, static_cast<double>(z) + 0.5);
+
+
+      {
+        // (3.b) Detect the "useful" portion of the intermediate image
+        // for this slice (i.e. the bounding box where the source
+        // slice is mapped to by the shear), so as to update "counter"
+        Matrix a = LinearAlgebra::ZeroMatrix(3, 3);
+        a(0,0) = a11;
+        a(0,2) = b1;
+        a(1,1) = a22;
+        a(1,2) = b2;
+        a(2,2) = 1;
+
+        unsigned int x1, y1, x2, y2;
+        if (GetProjectiveTransformExtent(x1, y1, x2, y2, a,
+                                         source.GetWidth(), source.GetHeight(),
+                                         intermediateWidth, intermediateHeight))
+        {
+          for (unsigned int y = y1; y <= y2; y++)
+          {
+            uint16_t* p = reinterpret_cast<uint16_t*>(counter->GetRow(y)) + x1;
+            for (unsigned int x = x1; x <= x2; x++, p++)
+            {
+              if (MIP)
+              {
+                // TODO - In the case of MIP, "counter" could be
+                // reduced to "PixelFormat_Grayscale8" to reduce
+                // memory usage
+                *p = 1;
+              }
+              else
+              {
+                *p += 1;
+              }
+            }
+          }
+        }
+      }
+
+
+      {
+        // (3.c) Shear the source slice into a temporary image
+        ImageBuffer3D::SliceReader reader(source, VolumeProjection_Axial, z);      
+        ApplyAffineTransform(*intermediate, reader.GetAccessor(),
+                             a11, 0,   b1,
+                             0,   a22, b2,
+                             shearInterpolation);
+      }
+      
+
+      for (unsigned int y = 0; y < intermediateHeight; y++)
+      {
+        // (3.d) Accumulate the pixels of the sheared image into "accumulator"
+        const typename SourceTraits::PixelType* p =
+          reinterpret_cast<const typename SourceTraits::PixelType*>(intermediate->GetConstRow(y));
+
+        float* q = reinterpret_cast<float*>(accumulator->GetRow(y));
+      
+        for (unsigned int x = 0; x < intermediateWidth; x++)
+        {
+          float pixel = SourceTraits::PixelToFloat(*p);
+          
+          if (MIP)
+          {
+            // Get maximum for MIP
+            if (*q < pixel)
+            {
+              *q = pixel;
+            }
+          }
+          else
+          {
+            *q += pixel;
+          }
+          
+          p++;
+          q++;
+        }
+      }
+    }
+
+
+    /**
+     * Step 4: The intermediate image (that will be transformed by the
+     * "warp") is now available as an accumulator image together with
+     * a counter image. "Flatten" these two images into one.
+     **/
+
+    intermediate.reset(new Orthanc::Image
+                       (TargetFormat, intermediateWidth, intermediateHeight, false));
+
+    maxValue = 0;
+    
+    for (unsigned int y = 0; y < intermediateHeight; y++)
+    {
+      const float *qacc = reinterpret_cast<const float*>(accumulator->GetConstRow(y));
+      const uint16_t *qcount = reinterpret_cast<const uint16_t*>(counter->GetConstRow(y));
+      typename TargetTraits::PixelType *p =
+        reinterpret_cast<typename TargetTraits::PixelType*>(intermediate->GetRow(y));
+
+      for (unsigned int x = 0; x < intermediateWidth; x++)
+      {
+        if (*qcount == 0)
+        {
+          TargetTraits::SetZero(*p);
+        }
+        else
+        {
+          *p = *qacc / static_cast<float>(*qcount);
+
+          if (*p > maxValue)
+          {
+            maxValue = *p;
+          }
+        }
+        
+        p++;
+        qacc++;
+        qcount++;
+      }
+    }
+
+    // We don't need the accumulator images anymore
+    accumulator.reset(NULL);
+    counter.reset(NULL);
+
+    
+    /**
+     * Step 6: Apply the "warp" part of the transform to map the
+     * intermediate image to the final image.
+     **/
+
+    Matrix warp;
+
+    {
+      // (5.a) Compute the "warp" matrix by removing the 3rd row and
+      // 3rd column from the GetWarp() matrix
+      // Check out: ../../Resources/Computations/ComputeWarp.py
+    
+      Matrix fullWarp = LinearAlgebra::Product
+        (shearWarp.GetIntrinsicParameters(), shearWarp.GetWarp());
+
+      const double v[] = {
+        fullWarp(0,0), fullWarp(0,1), fullWarp(0,3), 
+        fullWarp(1,0), fullWarp(1,1), fullWarp(1,3), 
+        fullWarp(2,0), fullWarp(2,1), fullWarp(2,3)
+      };
+
+      LinearAlgebra::FillMatrix(warp, 3, 3, v);
+    }
+
+    // (5.b) Apply the projective transform to the image
+    ApplyProjectiveTransform(target, *intermediate, warp, warpInterpolation);
+  }
+
+
+  template <Orthanc::PixelFormat SourceFormat,
+            Orthanc::PixelFormat TargetFormat>
+  static void ApplyAxialInternal2(Orthanc::ImageAccessor& target,
+                                  float& maxValue,
+                                  const Matrix& M_view,
+                                  const ImageBuffer3D& source,
+                                  bool mip,
+                                  double pixelSpacing,
+                                  unsigned int countSlices,
+                                  ImageInterpolation shearInterpolation,
+                                  ImageInterpolation warpInterpolation)
+  {
+    if (mip)
+    {
+      ApplyAxialInternal<SourceFormat, TargetFormat, true>
+        (target, maxValue, M_view, source, pixelSpacing,
+         countSlices, shearInterpolation, warpInterpolation);
+    }
+    else
+    {
+      ApplyAxialInternal<SourceFormat, TargetFormat, false>
+        (target, maxValue, M_view, source, pixelSpacing,
+         countSlices, shearInterpolation, warpInterpolation);
+    } 
+  }
+  
+
+  Orthanc::ImageAccessor*
+  ShearWarpProjectiveTransform::ApplyAxial(float& maxValue,
+                                           const Matrix& M_view,
+                                           const ImageBuffer3D& source,
+                                           Orthanc::PixelFormat targetFormat,
+                                           unsigned int targetWidth,
+                                           unsigned int targetHeight,
+                                           bool mip,
+                                           double pixelSpacing,
+                                           unsigned int countSlices,
+                                           ImageInterpolation shearInterpolation,
+                                           ImageInterpolation warpInterpolation)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor> target
+      (new Orthanc::Image(targetFormat, targetWidth, targetHeight, false));
+    
+    if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
+        targetFormat == Orthanc::PixelFormat_Grayscale16)
+    {
+      ApplyAxialInternal2<Orthanc::PixelFormat_Grayscale16,
+                          Orthanc::PixelFormat_Grayscale16>
+        (*target, maxValue, M_view, source, mip, pixelSpacing,
+         countSlices, shearInterpolation, warpInterpolation);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    return target.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/ShearWarpProjectiveTransform.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,104 @@
+/**
+ * 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 "FiniteProjectiveCamera.h"
+
+namespace OrthancStone
+{
+  class ShearWarpProjectiveTransform : public boost::noncopyable
+  {
+  private:
+    Matrix        k_;
+    Matrix        M_shear;
+    Matrix        M_warp;
+    Vector        eye_o;
+    unsigned int  intermediateWidth_;
+    unsigned int  intermediateHeight_;
+
+  public:
+    ShearWarpProjectiveTransform(const Matrix& M_view,
+                                 //const Matrix& P,           // Permutation applied to the volume
+                                 unsigned int volumeWidth,
+                                 unsigned int volumeHeight,
+                                 unsigned int volumeDepth,
+                                 double pixelSpacingX,
+                                 double pixelSpacingY,
+                                 unsigned int imageWidth,
+                                 unsigned int imageHeight);
+
+    const Matrix& GetIntrinsicParameters() const
+    {
+      return k_;
+    }
+
+    const Matrix& GetShear() const
+    {
+      return M_shear;
+    }
+
+    const Matrix& GetWarp() const
+    {
+      return M_warp;
+    }
+
+    const Vector& GetCameraCenter() const
+    {
+      return eye_o;
+    }
+
+    unsigned int GetIntermediateWidth() const
+    {
+      return intermediateWidth_;
+    }
+
+    unsigned int GetIntermediateHeight() const
+    {
+      return intermediateHeight_;
+    }
+
+    FiniteProjectiveCamera *CreateCamera() const;
+
+    void ComputeShearOnSlice(double& a11,
+                             double& b1,
+                             double& a22,
+                             double& b2,
+                             double& shearedZ,
+                             const double sourceZ);
+
+    static Matrix CalibrateView(const Vector& camera,
+                                const Vector& principalPoint,
+                                double angle);
+
+    static Orthanc::ImageAccessor* ApplyAxial(float& maxValue,
+                                              const Matrix& M_view,  // cf. "CalibrateView()"
+                                              const ImageBuffer3D& source,
+                                              Orthanc::PixelFormat targetFormat,
+                                              unsigned int targetWidth,
+                                              unsigned int targetHeight,
+                                              bool mip,
+                                              double pixelSpacing,
+                                              unsigned int countSlices,
+                                              ImageInterpolation shearInterpolation,
+                                              ImageInterpolation warpInterpolation);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Slice.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,349 @@
+/**
+ * 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 "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 OrthancStone
+{
+  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;
+    }
+  }
+  
+  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 (!LinearAlgebra::IsCloseToZero(offset0))
+    {
+      LOG(ERROR) << "Invalid syntax";
+      return false;
+    }
+
+    geometry_ = 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);
+
+    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_ = CoordinateSystem3D(position, orientation);
+
+      bool ok = true;
+      SopClassUid tmp;
+
+      if (StringToSopClassUid(tmp, sopClassUid_))
+      {
+        switch (tmp)
+        {
+          case 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 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 CoordinateSystem3D& plane) const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    bool opposite;
+    return (GeometryToolbox::IsParallelOrOpposite(opposite,
+                                                  GetGeometry().GetNormal(),
+                                                  plane.GetNormal()) &&
+            LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
+                                  GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
+                                  thickness_ / 2.0));
+  }
+
+  
+  void Slice::GetExtent(std::vector<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/Toolbox/Slice.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,140 @@
+/**
+ * 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 "CoordinateSystem3D.h"
+#include "DicomFrameConverter.h"
+
+#include <Core/DicomFormat/DicomImageInformation.h>
+
+namespace OrthancStone
+{
+  class Slice : public boost::noncopyable
+  {
+  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_"
+    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)
+    {        
+    }
+
+    // TODO Is this constructor the best way to go to tackle missing
+    // layers within LayerWidget?
+    Slice(const 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 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 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 CoordinateSystem3D& plane) const;
+
+    void GetExtent(std::vector<Vector>& points) const;
+
+    const Orthanc::DicomImageInformation& GetImageInformation() const;
+  };
+}
--- a/Framework/Toolbox/SliceGeometry.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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 "SliceGeometry.h"
-
-#include "GeometryToolbox.h"
-
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/Toolbox.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  void SliceGeometry::CheckAndComputeNormal()
-  {
-    // DICOM expects normal vectors to define the axes: "The row and
-    // column direction cosine vectors shall be normal, i.e., the dot
-    // product of each direction cosine vector with itself shall be
-    // unity."
-    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
-    if (!GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(axisX_), 1.0) ||
-        !GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(axisY_), 1.0))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    // The vectors within "Image Orientation Patient" must be
-    // orthogonal, according to the DICOM specification: "The row and
-    // column direction cosine vectors shall be orthogonal, i.e.,
-    // their dot product shall be zero."
-    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.2.html
-    if (!GeometryToolbox::IsCloseToZero(boost::numeric::ublas::inner_prod(axisX_, axisY_)))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    GeometryToolbox::CrossProduct(normal_, axisX_, axisY_);
-
-    // Just a sanity check, it should be useless by construction
-    assert(GeometryToolbox::IsNear(boost::numeric::ublas::norm_2(normal_), 1.0));
-  }
-
-
-  void SliceGeometry::SetupCanonical()
-  {
-    GeometryToolbox::AssignVector(origin_, 0, 0, 0);
-    GeometryToolbox::AssignVector(axisX_, 1, 0, 0);
-    GeometryToolbox::AssignVector(axisY_, 0, 1, 0);
-    CheckAndComputeNormal();
-  }
-
-
-  SliceGeometry::SliceGeometry(const Vector& origin,
-                               const Vector& axisX,
-                               const Vector& axisY) :
-    origin_(origin),
-    axisX_(axisX),
-    axisY_(axisY)
-  {
-    CheckAndComputeNormal();
-  }
-
-
-  void SliceGeometry::Setup(const std::string& imagePositionPatient,
-                            const std::string& imageOrientationPatient)
-  {
-    std::string tmpPosition = Orthanc::Toolbox::StripSpaces(imagePositionPatient);
-    std::string tmpOrientation = Orthanc::Toolbox::StripSpaces(imageOrientationPatient);
-
-    Vector orientation;
-    if (!GeometryToolbox::ParseVector(origin_, tmpPosition) ||
-        !GeometryToolbox::ParseVector(orientation, tmpOrientation) ||
-        origin_.size() != 3 ||
-        orientation.size() != 6)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    axisX_.resize(3);
-    axisX_[0] = orientation[0];
-    axisX_[1] = orientation[1];
-    axisX_[2] = orientation[2];
-
-    axisY_.resize(3);
-    axisY_[0] = orientation[3];
-    axisY_[1] = orientation[4];
-    axisY_[2] = orientation[5];
-
-    CheckAndComputeNormal();
-  }   
-
-
-  SliceGeometry::SliceGeometry(const OrthancPlugins::IDicomDataset& dicom)
-  {
-    std::string a, b;
-
-    if (dicom.GetStringValue(a, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) &&
-        dicom.GetStringValue(b, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT))
-    {
-      Setup(a, b);
-    }
-    else
-    {
-      SetupCanonical();
-    }
-  }   
-
-
-  Vector SliceGeometry::MapSliceToWorldCoordinates(double x,
-                                                   double y) const
-  {
-    return origin_ + x * axisX_ + y * axisY_;
-  }
-
-
-  double SliceGeometry::ProjectAlongNormal(const Vector& point) const
-  {
-    return boost::numeric::ublas::inner_prod(point, normal_);
-  }
-
-
-  void SliceGeometry::ProjectPoint(double& offsetX,
-                                   double& offsetY,
-                                   const Vector& point) const
-  {
-    // Project the point onto the slice
-    Vector projection;
-    GeometryToolbox::ProjectPointOntoPlane(projection, point, normal_, origin_);
-
-    // As the axes are orthonormal vectors thanks to
-    // CheckAndComputeNormal(), the following dot products give the
-    // offset of the origin of the slice wrt. the origin of the
-    // reference plane https://en.wikipedia.org/wiki/Vector_projection
-    offsetX = boost::numeric::ublas::inner_prod(axisX_, projection - origin_);
-    offsetY = boost::numeric::ublas::inner_prod(axisY_, projection - origin_);
-  }
-}
--- a/Framework/Toolbox/SliceGeometry.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +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 "GeometryToolbox.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h"
-
-namespace OrthancStone
-{
-  // Geometry of a 3D plane, NOT thread-safe
-  class SliceGeometry
-  {
-  private:
-    Vector    origin_;
-    Vector    normal_;
-    Vector    axisX_;
-    Vector    axisY_;
-
-    void CheckAndComputeNormal();
-
-    void Setup(const std::string& imagePositionPatient,
-               const std::string& imageOrientationPatient);
-
-    void SetupCanonical();
-
-  public:
-    SliceGeometry()
-    {
-      SetupCanonical();
-    }
-
-    SliceGeometry(const Vector& origin,
-                  const Vector& axisX,
-                  const Vector& axisY);
-
-    SliceGeometry(const OrthancPlugins::IDicomDataset& dicom);
-
-    SliceGeometry(const std::string& imagePositionPatient,
-                  const std::string& imageOrientationPatient)
-    {
-      Setup(imagePositionPatient, imageOrientationPatient);
-    }
-
-    const Vector& GetNormal() const
-    {
-      return normal_;
-    }
-
-    const Vector& GetOrigin() const
-    {
-      return origin_;
-    }
-
-    const Vector& GetAxisX() const
-    {
-      return axisX_;
-    }
-
-    const Vector& GetAxisY() const
-    {
-      return axisY_;
-    }
-
-    Vector MapSliceToWorldCoordinates(double x,
-                                      double y) const;
-    
-    double ProjectAlongNormal(const Vector& point) const;
-
-    void ProjectPoint(double& offsetX,
-                      double& offsetY,
-                      const Vector& point) const;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SlicesSorter.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,222 @@
+/**
+ * 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 "SlicesSorter.h"
+
+#include "GeometryToolbox.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class SlicesSorter::SliceWithDepth : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<Slice>   slice_;
+    double                 depth_;
+
+  public:
+    SliceWithDepth(Slice* slice) :
+      slice_(slice),
+      depth_(0)
+    {
+      if (slice == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    void SetNormal(const Vector& normal)
+    {
+      assert(slice_.get() != NULL);
+      depth_ = boost::numeric::ublas::inner_prod
+        (slice_->GetGeometry().GetOrigin(), normal);
+    }
+
+    double GetDepth() const
+    {
+      return depth_;
+    }
+
+    const Slice& GetSlice() const
+    {
+      assert(slice_.get() != NULL);
+      return *slice_;
+    }
+  };
+
+
+  struct SlicesSorter::Comparator
+  {
+    bool operator() (const SliceWithDepth* const& a,
+                     const SliceWithDepth* const& b) const
+    {
+      return a->GetDepth() < b->GetDepth();
+    }
+  };
+
+
+  SlicesSorter::~SlicesSorter()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(slices_[i] != NULL);
+      delete slices_[i];
+    }
+  }
+
+
+  void SlicesSorter::AddSlice(Slice* slice)
+  {
+    slices_.push_back(new SliceWithDepth(slice));
+  }
+
+  
+  const Slice& SlicesSorter::GetSlice(size_t i) const
+  {
+    if (i >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(slices_[i] != NULL);
+    return slices_[i]->GetSlice();
+  }
+
+  
+  void SlicesSorter::SetNormal(const Vector& normal)
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      slices_[i]->SetNormal(normal);
+    }
+
+    hasNormal_ = true;
+  }
+  
+    
+  void SlicesSorter::Sort()
+  {
+    if (!hasNormal_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    Comparator comparator;
+    std::sort(slices_.begin(), slices_.end(), comparator);
+  }
+  
+
+  void SlicesSorter::FilterNormal(const Vector& normal)
+  {
+    size_t pos = 0;
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      if (GeometryToolbox::IsParallel(normal, slices_[i]->GetSlice().GetGeometry().GetNormal()))
+      {
+        // This slice is compatible with the selected normal
+        slices_[pos] = slices_[i];
+        pos += 1;
+      }
+      else
+      {
+        delete slices_[i];
+        slices_[i] = NULL;
+      }
+    }
+
+    slices_.resize(pos);
+  }
+  
+    
+  bool SlicesSorter::SelectNormal(Vector& normal) const
+  {
+    std::vector<Vector>  normalCandidates;
+    std::vector<unsigned int>  normalCount;
+
+    bool found = false;
+
+    for (size_t i = 0; !found && i < GetSliceCount(); i++)
+    {
+      const Vector& normal = GetSlice(i).GetGeometry().GetNormal();
+
+      bool add = true;
+      for (size_t j = 0; add && j < normalCandidates.size(); j++)  // (*)
+      {
+        if (GeometryToolbox::IsParallel(normal, normalCandidates[j]))
+        {
+          normalCount[j] += 1;
+          add = false;
+        }
+      }
+
+      if (add)
+      {
+        if (normalCount.size() > 2)
+        {
+          // To get linear-time complexity in (*). This heuristics
+          // allows the series to have one single frame that is
+          // not parallel to the others (such a frame could be a
+          // generated preview)
+          found = false;
+        }
+        else
+        {
+          normalCandidates.push_back(normal);
+          normalCount.push_back(1);
+        }
+      }
+    }
+
+    for (size_t i = 0; !found && i < normalCandidates.size(); i++)
+    {
+      unsigned int count = normalCount[i];
+      if (count == GetSliceCount() ||
+          count + 1 == GetSliceCount())
+      {
+        normal = normalCandidates[i];
+        found = true;
+      }
+    }
+
+    return found;
+  }
+
+
+  bool SlicesSorter::LookupSlice(size_t& index,
+                                 const CoordinateSystem3D& slice) const
+  {
+    // TODO Turn this linear-time lookup into a log-time lookup,
+    // keeping track of whether the slices are sorted along the normal
+
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      if (slices_[i]->GetSlice().ContainsPlane(slice))
+      {
+        index = i;
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SlicesSorter.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,71 @@
+/**
+ * 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 "Slice.h"
+
+namespace OrthancStone
+{
+  class SlicesSorter : public boost::noncopyable
+  {
+  private:
+    class SliceWithDepth;
+    struct Comparator;
+
+    typedef std::vector<SliceWithDepth*>  Slices;
+
+    Slices  slices_;
+    bool    hasNormal_;
+    
+  public:
+    SlicesSorter() : hasNormal_(false)
+    {
+    }
+
+    ~SlicesSorter();
+    
+    void Reserve(size_t count)
+    {
+      slices_.reserve(count);
+    }
+
+    void AddSlice(Slice* slice);  // Takes ownership
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const Slice& GetSlice(size_t i) const;
+
+    void SetNormal(const Vector& normal);
+    
+    void Sort();
+
+    void FilterNormal(const Vector& normal);
+    
+    bool SelectNormal(Vector& normal) const;
+
+    bool LookupSlice(size_t& index,
+                     const CoordinateSystem3D& slice) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SubpixelReader.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,260 @@
+/**
+ * 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 "../StoneEnumerations.h"
+#include "GeometryToolbox.h"
+
+#include <Core/Images/ImageTraits.h>
+
+#include <boost/noncopyable.hpp>
+#include <cmath>
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    class SubpixelReaderBase : public boost::noncopyable
+    {
+    private:
+      const Orthanc::ImageAccessor& source_;
+      unsigned int                  width_;
+      unsigned int                  height_;
+
+    public:
+      SubpixelReaderBase(const Orthanc::ImageAccessor& source) :
+        source_(source),
+        width_(source.GetWidth()),
+        height_(source.GetHeight())
+      {
+      }
+
+      ORTHANC_FORCE_INLINE
+      const Orthanc::ImageAccessor& GetSource() const
+      {
+        return source_;
+      }
+      
+      ORTHANC_FORCE_INLINE
+      unsigned int GetWidth() const
+      {
+        return width_;
+      }
+      
+      ORTHANC_FORCE_INLINE
+      unsigned int GetHeight() const
+      {
+        return height_;
+      }
+    };
+  }
+
+    
+  template <Orthanc::PixelFormat Format,
+            ImageInterpolation Interpolation>
+  class SubpixelReader;
+
+    
+  template <Orthanc::PixelFormat Format>
+  class SubpixelReader<Format, ImageInterpolation_Nearest> : 
+    public Internals::SubpixelReaderBase
+  {
+  public:
+    typedef Orthanc::PixelTraits<Format>  Traits;
+    typedef typename Traits::PixelType    PixelType;
+
+    SubpixelReader(const Orthanc::ImageAccessor& source) :
+      SubpixelReaderBase(source)
+    {
+    }
+
+    inline bool GetValue(PixelType& target,
+                         float x,
+                         float y) const;
+
+    inline bool GetFloatValue(float& target,
+                              float x,
+                              float y) const;
+  };
+
+    
+    
+  template <Orthanc::PixelFormat Format>
+  class SubpixelReader<Format, ImageInterpolation_Bilinear> : 
+    public Internals::SubpixelReaderBase
+  {
+  public:
+    typedef Orthanc::PixelTraits<Format>  Traits;
+    typedef typename Traits::PixelType    PixelType;
+
+    SubpixelReader(const Orthanc::ImageAccessor& source) :
+      SubpixelReaderBase(source)
+    {
+    }
+
+    inline bool GetFloatValue(float& target,
+                              float x,
+                              float y) const;
+
+    inline bool GetValue(PixelType& target,
+                         float x,
+                         float y) const;
+  };
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubpixelReader<Format, ImageInterpolation_Nearest>::GetValue(PixelType& target,
+                                                                    float x,
+                                                                    float y) const
+  {
+    if (x < 0 ||
+        y < 0)
+    {
+      return false;
+    }
+    else
+    {
+      unsigned int ux = static_cast<unsigned int>(std::floor(x));
+      unsigned int uy = static_cast<unsigned int>(std::floor(y));
+
+      if (ux < GetWidth() &&
+          uy < GetHeight())
+      {
+        Orthanc::ImageTraits<Format>::GetPixel(target, GetSource(), ux, uy);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubpixelReader<Format, ImageInterpolation_Nearest>::GetFloatValue(float& target,
+                                                                         float x,
+                                                                         float y) const
+  {
+    PixelType value;
+    
+    if (GetValue(value, x, y))
+    {
+      target = Traits::PixelToFloat(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubpixelReader<Format, ImageInterpolation_Bilinear>::GetValue(PixelType& target,
+                                                                     float x,
+                                                                     float y) const
+  {
+    float value;
+
+    if (GetFloatValue(value, x, y))
+    {
+      Traits::FloatToPixel(target, value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubpixelReader<Format, ImageInterpolation_Bilinear>::GetFloatValue(float& target,
+                                                                          float x,
+                                                                          float y) const
+  {
+    x -= 0.5f;
+    y -= 0.5f;
+        
+    if (x < 0 ||
+        y < 0)
+    {
+      return false;
+    }
+    else
+    {
+      unsigned int ux = static_cast<unsigned int>(std::floor(x));
+      unsigned int uy = static_cast<unsigned int>(std::floor(y));
+
+      float f00, f01, f10, f11;
+
+      if (ux < GetWidth() &&
+          uy < GetHeight())
+      {
+        f00 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux, uy);
+      }
+      else
+      {
+        return false;
+      }
+
+      if (ux + 1 < GetWidth())
+      {
+        f01 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux + 1, uy);
+      }
+      else
+      {
+        f01 = f00;
+      }
+
+      if (uy + 1 < GetHeight())
+      {
+        f10 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux, uy + 1);
+      }
+      else
+      {
+        f10 = f00;
+      }
+
+      if (ux + 1 < GetWidth() &&
+          uy + 1 < GetHeight())
+      {
+        f11 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux + 1, uy + 1);
+      }
+      else
+      {
+        f11 = f00;
+      }
+
+      float ax = x - static_cast<float>(ux);
+      float ay = y - static_cast<float>(uy);
+      target = GeometryToolbox::ComputeBilinearInterpolationUnitSquare(ax, ay, f00, f01, f10, f11);
+          
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SubvoxelReader.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,419 @@
+/**
+ * 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 "../Volumes/ImageBuffer3D.h"
+#include "GeometryToolbox.h"
+
+#include <Core/Images/ImageTraits.h>
+
+#include <boost/noncopyable.hpp>
+#include <cmath>
+
+namespace OrthancStone
+{
+  namespace Internals
+  {
+    class SubvoxelReaderBase : public boost::noncopyable
+    {
+    private:
+      const ImageBuffer3D& source_;
+      unsigned int         width_;
+      unsigned int         height_;
+      unsigned int         depth_;
+
+    public:
+      SubvoxelReaderBase(const ImageBuffer3D& source) :
+        source_(source),
+        width_(source.GetWidth()),
+        height_(source.GetHeight()),
+        depth_(source.GetDepth())
+      {
+      }
+
+      ORTHANC_FORCE_INLINE
+      const Orthanc::ImageAccessor& GetSource() const
+      {
+        return source_.GetInternalImage();
+      }
+      
+      ORTHANC_FORCE_INLINE
+      unsigned int GetWidth() const
+      {
+        return width_;
+      }
+      
+      ORTHANC_FORCE_INLINE
+      unsigned int GetHeight() const
+      {
+        return height_;
+      }
+      
+      ORTHANC_FORCE_INLINE
+      unsigned int GetDepth() const
+      {
+        return depth_;
+      }
+      
+      ORTHANC_FORCE_INLINE
+      unsigned int ComputeRow(unsigned int y,
+                              unsigned int z) const
+      {
+        return z * height_ + y;
+      }
+    };
+  }
+
+    
+  template <Orthanc::PixelFormat Format,
+            ImageInterpolation Interpolation>
+  class SubvoxelReader;
+
+    
+  template <Orthanc::PixelFormat Format>
+  class SubvoxelReader<Format, ImageInterpolation_Nearest> : 
+    public Internals::SubvoxelReaderBase
+  {
+  public:
+    typedef Orthanc::PixelTraits<Format>  Traits;
+    typedef typename Traits::PixelType    PixelType;
+
+    SubvoxelReader(const ImageBuffer3D& source) :
+      SubvoxelReaderBase(source)
+    {
+    }
+
+    inline bool GetValue(PixelType& target,
+                         float x,
+                         float y,
+                         float z) const;
+    
+    inline bool GetFloatValue(float& target,
+                              float x,
+                              float y,
+                              float z) const;
+  };
+    
+    
+  template <Orthanc::PixelFormat Format>
+  class SubvoxelReader<Format, ImageInterpolation_Bilinear> : 
+    public Internals::SubvoxelReaderBase
+  {
+  public:
+    typedef Orthanc::PixelTraits<Format>  Traits;
+    typedef typename Traits::PixelType    PixelType;
+
+    SubvoxelReader(const ImageBuffer3D& source) :
+      SubvoxelReaderBase(source)
+    {
+    }
+
+    inline bool Sample(float& f00,
+                       float& f01,
+                       float& f10,
+                       float& f11,
+                       unsigned int ux,
+                       unsigned int uy,
+                       unsigned int uz) const;
+
+    inline bool GetValue(PixelType& target,
+                         float x,
+                         float y,
+                         float z) const;
+
+    inline bool GetFloatValue(float& target,
+                              float x,
+                              float y,
+                              float z) const;
+  };
+    
+
+  template <Orthanc::PixelFormat Format>
+  class SubvoxelReader<Format, ImageInterpolation_Trilinear> : 
+    public Internals::SubvoxelReaderBase
+  {
+  private:
+    SubvoxelReader<Format, ImageInterpolation_Bilinear>   bilinear_;
+
+  public:
+    typedef Orthanc::PixelTraits<Format>  Traits;
+    typedef typename Traits::PixelType    PixelType;
+
+    SubvoxelReader(const ImageBuffer3D& source) :
+      SubvoxelReaderBase(source),
+      bilinear_(source)
+    {
+    }
+
+    inline bool GetValue(PixelType& target,
+                         float x,
+                         float y,
+                         float z) const;
+
+    inline bool GetFloatValue(float& target,
+                              float x,
+                              float y,
+                              float z) const;
+  };
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Nearest>::GetValue(PixelType& target,
+                                                                    float x,
+                                                                    float y,
+                                                                    float z) const
+  {
+    if (x < 0 ||
+        y < 0 ||
+        z < 0)
+    {
+      return false;
+    }
+    else
+    {
+      unsigned int ux = static_cast<unsigned int>(std::floor(x));
+      unsigned int uy = static_cast<unsigned int>(std::floor(y));
+      unsigned int uz = static_cast<unsigned int>(std::floor(z));
+
+      if (ux < GetWidth() &&
+          uy < GetHeight() &&
+          uz < GetDepth())
+      {
+        Orthanc::ImageTraits<Format>::GetPixel(target, GetSource(), ux, ComputeRow(uy, uz));
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Nearest>::GetFloatValue(float& target,
+                                                                         float x,
+                                                                         float y,
+                                                                         float z) const
+  {
+    PixelType value;
+    
+    if (GetValue(value, x, y, z))
+    {
+      target = Traits::PixelToFloat(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Bilinear>::Sample(float& f00,
+                                                                   float& f01,
+                                                                   float& f10,
+                                                                   float& f11,
+                                                                   unsigned int ux,
+                                                                   unsigned int uy,
+                                                                   unsigned int uz) const
+  {
+    if (ux < GetWidth() &&
+        uy < GetHeight() &&
+        uz < GetDepth())
+    {
+      f00 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux, ComputeRow(uy, uz));
+    }
+    else
+    {
+      // Pixel is out of the volume
+      return false;
+    }
+
+    if (ux + 1 < GetWidth())
+    {
+      f01 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux + 1, ComputeRow(uy, uz));
+    }
+    else
+    {
+      f01 = f00;
+    }
+
+    if (uy + 1 < GetHeight())
+    {
+      f10 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux, ComputeRow(uy + 1, uz));
+    }
+    else
+    {
+      f10 = f00;
+    }
+
+    if (ux + 1 < GetWidth() &&
+        uy + 1 < GetHeight())
+    {
+      f11 = Orthanc::ImageTraits<Format>::GetFloatPixel(GetSource(), ux + 1, ComputeRow(uy + 1, uz));
+    }
+    else
+    {
+      f11 = f00;
+    }
+
+    return true;
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Bilinear>::GetFloatValue(float& target,
+                                                                          float x,
+                                                                          float y,
+                                                                          float z) const
+  {
+    x -= 0.5f;
+    y -= 0.5f;
+        
+    if (x < 0 ||
+        y < 0 ||
+        z < 0)
+    {
+      return false;
+    }
+    else
+    {
+      unsigned int ux = static_cast<unsigned int>(std::floor(x));
+      unsigned int uy = static_cast<unsigned int>(std::floor(y));
+      unsigned int uz = static_cast<unsigned int>(std::floor(z));
+
+      float f00, f01, f10, f11;
+      if (Sample(f00, f01, f10, f11, ux, uy, uz))
+      {
+        float ax = x - static_cast<float>(ux);
+        float ay = y - static_cast<float>(uy);
+
+        target = GeometryToolbox::ComputeBilinearInterpolationUnitSquare(ax, ay, f00, f01, f10, f11);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Bilinear>::GetValue(PixelType& target,
+                                                                     float x,
+                                                                     float y,
+                                                                     float z) const
+  {
+    float value;
+
+    if (GetFloatValue(value, x, y, z))
+    {
+      Traits::FloatToPixel(target, value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Trilinear>::GetFloatValue(float& target,
+                                                                           float x,
+                                                                           float y,
+                                                                           float z) const
+  {
+    x -= 0.5f;
+    y -= 0.5f;
+    z -= 0.5f;
+        
+    if (x < 0 ||
+        y < 0 ||
+        z < 0)
+    {
+      return false;
+    }
+    else
+    {
+      unsigned int ux = static_cast<unsigned int>(std::floor(x));
+      unsigned int uy = static_cast<unsigned int>(std::floor(y));
+      unsigned int uz = static_cast<unsigned int>(std::floor(z));
+
+      float f000, f001, f010, f011;
+      if (bilinear_.Sample(f000, f001, f010, f011, ux, uy, uz))
+      {
+        const float ax = x - static_cast<float>(ux);
+        const float ay = y - static_cast<float>(uy);
+
+        float f100, f101, f110, f111;
+
+        if (bilinear_.Sample(f100, f101, f110, f111, ux, uy, uz + 1))
+        {
+          const float az = z - static_cast<float>(uz);
+          target = GeometryToolbox::ComputeTrilinearInterpolationUnitSquare
+            (ax, ay, az, f000, f001, f010, f011, f100, f101, f110, f111);
+        }
+        else
+        {
+          target = GeometryToolbox::ComputeBilinearInterpolationUnitSquare
+            (ax, ay, f000, f001, f010, f011);
+        }
+
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+
+  template <Orthanc::PixelFormat Format>
+  bool SubvoxelReader<Format, ImageInterpolation_Trilinear>::GetValue(PixelType& target,
+                                                                      float x,
+                                                                      float y,
+                                                                      float z) const
+  {
+    float value;
+
+    if (GetFloatValue(value, x, y, z))
+    {
+      Traits::FloatToPixel(target, value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- a/Framework/Toolbox/ViewportGeometry.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ViewportGeometry.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #include "ViewportGeometry.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
 #include <boost/math/special_functions/round.hpp>
 
@@ -43,18 +43,15 @@
     cairo_matrix_multiply(&transform_, &tmp, &transform_);
 
     // Bring the center of the scene to (0,0)
-    cairo_matrix_init_translate(&tmp, -(x1_ + x2_) / 2.0, -(y1_ + y2_) / 2.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()
   {
-    x1_ = 0;
-    y1_ = 0;
-    x2_ = 0;
-    y2_ = 0;
-
     width_ = 0;
     height_ = 0;
 
@@ -82,46 +79,14 @@
   }
 
 
-  void ViewportGeometry::SetSceneExtent(double x1,
-                                        double y1,
-                                        double x2,
-                                        double y2)
+  void ViewportGeometry::SetSceneExtent(const Extent2D& extent)
   {
-    if (x1 == x1_ &&
-        y1 == y1_ &&
-        x2 == x2_ &&
-        y2 == y2_)
-    {
-      return;
-    }
-    else if (x1 > x2 || 
-             y1 > y2)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      LOG(INFO) << "New scene extent: (" << x1 << "," << y1 << ") => (" << x2 << "," << y2 << ")";
+    LOG(INFO) << "New scene extent: ("
+              << extent.GetX1() << "," << extent.GetY1() << ") => ("
+              << extent.GetX2() << "," << extent.GetY2() << ")";
 
-      x1_ = x1;
-      y1_ = y1;
-      x2_ = x2;
-      y2_ = y2;
-
-      ComputeTransform();
-    }
-  }
-
-
-  void ViewportGeometry::GetSceneExtent(double& x1,
-                                        double& y1,
-                                        double& x2,
-                                        double& y2) const
-  {
-    x1 = x1_;
-    y1 = y1_;
-    x2 = x2_;
-    y2 = y2_;
+    sceneExtent_ = extent;
+    ComputeTransform();
   }
 
 
@@ -160,11 +125,10 @@
   {
     if (width_ > 0 &&
         height_ > 0 &&
-        x2_ > x1_ + 10 * std::numeric_limits<double>::epsilon() &&
-        y2_ > y1_ + 10 * std::numeric_limits<double>::epsilon())
+        !sceneExtent_.IsEmpty())
     {
-      double zoomX = static_cast<double>(width_) / (x2_ - x1_);
-      double zoomY = static_cast<double>(height_) / (y2_ - y1_);
+      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;
--- a/Framework/Toolbox/ViewportGeometry.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Toolbox/ViewportGeometry.h	Tue Mar 20 20:03:02 2018 +0100
@@ -22,18 +22,15 @@
 #pragma once
 
 #include "../Viewport/CairoContext.h"
+#include "../Toolbox/Extent2D.h"
 
 namespace OrthancStone
 {
-  // Not thread-safe
   class ViewportGeometry
   {
   private:
     // Extent of the scene (in world units)
-    double   x1_;
-    double   y1_;
-    double   x2_;
-    double   y2_;
+    Extent2D   sceneExtent_;
 
     // Size of the display (in pixels)
     unsigned int  width_;
@@ -54,15 +51,12 @@
     void SetDisplaySize(unsigned int width,
                         unsigned int height);
 
-    void SetSceneExtent(double x1,
-                        double y1,
-                        double x2,
-                        double y2);
+    void SetSceneExtent(const Extent2D& extent);
 
-    void GetSceneExtent(double& x1,
-                        double& y1,
-                        double& x2,
-                        double& y2) const;
+    const Extent2D& GetSceneExtent() const
+    {
+      return sceneExtent_;
+    }
 
     void MapDisplayToScene(double& sceneX /* out */,
                            double& sceneY /* out */,
--- a/Framework/Viewport/CairoContext.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/CairoContext.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #include "CairoContext.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
--- a/Framework/Viewport/CairoFont.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/CairoFont.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #include "CairoFont.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
--- a/Framework/Viewport/CairoSurface.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/CairoSurface.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,9 +21,9 @@
 
 #include "CairoSurface.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageProcessing.h>
 
 namespace OrthancStone
 {
--- a/Framework/Viewport/CairoSurface.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/CairoSurface.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
+#include <Core/Images/ImageAccessor.h>
 
 #include <boost/noncopyable.hpp>
 #include <cairo.h>
--- a/Framework/Viewport/IMouseTracker.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/IMouseTracker.h	Tue Mar 20 20:03:02 2018 +0100
@@ -22,14 +22,16 @@
 #pragma once
 
 #include "CairoSurface.h"
-#include "../Toolbox/IThreadSafety.h"
 
 namespace OrthancStone
 {
-  // Not thread-safe
-  class IMouseTracker : public IThreadUnsafe
+  class IMouseTracker : public boost::noncopyable
   {
   public:
+    virtual ~IMouseTracker()
+    {
+    }
+    
     virtual void Render(Orthanc::ImageAccessor& surface) = 0;
 
     virtual void MouseUp() = 0;
--- a/Framework/Viewport/IStatusBar.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/IStatusBar.h	Tue Mar 20 20:03:02 2018 +0100
@@ -22,14 +22,17 @@
 #pragma once
 
 #include <string>
-#include "../Toolbox/IThreadSafety.h"
+#include <boost/noncopyable.hpp>
 
 namespace OrthancStone
 {
-  // This class must be thread-safe
-  class IStatusBar : public IThreadSafe
+  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	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/IViewport.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,40 +21,38 @@
 
 #pragma once
 
-#include "../Toolbox/IThreadSafety.h"
 #include "IStatusBar.h"
-#include "../Enumerations.h"
+#include "../StoneEnumerations.h"
 
-#include "../../Resources/Orthanc/Core/Images/ImageAccessor.h"
+#include <Core/Images/ImageAccessor.h>
 
 namespace OrthancStone
 {
-  // This class must be thread-safe
-  class IViewport : public IThreadSafe
+  class IWidget;   // Forward declaration
+  
+  class IViewport : public boost::noncopyable
   {
   public:
-    class IChangeObserver : public boost::noncopyable
+    class IObserver : public boost::noncopyable
     {
     public:
-      virtual ~IChangeObserver()
+      virtual ~IObserver()
       {
       }
 
       virtual void NotifyChange(const IViewport& scene) = 0;
     };
 
-    virtual void Register(IChangeObserver& observer) = 0;
+    virtual ~IViewport()
+    {
+    }
 
-    virtual void Unregister(IChangeObserver& observer) = 0;
+    virtual void SetDefaultView() = 0;
+
+    virtual void Register(IObserver& observer) = 0;
 
     virtual void SetStatusBar(IStatusBar& statusBar) = 0;
 
-    virtual void ResetStatusBar() = 0;
-
-    virtual void Start() = 0;
-
-    virtual void Stop() = 0;
-
     virtual void SetSize(unsigned int width,
                          unsigned int height) = 0;
 
@@ -82,5 +80,12 @@
 
     virtual void KeyPressed(char key,
                             KeyboardModifiers modifiers) = 0;
+
+    virtual bool HasUpdateContent() = 0;
+
+    virtual void UpdateContent() = 0;
+
+    // Should only be called from IWidget
+    virtual void NotifyChange(const IWidget& widget) = 0;
   };
 }
--- a/Framework/Viewport/WidgetViewport.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/WidgetViewport.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,37 +21,32 @@
 
 #include "WidgetViewport.h"
 
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
-  void WidgetViewport::UnregisterCentralWidget()
-  {
-    mouseTracker_.reset(NULL);
-
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->Unregister(*this);
-    }
-  }
-
-
   WidgetViewport::WidgetViewport() :
     statusBar_(NULL),
     isMouseOver_(false),
     lastMouseX_(0),
     lastMouseY_(0),
-    backgroundChanged_(false),
-    started_(false)
+    backgroundChanged_(false)
   {
   }
 
 
+  void WidgetViewport::SetDefaultView()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->SetDefaultView();
+    }
+  }
+
+
   void WidgetViewport::SetStatusBar(IStatusBar& statusBar)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     statusBar_ = &statusBar;
 
     if (centralWidget_.get() != NULL)
@@ -61,48 +56,25 @@
   }
 
 
-  void WidgetViewport::ResetStatusBar()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    statusBar_ = NULL;
-
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->ResetStatusBar();
-    }
-  }
-
-
   IWidget& WidgetViewport::SetCentralWidget(IWidget* widget)
   {
-    if (started_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    boost::mutex::scoped_lock lock(mutex_);
-
     if (widget == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    UnregisterCentralWidget();
+    mouseTracker_.reset(NULL);
       
     centralWidget_.reset(widget);
-    centralWidget_->Register(*this);
+    centralWidget_->SetViewport(*this);
 
-    if (statusBar_ == NULL)
-    {
-      centralWidget_->ResetStatusBar();
-    }
-    else
+    if (statusBar_ != NULL)
     {
       centralWidget_->SetStatusBar(*statusBar_);
     }
 
     backgroundChanged_ = true;
+    observers_.Apply(*this, &IObserver::NotifyChange);
 
     return *widget;
   }
@@ -111,41 +83,13 @@
   void WidgetViewport::NotifyChange(const IWidget& widget)
   {
     backgroundChanged_ = true;
-    observers_.NotifyChange(this);
-  }
-
-
-  void WidgetViewport::Start()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->Start();
-    }
-
-    started_ = true;
-  }
-
-
-  void WidgetViewport::Stop()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    started_ = false;
-
-    if (centralWidget_.get() != NULL)
-    {
-      centralWidget_->Stop();
-    }
+    observers_.Apply(*this, &IObserver::NotifyChange);
   }
 
 
   void WidgetViewport::SetSize(unsigned int width,
                                unsigned int height)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     background_.SetSize(width, height);
 
     if (centralWidget_.get() != NULL)
@@ -153,31 +97,33 @@
       centralWidget_->SetSize(width, height);
     }
 
-    observers_.NotifyChange(this);
+    observers_.Apply(*this, &IObserver::NotifyChange);
   }
 
 
   bool WidgetViewport::Render(Orthanc::ImageAccessor& surface)
   {
-    boost::mutex::scoped_lock lock(mutex_);
+    if (centralWidget_.get() == NULL)
+    {
+      return false;
+    }
+    
+    Orthanc::ImageAccessor background = background_.GetAccessor();
 
-    if (!started_ ||
-        centralWidget_.get() == NULL)
+    if (backgroundChanged_ &&
+        !centralWidget_->Render(background))
     {
       return false;
     }
 
-    if (backgroundChanged_)
+    if (background.GetWidth() != surface.GetWidth() ||
+        background.GetHeight() != surface.GetHeight())
     {
-      Orthanc::ImageAccessor accessor = background_.GetAccessor();
-      if (!centralWidget_->Render(accessor))
-      {
-        return false;
-      }
+      return false;
     }
 
-    Orthanc::ImageProcessing::Copy(surface, background_.GetAccessor());
-
+    Orthanc::ImageProcessing::Convert(surface, background);
+    
     if (mouseTracker_.get() != NULL)
     {
       mouseTracker_->Render(surface);
@@ -196,13 +142,6 @@
                                  int y,
                                  KeyboardModifiers modifiers)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (!started_)
-    {
-      return;
-    }
-
     lastMouseX_ = x;
     lastMouseY_ = y;
 
@@ -215,24 +154,17 @@
       mouseTracker_.reset(NULL);
     }      
 
-    observers_.NotifyChange(this);;
+    observers_.Apply(*this, &IObserver::NotifyChange);
   }
 
 
   void WidgetViewport::MouseUp()
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (!started_)
-    {
-      return;
-    }
-
     if (mouseTracker_.get() != NULL)
     {
       mouseTracker_->MouseUp();
       mouseTracker_.reset(NULL);
-      observers_.NotifyChange(this);;
+      observers_.Apply(*this, &IObserver::NotifyChange);
     }
   }
 
@@ -240,9 +172,7 @@
   void WidgetViewport::MouseMove(int x, 
                                  int y) 
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (!started_)
+    if (centralWidget_.get() == NULL)
     {
       return;
     }
@@ -250,29 +180,44 @@
     lastMouseX_ = x;
     lastMouseY_ = y;
 
+    bool repaint = false;
+    
     if (mouseTracker_.get() != NULL)
     {
       mouseTracker_->MouseMove(x, y);
+      repaint = true;
+    }
+    else
+    {
+      repaint = centralWidget_->HasRenderMouseOver();
     }
 
-    // The scene must be repainted
-    observers_.NotifyChange(this);
+    if (repaint)
+    {
+      // The scene must be repainted, notify the observers
+      observers_.Apply(*this, &IObserver::NotifyChange);
+    }
   }
 
 
   void WidgetViewport::MouseEnter()
   {
-    boost::mutex::scoped_lock lock(mutex_);
     isMouseOver_ = true;
-    observers_.NotifyChange(this);
+    observers_.Apply(*this, &IObserver::NotifyChange);
   }
 
 
   void WidgetViewport::MouseLeave()
   {
-    boost::mutex::scoped_lock lock(mutex_);
     isMouseOver_ = false;
-    observers_.NotifyChange(this);
+
+    if (mouseTracker_.get() != NULL)
+    {
+      mouseTracker_->MouseUp();
+      mouseTracker_.reset(NULL);
+    }
+
+    observers_.Apply(*this, &IObserver::NotifyChange);
   }
 
 
@@ -281,13 +226,6 @@
                                   int y,
                                   KeyboardModifiers modifiers)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (!started_)
-    {
-      return;
-    }
-
     if (centralWidget_.get() != NULL &&
         mouseTracker_.get() == NULL)
     {
@@ -299,12 +237,32 @@
   void WidgetViewport::KeyPressed(char key,
                                   KeyboardModifiers modifiers)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     if (centralWidget_.get() != NULL &&
         mouseTracker_.get() == NULL)
     {
       centralWidget_->KeyPressed(key, modifiers);
     }
   }
+
+
+  bool WidgetViewport::HasUpdateContent()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      return centralWidget_->HasUpdateContent();
+    }
+    else
+    {
+      return false;
+    }
+  }
+   
+
+  void WidgetViewport::UpdateContent()
+  {
+    if (centralWidget_.get() != NULL)
+    {
+      centralWidget_->UpdateContent();
+    }
+  }
 }
--- a/Framework/Viewport/WidgetViewport.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Viewport/WidgetViewport.h	Tue Mar 20 20:03:02 2018 +0100
@@ -25,14 +25,13 @@
 #include "../Toolbox/ObserversRegistry.h"
 #include "../Widgets/IWidget.h"
 
+#include <memory>
+
 namespace OrthancStone
 {
-  class WidgetViewport : 
-    public IViewport,
-    public IWidget::IChangeObserver    
+  class WidgetViewport : public IViewport
   {
   private:
-    boost::mutex                  mutex_;
     std::auto_ptr<IWidget>        centralWidget_;
     IStatusBar*                   statusBar_;
     ObserversRegistry<IViewport>  observers_;
@@ -42,40 +41,23 @@
     int                           lastMouseY_;
     CairoSurface                  background_;
     bool                          backgroundChanged_;
-    bool                          started_;
-
-    void UnregisterCentralWidget();
 
   public:
     WidgetViewport();
 
-    virtual ~WidgetViewport()
-    {
-      UnregisterCentralWidget();
-    }
+    virtual void SetDefaultView();
 
     virtual void SetStatusBar(IStatusBar& statusBar);
 
-    virtual void ResetStatusBar();
-
     IWidget& SetCentralWidget(IWidget* widget);  // Takes ownership
 
     virtual void NotifyChange(const IWidget& widget);
 
-    virtual void Register(IViewport::IChangeObserver& observer)
+    virtual void Register(IObserver& observer)
     {
       observers_.Register(observer);
     }
 
-    virtual void Unregister(IViewport::IChangeObserver& observer)
-    {
-      observers_.Unregister(observer);
-    }
-
-    virtual void Start();
-
-    virtual void Stop();
-
     virtual void SetSize(unsigned int width,
                          unsigned int height);
 
@@ -102,5 +84,9 @@
 
     virtual void KeyPressed(char key,
                             KeyboardModifiers modifiers);
+
+    virtual bool HasUpdateContent();
+
+    virtual void UpdateContent();
   };
 }
--- a/Framework/Volumes/ISliceableVolume.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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 "../Toolbox/IThreadSafety.h"
-
-namespace OrthancStone
-{
-  class ISliceableVolume : public IThreadSafe
-  {
-  public:
-    // Must be thread-safe
-    class IChangeObserver : public boost::noncopyable
-    {
-    public:
-      virtual ~IChangeObserver()
-      {
-      }
-
-      virtual void NotifyChange(const ISliceableVolume& volume) = 0;
-    };
-
-    virtual void Register(IChangeObserver& observer) = 0;
-
-    virtual void Unregister(IChangeObserver& observer) = 0;
-
-    virtual void Start() = 0;
-
-    virtual void Stop() = 0;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/ISlicedVolume.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,66 @@
+/**
+ * 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 "../Toolbox/Slice.h"
+
+namespace OrthancStone
+{
+  class ISlicedVolume : public boost::noncopyable
+  {
+  public:
+    class IObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IObserver()
+      {
+      }
+
+      virtual void NotifyGeometryReady(const ISlicedVolume& volume) = 0;
+      
+      virtual void NotifyGeometryError(const ISlicedVolume& volume) = 0;
+      
+      // Triggered if the content of several slices in the volume has
+      // changed
+      virtual void NotifyContentChange(const ISlicedVolume& volume) = 0;
+
+      // Triggered if the content of some individual slice in the
+      // source volume has changed
+      virtual void NotifySliceChange(const ISlicedVolume& volume,
+                                     const size_t& sliceIndex,
+                                     const Slice& slice) = 0;
+
+      // Triggered when the geometry *and* the content of the volume are available
+      virtual void NotifyVolumeReady(const ISlicedVolume& volume) = 0;
+    };
+    
+    virtual ~ISlicedVolume()
+    {
+    }
+
+    virtual void Register(IObserver& observer) = 0;
+
+    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/Volumes/IVolumeLoader.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,53 @@
+/**
+ * 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/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class IVolumeLoader : public boost::noncopyable
+  {
+  public:
+    class IObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IObserver()
+      {
+      }
+      
+      virtual void NotifyGeometryReady(const IVolumeLoader& loader) = 0;
+      
+      virtual void NotifyGeometryError(const IVolumeLoader& loader) = 0;
+      
+      // Triggered if the content of several slices in the loader has
+      // changed
+      virtual void NotifyContentChange(const IVolumeLoader& loader) = 0;
+    };
+    
+    virtual ~IVolumeLoader()
+    {
+    }
+
+    virtual void Register(IObserver& observer) = 0;
+  };
+}
--- a/Framework/Volumes/ImageBuffer3D.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,13 +21,16 @@
 
 #include "ImageBuffer3D.h"
 
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <string.h>
 
 namespace OrthancStone
 {
   Orthanc::ImageAccessor ImageBuffer3D::GetAxialSliceAccessor(unsigned int slice,
-                                                              bool readOnly)
+                                                              bool readOnly) const
   {
     if (slice >= depth_)
     {
@@ -52,7 +55,7 @@
 
 
   Orthanc::ImageAccessor ImageBuffer3D::GetCoronalSliceAccessor(unsigned int slice,
-                                                                bool readOnly)
+                                                                bool readOnly) const
   {
     if (slice >= height_)
     {
@@ -109,27 +112,31 @@
   ImageBuffer3D::ImageBuffer3D(Orthanc::PixelFormat format,
                                unsigned int width,
                                unsigned int height,
-                               unsigned int depth) :
+                               unsigned int depth,
+                               bool computeRange) :
     image_(format, width, height * depth, false),
     format_(format),
     width_(width),
     height_(height),
-    depth_(depth)
+    depth_(depth),
+    computeRange_(computeRange),
+    hasRange_(false)
   {
-    GeometryToolbox::AssignVector(voxelDimensions_, 1, 1, 1);
+    LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
+
+    LOG(INFO) << "Created an image of "
+              << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB";
   }
 
 
   void ImageBuffer3D::Clear()
   {
-    WriteLock lock(mutex_);
-    Orthanc::ImageProcessing::Set(image_, 0);
+    memset(image_.GetBuffer(), 0, image_.GetHeight() * image_.GetPitch());
   }
 
 
-  void ImageBuffer3D::SetAxialGeometry(const SliceGeometry& geometry)
+  void ImageBuffer3D::SetAxialGeometry(const CoordinateSystem3D& geometry)
   {
-    WriteLock lock(mutex_);
     axialGeometry_ = geometry;
   }
 
@@ -146,16 +153,13 @@
     }
 
     {
-      WriteLock lock(mutex_);
-      GeometryToolbox::AssignVector(voxelDimensions_, x, y, z);
+      LinearAlgebra::AssignVector(voxelDimensions_, x, y, z);
     }
   }
 
 
-  Vector ImageBuffer3D::GetVoxelDimensions(VolumeProjection projection)
+  Vector ImageBuffer3D::GetVoxelDimensions(VolumeProjection projection) const
   {
-    ReadLock lock(mutex_);
-
     Vector result;
     switch (projection)
     {
@@ -164,11 +168,11 @@
         break;
 
       case VolumeProjection_Coronal:
-        GeometryToolbox::AssignVector(result, voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
+        LinearAlgebra::AssignVector(result, voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]);
         break;
 
       case VolumeProjection_Sagittal:
-        GeometryToolbox::AssignVector(result, voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
+        LinearAlgebra::AssignVector(result, voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]);
         break;
 
       default:
@@ -206,7 +210,7 @@
   }
 
 
-  ParallelSlices* ImageBuffer3D::GetGeometry(VolumeProjection projection)
+  ParallelSlices* ImageBuffer3D::GetGeometry(VolumeProjection projection) const
   {
     std::auto_ptr<ParallelSlices> result(new ParallelSlices);
 
@@ -258,10 +262,100 @@
   }
     
 
-  ImageBuffer3D::SliceReader::SliceReader(ImageBuffer3D& that,
+  uint64_t ImageBuffer3D::GetEstimatedMemorySize() const
+  {
+    return image_.GetPitch() * image_.GetHeight() * Orthanc::GetBytesPerPixel(format_);
+  }
+
+
+  void ImageBuffer3D::ExtendImageRange(const Orthanc::ImageAccessor& slice)
+  {
+    if (!computeRange_ ||
+        slice.GetWidth() == 0 ||
+        slice.GetHeight() == 0)
+    {
+      return;
+    }
+
+    float sliceMin, sliceMax;
+      
+    switch (slice.GetFormat())
+    {
+      case Orthanc::PixelFormat_Grayscale8:
+      case Orthanc::PixelFormat_Grayscale16:
+      case Orthanc::PixelFormat_Grayscale32:
+      case Orthanc::PixelFormat_SignedGrayscale16:
+      {
+        int64_t a, b;
+        Orthanc::ImageProcessing::GetMinMaxIntegerValue(a, b, slice);
+        sliceMin = static_cast<float>(a);
+        sliceMax = static_cast<float>(b);
+        break;
+      }
+
+      case Orthanc::PixelFormat_Float32:
+        Orthanc::ImageProcessing::GetMinMaxFloatValue(sliceMin, sliceMax, slice);
+        break;
+
+      default:
+        return;
+    }
+
+    if (hasRange_)
+    {
+      minValue_ = std::min(minValue_, sliceMin);
+      maxValue_ = std::max(maxValue_, sliceMax);
+    }
+    else
+    {
+      hasRange_ = true;
+      minValue_ = sliceMin;
+      maxValue_ = sliceMax;
+    }
+  }
+
+
+  bool ImageBuffer3D::GetRange(float& minValue,
+                               float& maxValue) const
+  {
+    if (hasRange_)
+    {
+      minValue = minValue_;
+      maxValue = maxValue_;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ImageBuffer3D::FitWindowingToRange(RenderStyle& style,
+                                          const DicomFrameConverter& converter) const
+  {
+    if (hasRange_)
+    {
+      style.windowing_ = ImageWindowing_Custom;
+      style.customWindowCenter_ = converter.Apply((minValue_ + maxValue_) / 2.0);
+      style.customWindowWidth_ = converter.Apply(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) :
-  lock_(that.mutex_)
+                                          unsigned int slice)
   {
     switch (projection)
     {
@@ -286,10 +380,17 @@
 
   void ImageBuffer3D::SliceWriter::Flush()
   {
-    if (sagittal_.get() != NULL)
+    if (modified_)
     {
-      // TODO
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+      if (sagittal_.get() != NULL)
+      {
+        // TODO
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+      }
+
+      // Update the dynamic range of the underlying image, if
+      // "computeRange_" is set to true
+      that_.ExtendImageRange(accessor_);
     }
   }
 
@@ -297,7 +398,8 @@
   ImageBuffer3D::SliceWriter::SliceWriter(ImageBuffer3D& that,
                                           VolumeProjection projection,
                                           unsigned int slice) :
-  lock_(that.mutex_)
+    that_(that),
+    modified_(false)
   {
     switch (projection)
     {
@@ -318,4 +420,64 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);          
     }
   }
+
+
+  uint8_t ImageBuffer3D::GetVoxelGrayscale8(unsigned int x,
+                                            unsigned int y,
+                                            unsigned int z) const
+  {
+    if (format_ != Orthanc::PixelFormat_Grayscale8)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (x >= width_ ||
+        y >= height_ ||
+        z >= depth_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const void* p = image_.GetConstRow(y + height_ * (depth_ - 1 - z));
+    return reinterpret_cast<const uint8_t*>(p) [x];
+  }
+
+
+  uint16_t ImageBuffer3D::GetVoxelGrayscale16(unsigned int x,
+                                              unsigned int y,
+                                              unsigned int z) const
+  {
+    if (format_ != Orthanc::PixelFormat_Grayscale16)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (x >= width_ ||
+        y >= height_ ||
+        z >= depth_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    const void* p = image_.GetConstRow(y + height_ * (depth_ - 1 - z));
+    return reinterpret_cast<const uint16_t*>(p) [x];
+  }
+
+
+  Vector ImageBuffer3D::GetCoordinates(float x,
+                                       float y,
+                                       float z) const
+  {
+    Vector ps = GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
+
+    const CoordinateSystem3D& axial = GetAxialGeometry();
+    
+    Vector origin = (axial.MapSliceToWorldCoordinates(-0.5 * ps[0], -0.5 * ps[1]) -
+                     0.5 * ps[2] * axial.GetNormal());
+
+    return (origin +
+            axial.GetAxisX() * ps[0] * x * static_cast<double>(GetWidth()) +
+            axial.GetAxisY() * ps[1] * y * static_cast<double>(GetHeight()) +
+            axial.GetNormal() * ps[2] * z * static_cast<double>(GetDepth()));
+  }
 }
--- a/Framework/Volumes/ImageBuffer3D.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Volumes/ImageBuffer3D.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,67 +21,84 @@
 
 #pragma once
 
-#include "../Enumerations.h"
-#include "../Toolbox/IThreadSafety.h"
-#include "../Toolbox/SliceGeometry.h"
+#include "../StoneEnumerations.h"
+#include "../Layers/RenderStyle.h"
+#include "../Toolbox/CoordinateSystem3D.h"
+#include "../Toolbox/DicomFrameConverter.h"
 #include "../Toolbox/ParallelSlices.h"
 
-#include "../../Resources/Orthanc/Core/Images/Image.h"
-
-#include <boost/thread/shared_mutex.hpp>
-
-#if defined(_WIN32)
-#  include <boost/thread/win32/mutex.hpp>
-#endif
+#include <Core/Images/Image.h>
 
 namespace OrthancStone
 {
-  class ImageBuffer3D : public IThreadSafe
+  class ImageBuffer3D : public boost::noncopyable
   {
   private:
-    typedef boost::shared_mutex          Mutex;
-    typedef boost::unique_lock<Mutex>    WriteLock;
-    typedef boost::shared_lock<Mutex>    ReadLock;
-
-    Mutex                  mutex_;
-    SliceGeometry          axialGeometry_;
+    CoordinateSystem3D     axialGeometry_;
     Vector                 voxelDimensions_;
     Orthanc::Image         image_;
     Orthanc::PixelFormat   format_;
     unsigned int           width_;
     unsigned int           height_;
     unsigned int           depth_;
+    bool                   computeRange_;
+    bool                   hasRange_;
+    float                  minValue_;
+    float                  maxValue_;
+
+    void ExtendImageRange(const Orthanc::ImageAccessor& slice);
 
     Orthanc::ImageAccessor GetAxialSliceAccessor(unsigned int slice,
-                                                 bool readOnly);
+                                                 bool readOnly) const;
 
     Orthanc::ImageAccessor GetCoronalSliceAccessor(unsigned int slice,
-                                                   bool readOnly);
+                                                   bool readOnly) const;
 
     Orthanc::Image*  ExtractSagittalSlice(unsigned int slice) const;
 
+    template <typename T>
+    T GetPixelUnchecked(unsigned int x,
+                        unsigned int y,
+                        unsigned int z) const
+    {
+      const uint8_t* buffer = reinterpret_cast<const uint8_t*>(image_.GetConstBuffer());
+      const uint8_t* row = buffer + (y + height_ * (depth_ - 1 - z)) * image_.GetPitch();
+      return reinterpret_cast<const T*>(row) [x];
+    }
+
   public:
     ImageBuffer3D(Orthanc::PixelFormat format,
                   unsigned int width,
                   unsigned int height,
-                  unsigned int depth);
+                  unsigned int depth,
+                  bool computeRange);
 
     void Clear();
 
     // Set the geometry of the first axial slice (i.e. the one whose
     // depth == 0)
-    void SetAxialGeometry(const SliceGeometry& geometry);
+    void SetAxialGeometry(const CoordinateSystem3D& geometry);
+
+    const CoordinateSystem3D& GetAxialGeometry() const
+    {
+      return axialGeometry_;
+    }
 
     void SetVoxelDimensions(double x,
                             double y,
                             double z);
 
-    Vector GetVoxelDimensions(VolumeProjection projection);
+    Vector GetVoxelDimensions(VolumeProjection projection) const;
 
     void GetSliceSize(unsigned int& width,
                       unsigned int& height,
                       VolumeProjection projection);
 
+    const Orthanc::ImageAccessor& GetInternalImage() const
+    {
+      return image_;
+    }
+
     unsigned int GetWidth() const
     {
       return width_;
@@ -102,18 +119,60 @@
       return format_;
     }
 
-    ParallelSlices* GetGeometry(VolumeProjection projection);
+    ParallelSlices* GetGeometry(VolumeProjection projection) const;
     
+    uint64_t GetEstimatedMemorySize() const;
+
+    bool GetRange(float& minValue,
+                  float& maxValue) const;
+
+    bool FitWindowingToRange(RenderStyle& style,
+                             const DicomFrameConverter& converter) const;
+
+    uint8_t GetVoxelGrayscale8Unchecked(unsigned int x,
+                                        unsigned int y,
+                                        unsigned int z) const
+    {
+      return GetPixelUnchecked<uint8_t>(x, y, z);
+    }
+
+    uint16_t GetVoxelGrayscale16Unchecked(unsigned int x,
+                                          unsigned int y,
+                                          unsigned int z) const
+    {
+      return GetPixelUnchecked<uint16_t>(x, y, z);
+    }
+
+    int16_t GetVoxelSignedGrayscale16Unchecked(unsigned int x,
+                                               unsigned int y,
+                                               unsigned int z) const
+    {
+      return GetPixelUnchecked<int16_t>(x, y, z);
+    }
+
+    uint8_t GetVoxelGrayscale8(unsigned int x,
+                               unsigned int y,
+                               unsigned int z) const;
+
+    uint16_t GetVoxelGrayscale16(unsigned int x,
+                                 unsigned int y,
+                                 unsigned int z) 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;
+
 
     class SliceReader : public boost::noncopyable
     {
     private:
-      ReadLock                       lock_;
       Orthanc::ImageAccessor         accessor_;
       std::auto_ptr<Orthanc::Image>  sagittal_;  // Unused for axial and coronal
 
     public:
-      SliceReader(ImageBuffer3D& that,
+      SliceReader(const ImageBuffer3D& that,
                   VolumeProjection projection,
                   unsigned int slice);
 
@@ -127,7 +186,8 @@
     class SliceWriter : public boost::noncopyable
     {
     private:
-      WriteLock                      lock_;
+      ImageBuffer3D&                 that_;
+      bool                           modified_;
       Orthanc::ImageAccessor         accessor_;
       std::auto_ptr<Orthanc::Image>  sagittal_;  // Unused for axial and coronal
 
@@ -143,8 +203,14 @@
         Flush();
       }
 
+      const Orthanc::ImageAccessor& GetAccessor() const
+      {
+        return accessor_;
+      }
+
       Orthanc::ImageAccessor& GetAccessor()
       {
+        modified_ = true;
         return accessor_;
       }
     };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/SlicedVolumeBase.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,51 @@
+/**
+ * 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 "SlicedVolumeBase.h"
+
+namespace OrthancStone
+{
+  void SlicedVolumeBase::NotifyGeometryReady()
+  {
+    observers_.Apply(*this, &IObserver::NotifyGeometryReady);
+  }
+      
+  void SlicedVolumeBase::NotifyGeometryError()
+  {
+    observers_.Apply(*this, &IObserver::NotifyGeometryError);
+  }
+    
+  void SlicedVolumeBase::NotifyContentChange()
+  {
+    observers_.Apply(*this, &IObserver::NotifyContentChange);
+  }
+
+  void SlicedVolumeBase::NotifySliceChange(const size_t& sliceIndex,
+                                           const Slice& slice)
+  {
+    observers_.Apply(*this, &IObserver::NotifySliceChange, sliceIndex, slice);
+  }
+
+  void SlicedVolumeBase::NotifyVolumeReady()
+  {
+    observers_.Apply(*this, &IObserver::NotifyVolumeReady);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/SlicedVolumeBase.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,54 @@
+/**
+ * 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 "ISlicedVolume.h"
+#include "../Toolbox/ObserversRegistry.h"
+
+namespace OrthancStone
+{
+  class SlicedVolumeBase : public ISlicedVolume
+  {
+  private:
+    typedef ObserversRegistry<ISlicedVolume, IObserver>  Observers;
+
+    Observers  observers_;
+
+  protected:
+    virtual void NotifyGeometryReady();
+      
+    virtual void NotifyGeometryError();
+    
+    virtual void NotifyContentChange();
+
+    virtual void NotifySliceChange(const size_t& sliceIndex,
+                                   const Slice& slice);
+
+    virtual void NotifyVolumeReady();
+
+  public:
+    virtual void Register(IObserver& observer)
+    {
+      observers_.Register(observer);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/StructureSetLoader.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,179 @@
+/**
+ * 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 "StructureSetLoader.h"
+
+#include "../Toolbox/MessagingToolbox.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class StructureSetLoader::Operation : public Orthanc::IDynamicObject
+  {
+  public:
+    enum Type
+    {
+      Type_LoadStructureSet,
+      Type_LookupSopInstanceUid,
+      Type_LoadReferencedSlice
+    };
+    
+  private:
+    Type         type_;
+    std::string  value_;
+
+  public:
+    Operation(Type type,
+              const std::string& value) :
+      type_(type),
+      value_(value)
+    {
+    }
+
+    Type GetType() const
+    {
+      return type_;
+    }
+
+    const std::string& GetIdentifier() const
+    {
+      return value_;
+    }
+  };
+
+
+  void StructureSetLoader::NotifyError(const std::string& uri,
+                                       Orthanc::IDynamicObject* payload)
+  {
+    // TODO
+  }
+
+  
+  void StructureSetLoader::NotifySuccess(const std::string& uri,
+                                         const void* answer,
+                                         size_t answerSize,
+                                         Orthanc::IDynamicObject* payload)
+  {
+    std::auto_ptr<Operation> op(dynamic_cast<Operation*>(payload));
+
+    switch (op->GetType())
+    {
+      case Operation::Type_LoadStructureSet:
+      {
+        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
+        structureSet_.reset(new 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_.SchedulePostRequest(*this, "/tools/lookup", *it,
+                                       new Operation(Operation::Type_LookupSopInstanceUid, *it));
+        }
+        
+        VolumeLoaderBase::NotifyGeometryReady();
+
+        break;
+      }
+        
+      case Operation::Type_LookupSopInstanceUid:
+      {
+        Json::Value lookup;
+        
+        if (MessagingToolbox::ParseJson(lookup, answer, answerSize))
+        {
+          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_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags",
+                                      new Operation(Operation::Type_LoadReferencedSlice, instance));
+        }
+        else
+        {
+          // TODO
+        }
+        
+        break;
+      }
+
+      case Operation::Type_LoadReferencedSlice:
+      {
+        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
+
+        Orthanc::DicomMap slice;
+        MessagingToolbox::ConvertDataset(slice, dataset);
+        structureSet_->AddReferencedSlice(slice);
+
+        VolumeLoaderBase::NotifyContentChange();
+
+        break;
+      }
+      
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  } 
+
+  
+  StructureSetLoader::StructureSetLoader(IWebService& orthanc) :
+    orthanc_(orthanc)
+  {
+  }
+  
+
+  void StructureSetLoader::ScheduleLoadInstance(const std::string& instance)
+  {
+    if (structureSet_.get() != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050";
+      orthanc_.ScheduleGetRequest(*this, uri, new Operation(Operation::Type_LoadStructureSet, instance));
+    }
+  }
+
+
+  DicomStructureSet& StructureSetLoader::GetStructureSet()
+  {
+    if (structureSet_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *structureSet_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/StructureSetLoader.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,60 @@
+/**
+ * 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 "../Toolbox/DicomStructureSet.h"
+#include "../Toolbox/IWebService.h"
+#include "VolumeLoaderBase.h"
+
+namespace OrthancStone
+{
+  class StructureSetLoader :
+    public VolumeLoaderBase,
+    private IWebService::ICallback
+  {
+  private:
+    class Operation;
+    
+    virtual void NotifyError(const std::string& uri,
+                             Orthanc::IDynamicObject* payload);
+
+    virtual void NotifySuccess(const std::string& uri,
+                               const void* answer,
+                               size_t answerSize,
+                               Orthanc::IDynamicObject* payload);
+
+    IWebService&                      orthanc_;
+    std::auto_ptr<DicomStructureSet>  structureSet_;
+
+  public:
+    StructureSetLoader(IWebService& orthanc);
+
+    void ScheduleLoadInstance(const std::string& instance);
+
+    bool HasStructureSet() const
+    {
+      return structureSet_.get() != NULL;
+    }
+
+    DicomStructureSet& GetStructureSet();
+  };
+}
--- a/Framework/Volumes/VolumeImage.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,401 +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 "VolumeImage.h"
-
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../Layers/FrameRenderer.h"
-
-namespace OrthancStone
-{
-  void VolumeImage::StoreUpdateTime()
-  {
-    lastUpdate_ = MessagingToolbox::Timestamp();
-  }
-
-
-  void VolumeImage::NotifyChange(bool force)
-  {
-    bool go = false;
-
-    if (force)
-    {
-      go = true;
-    }
-    else
-    {
-      // Don't notify the observers more than 5 times per second
-      MessagingToolbox::Timestamp now;
-      go = (now.GetMillisecondsSince(lastUpdate_) > 200);
-    }
-
-    if (go)
-    {
-      StoreUpdateTime();
-      observers_.NotifyChange(this);
-    }
-  }
-
-
-  void VolumeImage::LoadThread(VolumeImage* that)
-  {
-    while (that->continue_)
-    {
-      bool complete = false;
-      bool done = that->policy_->DownloadStep(complete);
-
-      if (complete)
-      {
-        that->loadingComplete_ = true;
-      }
-
-      if (done)
-      {
-        break;
-      }
-      else
-      {
-        that->NotifyChange(false);
-      }
-    }
-
-    that->NotifyChange(true);
-  }
-
-
-  VolumeImage::VolumeImage(ISeriesLoader* loader) :   // Takes ownership
-    loader_(loader),
-    threads_(1),
-    started_(false),
-    continue_(false),
-    loadingComplete_(false)
-  {
-    if (loader == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    const size_t depth = loader_->GetGeometry().GetSliceCount();
-      
-    if (depth < 2)
-    {
-      // Empty or flat series
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-    }
-
-    // TODO Check pixel spacing, slice thickness, and windowing are
-    // constant across slices
-    referenceDataset_.reset(loader->DownloadDicom(0));
-
-    double spacingZ;
-
-    {
-      // Project the origin of the first and last slices onto the normal
-      const SliceGeometry& s1 = loader_->GetGeometry().GetSlice(0);
-      const SliceGeometry& s2 = loader_->GetGeometry().GetSlice(depth - 1);
-      const Vector& normal = loader_->GetGeometry().GetNormal();
-
-      double p1 = boost::numeric::ublas::inner_prod(s1.GetOrigin(), normal);
-      double p2 = boost::numeric::ublas::inner_prod(s2.GetOrigin(), normal);
-
-      spacingZ = fabs(p2 - p1) / static_cast<double>(depth);
-
-      // TODO Check that all slices are evenly distributed
-    }      
-
-    buffer_.reset(new ImageBuffer3D(loader_->GetPixelFormat(), 
-                                    loader_->GetWidth(), 
-                                    loader_->GetHeight(),
-                                    depth));
-    buffer_->Clear();
-    buffer_->SetAxialGeometry(loader_->GetGeometry().GetSlice(0));
-
-    double spacingX, spacingY;
-    GeometryToolbox::GetPixelSpacing(spacingX, spacingY, *referenceDataset_);
-    buffer_->SetVoxelDimensions(spacingX, spacingY, spacingZ);
-
-    // These 3 values are only used to speed up the LayerFactory
-    axialGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Axial));
-    coronalGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Coronal));
-    sagittalGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Sagittal));
-  }
-
-
-  VolumeImage::~VolumeImage()
-  {
-    Stop();
-      
-    for (size_t i = 0; i < threads_.size(); i++)
-    {
-      if (threads_[i] != NULL)
-      {
-        delete threads_[i];
-      }
-    }
-  }
-
-
-  void VolumeImage::SetDownloadPolicy(IDownloadPolicy* policy)   // Takes ownership
-  {
-    if (started_)
-    {
-      LOG(ERROR) << "Cannot change the number of threads after a call to Start()";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    policy_.reset(policy);
-  }
-
-
-  void VolumeImage::SetThreadCount(size_t count)
-  {
-    if (started_)
-    {
-      LOG(ERROR) << "Cannot change the number of threads after a call to Start()";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    if (count <= 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    threads_.resize(count);
-  }
-
-
-  void VolumeImage::Register(IChangeObserver& observer)
-  {
-    observers_.Register(observer);
-  }
-
-
-  void VolumeImage::Unregister(IChangeObserver& observer)
-  {
-    observers_.Unregister(observer);
-  }
-
-
-  void VolumeImage::Start()
-  {
-    if (started_)
-    {
-      LOG(ERROR) << "Cannot call Start() twice";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    started_ = true;
-    StoreUpdateTime();
-
-    if (policy_.get() != NULL &&
-        threads_.size() > 0)
-    {
-      continue_ = true;
-      policy_->Initialize(*buffer_, *loader_);
-
-      for (size_t i = 0; i < threads_.size(); i++)
-      {
-        assert(threads_[i] == NULL);
-        threads_[i] = new boost::thread(LoadThread, this);
-      }
-    }
-  }
-
-
-  void VolumeImage::Stop()
-  {
-    if (!started_)
-    {
-      LOG(ERROR) << "Cannot call Stop() without calling Start() beforehand";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    if (continue_)
-    {
-      continue_ = false;
-
-      for (size_t i = 0; i < threads_.size(); i++)
-      {
-        if (threads_[i]->joinable())
-        {
-          threads_[i]->join();
-        }
-      }
-
-      assert(policy_.get() != NULL);
-      policy_->Finalize();
-    }
-  }
-
-
-  ParallelSlices* VolumeImage::GetGeometry(VolumeProjection projection,
-                                           bool reverse)
-  {
-    std::auto_ptr<ParallelSlices> slices(buffer_->GetGeometry(projection));
-
-    if (reverse)
-    {
-      return slices->Reverse();
-    }
-    else
-    {
-      return slices.release();
-    }
-  }
-
-
-  bool VolumeImage::DetectProjection(VolumeProjection& projection,
-                                     bool& reverse,
-                                     const SliceGeometry& viewportSlice)
-  {
-    if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), axialGeometry_->GetNormal()))
-    {
-      projection = VolumeProjection_Axial;
-      return true;
-    }
-    else if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), sagittalGeometry_->GetNormal()))
-    {
-      projection = VolumeProjection_Sagittal;
-      return true;
-    }
-    else if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), coronalGeometry_->GetNormal()))
-    {
-      projection = VolumeProjection_Coronal;
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  const ParallelSlices& VolumeImage::GetGeometryInternal(VolumeProjection projection)
-  {
-    switch (projection)
-    {
-      case VolumeProjection_Axial:
-        return *axialGeometry_;
-
-      case VolumeProjection_Sagittal:
-        return *sagittalGeometry_;
-
-      case VolumeProjection_Coronal:
-        return *coronalGeometry_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  bool VolumeImage::LayerFactory::GetExtent(double& x1,
-                                            double& y1,
-                                            double& x2,
-                                            double& y2,
-                                            const SliceGeometry& viewportSlice)
-  {
-    VolumeProjection projection;
-    bool reverse;
-
-    if (that_.buffer_->GetWidth() == 0 ||
-        that_.buffer_->GetHeight() == 0 ||
-        that_.buffer_->GetDepth() == 0 ||
-        !that_.DetectProjection(projection, reverse, viewportSlice))
-    {
-      return false;
-    }
-    else
-    {
-      Vector spacing = that_.GetVoxelDimensions(projection);
-
-      unsigned int width, height;
-      that_.buffer_->GetSliceSize(width, height, projection);
-
-      // As the slices of the volumic image are arranged in a box,
-      // we only consider one single reference slice (the one with index 0).
-      const SliceGeometry& volumeSlice = that_.GetGeometryInternal(projection).GetSlice(0);
-
-      return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, 
-                                               viewportSlice, volumeSlice,
-                                               width, height,
-                                               spacing[0], spacing[1]);
-    }
-  }
-
-
-  ILayerRenderer* VolumeImage::LayerFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice)
-  {
-    VolumeProjection projection;
-    bool reverse;
-    
-    if (that_.buffer_->GetWidth() == 0 ||
-        that_.buffer_->GetHeight() == 0 ||
-        that_.buffer_->GetDepth() == 0 ||
-        !that_.DetectProjection(projection, reverse, viewportSlice))
-    {
-      return NULL;
-    }
-
-    const ParallelSlices& geometry = that_.GetGeometryInternal(projection);
-
-    size_t closest;
-    double distance;
-
-    const Vector spacing = that_.GetVoxelDimensions(projection);
-    const double sliceThickness = spacing[2];
-
-    if (geometry.ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin()) &&
-        distance <= sliceThickness / 2.0)
-    {
-      bool isFullQuality;
-
-      if (projection == VolumeProjection_Axial &&
-          that_.policy_.get() != NULL)
-      {
-        isFullQuality = that_.policy_->IsFullQualityAxial(closest);
-      }
-      else
-      {
-        isFullQuality = that_.IsLoadingComplete();
-      }
-
-      std::auto_ptr<Orthanc::Image> frame;
-      SliceGeometry frameSlice = geometry.GetSlice(closest);
-
-      {
-        ImageBuffer3D::SliceReader reader(*that_.buffer_, projection, closest);
-        frame.reset(Orthanc::Image::Clone(reader.GetAccessor()));
-      }
-
-      return FrameRenderer::CreateRenderer(frame.release(), 
-                                           viewportSlice, 
-                                           frameSlice, 
-                                           *that_.referenceDataset_, 
-                                           spacing[0], spacing[1],
-                                           isFullQuality);
-    }
-    else
-    {
-      return NULL;
-    }
-  }
-}
--- a/Framework/Volumes/VolumeImage.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +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 "ISliceableVolume.h"
-#include "ImageBuffer3D.h"
-#include "../Toolbox/ISeriesLoader.h"
-#include "../Toolbox/MessagingToolbox.h"
-#include "../Toolbox/ObserversRegistry.h"
-#include "../Layers/ILayerRendererFactory.h"
-
-#include <boost/thread.hpp>
-
-namespace OrthancStone
-{
-  class VolumeImage : public ISliceableVolume
-  {
-  public:
-    class IDownloadPolicy : public IThreadSafe
-    {
-    public:
-      virtual void Initialize(ImageBuffer3D& buffer,
-                              ISeriesLoader& loader) = 0;
-
-      virtual void Finalize() = 0;
-
-      // Must return "true" if the thread has completed its task. Pay
-      // attention that this method can be invoked concurrently by
-      // several download threads.
-      virtual bool DownloadStep(bool& complete) = 0;
-
-      virtual bool IsFullQualityAxial(size_t slice) = 0;
-    };
-
-
-  private:
-    std::auto_ptr<ISeriesLoader>                  loader_;
-    std::auto_ptr<ImageBuffer3D>                  buffer_;
-    std::vector<boost::thread*>                   threads_;
-    bool                                          started_;
-    bool                                          continue_;
-    ObserversRegistry<ISliceableVolume>           observers_;
-    bool                                          loadingComplete_;
-    MessagingToolbox::Timestamp                   lastUpdate_;
-    std::auto_ptr<OrthancPlugins::IDicomDataset>  referenceDataset_;
-    std::auto_ptr<IDownloadPolicy>                policy_;
-    
-    std::auto_ptr<ParallelSlices>        axialGeometry_;
-    std::auto_ptr<ParallelSlices>        coronalGeometry_;
-    std::auto_ptr<ParallelSlices>        sagittalGeometry_;
-
-    void StoreUpdateTime();
-
-    void NotifyChange(bool force);
-
-    static void LoadThread(VolumeImage* that);
-
-    bool DetectProjection(VolumeProjection& projection,
-                          bool& reverse,
-                          const SliceGeometry& viewportSlice);
-
-    const ParallelSlices& GetGeometryInternal(VolumeProjection projection);
-
-  public:
-    VolumeImage(ISeriesLoader* loader);   // Takes ownership
-
-    virtual ~VolumeImage();
-
-    void SetDownloadPolicy(IDownloadPolicy* policy);   // Takes ownership
-
-    void SetThreadCount(size_t count);
-
-    size_t GetThreadCount() const
-    {
-      return threads_.size();
-    }
-
-    virtual void Register(IChangeObserver& observer);
-
-    virtual void Unregister(IChangeObserver& observer);
-
-    virtual void Start();
-
-    virtual void Stop();
-
-    ParallelSlices* GetGeometry(VolumeProjection projection,
-                                bool reverse);
-
-    Vector GetVoxelDimensions(VolumeProjection projection)
-    {
-      return buffer_->GetVoxelDimensions(projection);
-    }
-
-    bool IsLoadingComplete() const
-    {
-      return loadingComplete_;
-    }
-
-    class LayerFactory : public ILayerRendererFactory
-    {
-    private:
-      VolumeImage&  that_;
-
-    public:
-      LayerFactory(VolumeImage& that) :
-        that_(that)
-      {
-      }
-
-      virtual bool HasSourceVolume() const
-      {
-        return true;
-      }
-
-      virtual ISliceableVolume& GetSourceVolume() const
-      {
-        return that_;
-      }
-
-      virtual bool GetExtent(double& x1,
-                             double& y1,
-                             double& x2,
-                             double& y2,
-                             const SliceGeometry& viewportSlice);
-
-      virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice);    
-    };
-  };
-}
--- a/Framework/Volumes/VolumeImagePolicyBase.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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 "VolumeImagePolicyBase.h"
-
-namespace OrthancStone
-{
-  VolumeImagePolicyBase::VolumeImagePolicyBase() : 
-    buffer_(NULL),
-    loader_(NULL)
-  {
-  }
-
-
-  void VolumeImagePolicyBase::Initialize(ImageBuffer3D& buffer,
-                                         ISeriesLoader& loader)
-  {
-    if (buffer_ != NULL ||
-        loader_ != NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    buffer_ = &buffer;
-    loader_ = &loader;
-
-    InitializeInternal(buffer, loader);
-  }
-
-
-  bool VolumeImagePolicyBase::DownloadStep(bool& complete)
-  {
-    if (buffer_ == NULL ||
-        loader_ == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return DownloadStepInternal(complete, *buffer_, *loader_);                                    
-    }
-  }
-}
--- a/Framework/Volumes/VolumeImagePolicyBase.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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 "VolumeImage.h"
-
-namespace OrthancStone
-{
-  class VolumeImagePolicyBase : public VolumeImage::IDownloadPolicy
-  {
-  private:
-    ImageBuffer3D*  buffer_;
-    ISeriesLoader*  loader_;
-
-  protected:
-    virtual void InitializeInternal(ImageBuffer3D& buffer,
-                                    ISeriesLoader& loader) = 0;
-
-    virtual bool DownloadStepInternal(bool& complete,
-                                      ImageBuffer3D& buffer,
-                                      ISeriesLoader& loader) = 0;
-
-  public:
-    VolumeImagePolicyBase();
-
-    virtual void Initialize(ImageBuffer3D& buffer,
-                            ISeriesLoader& loader);
-
-    virtual void Finalize()
-    {
-    }
-
-    virtual bool DownloadStep(bool& complete);
-  };
-}
--- a/Framework/Volumes/VolumeImageProgressivePolicy.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +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 "VolumeImageProgressivePolicy.h"
-
-#include "../Toolbox/DownloadStack.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-
-namespace OrthancStone
-{
-  class VolumeImageProgressivePolicy::AxialSlicesScheduler
-  {
-  private:
-    size_t         depth_;
-    DownloadStack  stack_;
-
-  public:
-    AxialSlicesScheduler(size_t depth) :
-      depth_(depth),
-      stack_(3 * depth)    // "3" stands for the number of quality levels
-    {
-      assert(depth > 0);
-    }
-
-    void TagFullPriority(int z,
-                         int neighborhood)
-    {
-      DownloadStack::Writer writer(stack_);
-
-      // Also schedule the neighboring slices for download in medium quality
-      for (int offset = neighborhood; offset >= 1; offset--)
-      {
-        writer.SetTopNodePermissive((z + offset) + depth_ * Quality_Medium);
-        writer.SetTopNodePermissive((z - offset) + depth_ * Quality_Medium);
-      }
-
-      writer.SetTopNodePermissive(z + depth_ * Quality_Full);
-    }
-
-    bool LookupSlice(unsigned int& z,
-                     Quality& quality)
-    {
-      unsigned int value;
-      if (stack_.Pop(value))
-      {
-        z = value % depth_;
-
-        switch (value / depth_)
-        {
-          case 0:
-            quality = Quality_Low;
-            break;
-
-          case 1:
-            quality = Quality_Medium;
-            break;
-
-          case 2:
-            quality = Quality_Full;
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  };
-
-
-  bool VolumeImageProgressivePolicy::IsComplete()
-  {
-    boost::mutex::scoped_lock lock(qualityMutex_);
-        
-    for (size_t i = 0; i < axialSlicesQuality_.size(); i++)
-    {
-      if (axialSlicesQuality_[i] != Quality_Full)
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  void VolumeImageProgressivePolicy::InitializeInternal(ImageBuffer3D& buffer,
-                                                        ISeriesLoader& loader)
-  {
-    const size_t depth = loader.GetGeometry().GetSliceCount();
-
-    isJpegAvailable_ = loader.IsJpegAvailable();
-
-    axialSlicesQuality_.clear();
-    axialSlicesQuality_.resize(depth, Quality_None);
-    scheduler_.reset(new AxialSlicesScheduler(depth));
-  }
-
-
-  bool VolumeImageProgressivePolicy::DownloadStepInternal(bool& complete,
-                                                          ImageBuffer3D& buffer,
-                                                          ISeriesLoader& loader)
-  {
-    unsigned int z;
-    Quality quality;
-
-    if (!scheduler_->LookupSlice(z, quality))
-    {
-      // There is no more frame to be downloaded. Before stopping,
-      // each loader thread checks whether all the frames have been
-      // downloaded at maximum quality.
-      complete = IsComplete();
-      return true;
-    }
-
-    if (quality != Quality_Full &&
-        !isJpegAvailable_)
-    {
-      // Cannot fulfill this command, as progressive JPEG download
-      // is unavailable (i.e. the Web viewer plugin is unavailable)
-      return false;
-    }
-
-    std::auto_ptr<Orthanc::ImageAccessor> frame;
-
-    try
-    {
-      switch (quality)
-      {
-        case Quality_Low:
-          frame.reset(loader.DownloadJpegFrame(z, 10));
-          break;
-
-        case Quality_Medium:
-          frame.reset(loader.DownloadJpegFrame(z, 90));
-          break;
-
-        case Quality_Full:
-          frame.reset(loader.DownloadFrame(z));
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-    catch (Orthanc::OrthancException&)
-    {
-      // The Orthanc server cannot decode this instance
-      return false;
-    }
-
-    if (frame.get() != NULL)
-    {
-      boost::mutex::scoped_lock lock(qualityMutex_);
-
-      if (axialSlicesQuality_[z] == Quality_None ||
-          axialSlicesQuality_[z] < quality)
-      {
-        axialSlicesQuality_[z] = quality;
-
-        ImageBuffer3D::SliceWriter writer(buffer, VolumeProjection_Axial, z);
-        Orthanc::ImageProcessing::Convert(writer.GetAccessor(), *frame);
-      }
-    }
-
-    return false;
-  }
-
-
-  bool VolumeImageProgressivePolicy::IsFullQualityAxial(size_t slice)
-  {
-    scheduler_->TagFullPriority(slice, 3);
-
-    {
-      boost::mutex::scoped_lock lock(qualityMutex_);
-      return (axialSlicesQuality_[slice] == Quality_Full);
-    }
-  }
-}
--- a/Framework/Volumes/VolumeImageProgressivePolicy.h	Tue Jan 02 09:51:36 2018 +0100
+++ /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-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 "VolumeImagePolicyBase.h"
-
-namespace OrthancStone
-{
-  class VolumeImageProgressivePolicy : public VolumeImagePolicyBase
-  {
-  private:
-    enum Quality
-    {
-      Quality_Low = 0,
-      Quality_Medium = 1,
-      Quality_Full = 2,
-      Quality_None = 3
-    };
-
-    class AxialSlicesScheduler;
-
-    std::auto_ptr<AxialSlicesScheduler>   scheduler_;
-    boost::mutex                          qualityMutex_;
-    std::vector<Quality>                  axialSlicesQuality_;
-    bool                                  isJpegAvailable_;
-
-    bool IsComplete();
-
-  protected:
-    virtual void InitializeInternal(ImageBuffer3D& buffer,
-                                    ISeriesLoader& loader);
-
-    virtual bool DownloadStepInternal(bool& complete,
-                                      ImageBuffer3D& buffer,
-                                      ISeriesLoader& loader);
-
-  public:
-    virtual bool IsFullQualityAxial(size_t slice);
-  };
-}
--- a/Framework/Volumes/VolumeImageSimplePolicy.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +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 "VolumeImageSimplePolicy.h"
-
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-
-namespace OrthancStone
-{
-  void VolumeImageSimplePolicy::InitializeInternal(ImageBuffer3D& buffer,
-                                                   ISeriesLoader& loader)
-  {
-    boost::mutex::scoped_lock  lock(mutex_);
-
-    const size_t depth = loader.GetGeometry().GetSliceCount();
-    pendingSlices_.clear();
-
-    for (size_t i = 0; i < depth; i++)
-    {
-      pendingSlices_.insert(i);
-    }
-
-    doneSlices_.clear();
-    doneSlices_.resize(depth, false);
-  }
-
-
-  bool VolumeImageSimplePolicy::DownloadStepInternal(bool& complete,
-                                                     ImageBuffer3D& buffer,
-                                                     ISeriesLoader& loader)
-  {
-    size_t slice;
-
-    {
-      boost::mutex::scoped_lock  lock(mutex_);
-      
-      if (pendingSlices_.empty())
-      {
-        return true;
-      }
-      else
-      {
-        slice = *pendingSlices_.begin();
-        pendingSlices_.erase(slice);
-      }
-    }
-
-    std::auto_ptr<Orthanc::ImageAccessor> frame;
-
-    try
-    {
-      frame.reset(loader.DownloadFrame(slice));
-    }
-    catch (Orthanc::OrthancException&)
-    {
-      // The Orthanc server cannot decode this instance
-      return false;
-    }
-
-    if (frame.get() != NULL)
-    {
-      {
-        ImageBuffer3D::SliceWriter writer(buffer, VolumeProjection_Axial, slice);
-        Orthanc::ImageProcessing::Convert(writer.GetAccessor(), *frame);
-      }
-
-      {
-        boost::mutex::scoped_lock  lock(mutex_);
-
-        doneSlices_[slice] = true;
-
-        if (pendingSlices_.empty())
-        {
-          complete = true;
-          return true;
-        }
-      }
-    }
-      
-    return false;
-  }
-
-
-  bool VolumeImageSimplePolicy::IsFullQualityAxial(size_t slice)
-  {
-    boost::mutex::scoped_lock  lock(mutex_);
-    return doneSlices_[slice];
-  }
-}
--- a/Framework/Volumes/VolumeImageSimplePolicy.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +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 "VolumeImagePolicyBase.h"
-
-namespace OrthancStone
-{
-  class VolumeImageSimplePolicy : public VolumeImagePolicyBase
-  {
-  private:
-    boost::mutex       mutex_;
-    std::set<size_t>   pendingSlices_;
-    std::vector<bool>  doneSlices_;
-
-  protected:
-    virtual void InitializeInternal(ImageBuffer3D& buffer,
-                                    ISeriesLoader& loader);
-
-    virtual bool DownloadStepInternal(bool& complete,
-                                      ImageBuffer3D& buffer,
-                                      ISeriesLoader& loader);
-
-  public:
-    virtual bool IsFullQualityAxial(size_t slice);
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeLoaderBase.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,40 @@
+/**
+ * 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 "VolumeLoaderBase.h"
+
+namespace OrthancStone
+{
+  void VolumeLoaderBase::NotifyGeometryReady()
+  {
+    observers_.Apply(*this, &IObserver::NotifyGeometryReady);
+  }
+      
+  void VolumeLoaderBase::NotifyGeometryError()
+  {
+    observers_.Apply(*this, &IObserver::NotifyGeometryError);
+  }
+    
+  void VolumeLoaderBase::NotifyContentChange()
+  {
+    observers_.Apply(*this, &IObserver::NotifyContentChange);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeLoaderBase.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,49 @@
+/**
+ * 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 "IVolumeLoader.h"
+#include "../Toolbox/ObserversRegistry.h"
+
+namespace OrthancStone
+{
+  class VolumeLoaderBase : public IVolumeLoader
+  {
+  private:
+    typedef ObserversRegistry<IVolumeLoader, IObserver>  Observers;
+
+    Observers  observers_;
+
+  protected:
+    virtual void NotifyGeometryReady();
+      
+    virtual void NotifyGeometryError();
+    
+    virtual void NotifyContentChange();
+
+  public:
+    virtual void Register(IObserver& observer)
+    {
+      observers_.Register(observer);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeReslicer.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,818 @@
+/**
+ * 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 "VolumeReslicer.h"
+
+#include "../Toolbox/GeometryToolbox.h"
+#include "../Toolbox/SubvoxelReader.h"
+
+#include <Core/Images/ImageTraits.h>
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+namespace OrthancStone
+{
+  // Anonymous namespace to avoid clashes between compilation modules
+  namespace
+  {
+    enum TransferFunction
+    {
+      TransferFunction_Copy,
+      TransferFunction_Float,
+      TransferFunction_Linear
+    };
+
+
+    template <Orthanc::PixelFormat InputFormat,
+              Orthanc::PixelFormat OutputFormat,
+              ImageInterpolation Interpolation,
+              TransferFunction Function>
+    class PixelShader;
+
+    
+    template <Orthanc::PixelFormat Format>
+    class PixelShader<Format, 
+                      Format, 
+                      ImageInterpolation_Nearest, 
+                      TransferFunction_Copy>
+    {
+    private:
+      typedef SubvoxelReader<Format, ImageInterpolation_Nearest>  VoxelReader;
+      typedef Orthanc::PixelTraits<Format>                        PixelWriter;
+
+      VoxelReader  reader_;
+      
+    public:
+      PixelShader(const ImageBuffer3D& image,
+                  float /* scaling */,
+                  float /* offset */) :
+        reader_(image)
+      {
+      }
+      
+      ORTHANC_FORCE_INLINE
+      void Apply(typename PixelWriter::PixelType* pixel,
+                 float volumeX,
+                 float volumeY,
+                 float volumeZ)
+      {
+        typename VoxelReader::PixelType value;
+
+        if (!reader_.GetValue(value, volumeX, volumeY, volumeZ))
+        {
+          VoxelReader::Traits::SetMinValue(value);
+        }
+
+        *pixel = value;
+      }        
+    };
+
+    
+    template <Orthanc::PixelFormat InputFormat,
+              Orthanc::PixelFormat OutputFormat>
+    class PixelShader<InputFormat, 
+                      OutputFormat, 
+                      ImageInterpolation_Nearest, 
+                      TransferFunction_Copy>
+    {
+    private:
+      typedef SubvoxelReader<InputFormat, ImageInterpolation_Nearest>  VoxelReader;
+      typedef Orthanc::PixelTraits<OutputFormat>                       PixelWriter;
+
+      VoxelReader  reader_;
+      
+    public:
+      PixelShader(const ImageBuffer3D& image,
+                  float /* scaling */,
+                  float /* offset */) :
+        reader_(image)
+      {
+      }
+      
+      ORTHANC_FORCE_INLINE
+      void Apply(typename PixelWriter::PixelType* pixel,
+                 float volumeX,
+                 float volumeY,
+                 float volumeZ)
+      {
+        typename VoxelReader::PixelType value;
+
+        if (!reader_.GetValue(value, volumeX, volumeY, volumeZ))
+        {
+          VoxelReader::Traits::SetMinValue(value);
+        }
+
+        PixelWriter::FloatToPixel(*pixel, VoxelReader::Traits::PixelToFloat(value));
+      }        
+    };
+
+    
+    template <Orthanc::PixelFormat InputFormat,
+              Orthanc::PixelFormat OutputFormat,
+              ImageInterpolation Interpolation>
+    class PixelShader<InputFormat,
+                      OutputFormat,
+                      Interpolation,
+                      TransferFunction_Float>
+    {
+    private:
+      typedef SubvoxelReader<InputFormat, Interpolation>  VoxelReader;
+      typedef Orthanc::PixelTraits<OutputFormat>          PixelWriter;
+
+      VoxelReader  reader_;
+      float        outOfVolume_;
+      
+    public:
+      PixelShader(const ImageBuffer3D& image,
+                  float /* scaling */,
+                  float /* offset */) :
+        reader_(image),
+        outOfVolume_(static_cast<float>(std::numeric_limits<typename VoxelReader::PixelType>::min()))
+      {
+      }
+      
+      ORTHANC_FORCE_INLINE
+      void Apply(typename PixelWriter::PixelType* pixel,
+                 float volumeX,
+                 float volumeY,
+                 float volumeZ)
+      {
+        float value;
+
+        if (!reader_.GetFloatValue(value, volumeX, volumeY, volumeZ))
+        {
+          value = outOfVolume_;
+        }
+
+        PixelWriter::FloatToPixel(*pixel, value);
+      }
+    };
+
+    
+   template <Orthanc::PixelFormat InputFormat,
+             Orthanc::PixelFormat OutputFormat,
+             ImageInterpolation Interpolation>
+    class PixelShader<InputFormat,
+                      OutputFormat,
+                      Interpolation,
+                      TransferFunction_Linear>
+    {
+    private:
+      typedef SubvoxelReader<InputFormat, Interpolation>  VoxelReader;
+      typedef Orthanc::PixelTraits<OutputFormat>          PixelWriter;
+
+      VoxelReader  reader_;
+      float        scaling_;
+      float        offset_;
+      float        outOfVolume_;
+      
+    public:
+      PixelShader(const ImageBuffer3D& image,
+                  float scaling,
+                  float offset) :
+        reader_(image),
+        scaling_(scaling),
+        offset_(offset),
+        outOfVolume_(static_cast<float>(std::numeric_limits<typename VoxelReader::PixelType>::min()))
+      {
+      }
+      
+      ORTHANC_FORCE_INLINE
+      void Apply(typename PixelWriter::PixelType* pixel,
+                 float volumeX,
+                 float volumeY,
+                 float volumeZ)
+      {
+        float value;
+
+        if (reader_.GetFloatValue(value, volumeX, volumeY, volumeZ))
+        {
+          value = scaling_ * value + offset_;
+        }
+        else
+        {
+          value = outOfVolume_;
+        }
+
+        PixelWriter::FloatToPixel(*pixel, value);
+      }        
+    };
+
+
+
+    class FastRowIterator : public boost::noncopyable
+    {
+    private:
+      float  position_[3];
+      float  offset_[3];
+      
+    public:
+      FastRowIterator(const Orthanc::ImageAccessor& slice,
+                      const Extent2D& extent,
+                      const CoordinateSystem3D& plane,
+                      const OrientedBoundingBox& box,
+                      unsigned int y)
+      {
+        const double width = static_cast<double>(slice.GetWidth());
+        const double height = static_cast<double>(slice.GetHeight());
+        assert(y < height);
+
+        Vector q1 = plane.MapSliceToWorldCoordinates
+          (extent.GetX1() + extent.GetWidth() * static_cast<double>(0) / static_cast<double>(width + 1),
+           extent.GetY1() + extent.GetHeight() * static_cast<double>(y) / static_cast<double>(height + 1));
+
+        Vector q2 = plane.MapSliceToWorldCoordinates
+          (extent.GetX1() + extent.GetWidth() * static_cast<double>(width - 1) / static_cast<double>(width + 1),
+           extent.GetY1() + extent.GetHeight() * static_cast<double>(y) / static_cast<double>(height + 1));
+
+        Vector r1, r2;
+        box.ToInternalCoordinates(r1, q1);
+        box.ToInternalCoordinates(r2, q2);
+
+        position_[0] = static_cast<float>(r1[0]);
+        position_[1] = static_cast<float>(r1[1]);
+        position_[2] = static_cast<float>(r1[2]);
+        
+        Vector tmp = (r2 - r1) / static_cast<double>(width - 1);
+        offset_[0] = static_cast<float>(tmp[0]);
+        offset_[1] = static_cast<float>(tmp[1]);
+        offset_[2] = static_cast<float>(tmp[2]);
+      }
+
+      ORTHANC_FORCE_INLINE
+      void Next()
+      {
+        position_[0] += offset_[0];
+        position_[1] += offset_[1];
+        position_[2] += offset_[2];
+      }
+
+      ORTHANC_FORCE_INLINE
+      void GetVolumeCoordinates(float& x,
+                                float& y,
+                                float& z) const
+      {
+        x = position_[0];
+        y = position_[1];
+        z = position_[2];
+      }
+    };
+
+
+    class SlowRowIterator : public boost::noncopyable
+    {
+    private:
+      const Orthanc::ImageAccessor&  slice_;
+      const Extent2D&                extent_;
+      const CoordinateSystem3D&      plane_;
+      const OrientedBoundingBox&     box_;
+      unsigned int                   x_;
+      unsigned int                   y_;
+      
+    public:
+      SlowRowIterator(const Orthanc::ImageAccessor& slice,
+                      const Extent2D& extent,
+                      const CoordinateSystem3D& plane,
+                      const OrientedBoundingBox& box,
+                      unsigned int y) :
+        slice_(slice),
+        extent_(extent),
+        plane_(plane),
+        box_(box),
+        x_(0),
+        y_(y)
+      {
+        assert(y_ < slice_.GetHeight());
+      }
+
+      void Next()
+      {
+        x_++;
+      }
+
+      void GetVolumeCoordinates(float& x,
+                                float& y,
+                                float& z) const
+      {
+        assert(x_ < slice_.GetWidth());
+        
+        const double width = static_cast<double>(slice_.GetWidth());
+        const double height = static_cast<double>(slice_.GetHeight());
+        
+        Vector q = plane_.MapSliceToWorldCoordinates
+          (extent_.GetX1() + extent_.GetWidth() * static_cast<double>(x_) / (width + 1.0),
+           extent_.GetY1() + extent_.GetHeight() * static_cast<double>(y_) / (height + 1.0));
+
+        Vector r;
+        box_.ToInternalCoordinates(r, q);
+
+        x = static_cast<float>(r[0]);
+        y = static_cast<float>(r[1]);
+        z = static_cast<float>(r[2]);
+      }
+    };
+
+
+    template <typename RowIterator,
+              Orthanc::PixelFormat InputFormat,
+              Orthanc::PixelFormat OutputFormat,
+              ImageInterpolation Interpolation,
+              TransferFunction Function>
+    static void ProcessImage(Orthanc::ImageAccessor& slice,
+                             const Extent2D& extent,
+                             const ImageBuffer3D& source,
+                             const CoordinateSystem3D& plane,
+                             const OrientedBoundingBox& box,
+                             float scaling,
+                             float offset)
+    {
+      typedef PixelShader<InputFormat, OutputFormat, Interpolation, Function>   Shader;
+
+      const unsigned int outputWidth = slice.GetWidth();
+      const unsigned int outputHeight = slice.GetHeight();
+
+      const float sourceWidth = static_cast<float>(source.GetWidth());
+      const float sourceHeight = static_cast<float>(source.GetHeight());
+      const float sourceDepth = static_cast<float>(source.GetDepth());
+
+      Shader shader(source, scaling, offset);
+
+      for (unsigned int y = 0; y < outputHeight; y++)
+      {
+        typedef typename Orthanc::ImageTraits<OutputFormat>::PixelType PixelType;
+        PixelType* p = reinterpret_cast<PixelType*>(slice.GetRow(y));
+
+        RowIterator it(slice, extent, plane, box, y);
+
+        for (unsigned int x = 0; x < outputWidth; x++, p++)
+        {
+          float volumeX, volumeY, volumeZ;
+          it.GetVolumeCoordinates(volumeX, volumeY, volumeZ);
+
+          shader.Apply(p, 
+                       volumeX * sourceWidth, 
+                       volumeY * sourceHeight, 
+                       volumeZ * sourceDepth);
+          it.Next();
+        }
+      }
+    }
+
+
+    template <typename RowIterator,
+              Orthanc::PixelFormat InputFormat,
+              Orthanc::PixelFormat OutputFormat>
+    static void ProcessImage(Orthanc::ImageAccessor& slice,
+                             const Extent2D& extent,
+                             const ImageBuffer3D& source,
+                             const CoordinateSystem3D& plane,
+                             const OrientedBoundingBox& box,
+                             ImageInterpolation interpolation,
+                             bool hasLinearFunction,
+                             float scaling,
+                             float offset)
+    {
+      if (hasLinearFunction)
+      {
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ProcessImage<RowIterator, InputFormat, OutputFormat,
+                         ImageInterpolation_Nearest, TransferFunction_Linear>
+              (slice, extent, source, plane, box, scaling, offset);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ProcessImage<RowIterator, InputFormat, OutputFormat,
+                         ImageInterpolation_Bilinear, TransferFunction_Linear>
+              (slice, extent, source, plane, box, scaling, offset);
+            break;
+
+          case ImageInterpolation_Trilinear:
+            ProcessImage<RowIterator, InputFormat, OutputFormat,
+                         ImageInterpolation_Trilinear, TransferFunction_Linear>
+              (slice, extent, source, plane, box, scaling, offset);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }        
+      }
+      else
+      {
+        switch (interpolation)
+        {
+          case ImageInterpolation_Nearest:
+            ProcessImage<RowIterator, InputFormat, OutputFormat,
+                         ImageInterpolation_Nearest, TransferFunction_Copy>
+              (slice, extent, source, plane, box, 0, 0);
+            break;
+
+          case ImageInterpolation_Bilinear:
+            ProcessImage<RowIterator, InputFormat, OutputFormat,
+                         ImageInterpolation_Bilinear, TransferFunction_Float>
+              (slice, extent, source, plane, box, 0, 0);
+            break;
+
+          case ImageInterpolation_Trilinear:
+            ProcessImage<RowIterator, InputFormat, OutputFormat,
+                         ImageInterpolation_Trilinear, TransferFunction_Float>
+              (slice, extent, source, plane, box, 0, 0);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }        
+      }
+    }
+    
+    
+    template <typename RowIterator>
+    static void ProcessImage(Orthanc::ImageAccessor& slice,
+                             const Extent2D& extent,
+                             const ImageBuffer3D& source,
+                             const CoordinateSystem3D& plane,
+                             const OrientedBoundingBox& box,
+                             ImageInterpolation interpolation,
+                             bool hasLinearFunction,
+                             float scaling,
+                             float offset)
+    {
+      if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
+          slice.GetFormat() == Orthanc::PixelFormat_Grayscale8)
+      {
+        ProcessImage<RowIterator,
+                     Orthanc::PixelFormat_Grayscale16,
+                     Orthanc::PixelFormat_Grayscale8>
+          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
+      }
+      else if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
+               slice.GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        ProcessImage<RowIterator,
+                     Orthanc::PixelFormat_Grayscale16,
+                     Orthanc::PixelFormat_Grayscale16>
+          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
+      }
+      else if (source.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16 &&
+               slice.GetFormat() == Orthanc::PixelFormat_BGRA32)
+      {
+        ProcessImage<RowIterator,
+                     Orthanc::PixelFormat_SignedGrayscale16,
+                     Orthanc::PixelFormat_BGRA32>
+          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
+      }
+      else if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 &&
+               slice.GetFormat() == Orthanc::PixelFormat_BGRA32)
+      {
+        ProcessImage<RowIterator,
+                     Orthanc::PixelFormat_Grayscale16,
+                     Orthanc::PixelFormat_BGRA32>
+          (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+    
+    
+
+  void VolumeReslicer::CheckIterators(const ImageBuffer3D& source,
+                                      const CoordinateSystem3D& plane,
+                                      const OrientedBoundingBox& box) const
+  {
+    for (unsigned int y = 0; y < slice_->GetHeight(); y++)
+    {
+      FastRowIterator fast(*slice_, extent_, plane, box, y);
+      SlowRowIterator slow(*slice_, extent_, plane, box, y);
+
+      for (unsigned int x = 0; x < slice_->GetWidth(); x++)
+      {
+        float px, py, pz;
+        fast.GetVolumeCoordinates(px, py, pz);
+
+        float qx, qy, qz;
+        slow.GetVolumeCoordinates(qx, qy, qz);
+
+        Vector d;
+        LinearAlgebra::AssignVector(d, px - qx, py - qy, pz - qz);
+        double norm = boost::numeric::ublas::norm_2(d);
+        if (norm > 0.0001)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+          
+        fast.Next();
+        slow.Next();
+      }
+    }
+  }
+
+    
+  void VolumeReslicer::Reset()
+  {
+    success_ = false;
+    extent_.Reset();
+    slice_.reset(NULL);
+  }
+
+
+  float VolumeReslicer::GetMinOutputValue() const
+  {
+    switch (outputFormat_)
+    {
+      case Orthanc::PixelFormat_Grayscale8:
+      case Orthanc::PixelFormat_Grayscale16:
+      case Orthanc::PixelFormat_BGRA32:
+        return 0.0f;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+    
+  float VolumeReslicer::GetMaxOutputValue() const
+  {
+    switch (outputFormat_)
+    {
+      case Orthanc::PixelFormat_Grayscale8:
+      case Orthanc::PixelFormat_BGRA32:
+        return static_cast<float>(std::numeric_limits<uint8_t>::max());
+        break;
+
+      case Orthanc::PixelFormat_Grayscale16:
+        return static_cast<float>(std::numeric_limits<uint16_t>::max());
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+    
+
+  VolumeReslicer::VolumeReslicer() :
+    outputFormat_(Orthanc::PixelFormat_Grayscale8),
+    interpolation_(ImageInterpolation_Nearest),
+    fastMode_(true),
+    success_(false)
+  {
+    ResetLinearFunction();
+  }
+
+
+  void VolumeReslicer::GetLinearFunction(float& scaling,
+                                         float& offset) const
+  {
+    if (hasLinearFunction_)
+    {
+      scaling = scaling_;
+      offset = offset_;
+    }
+    else
+    {
+      scaling = 1.0f;
+      offset = 0.0f;
+    }
+  }
+
+  
+  void VolumeReslicer::ResetLinearFunction()
+  {
+    Reset();
+    hasLinearFunction_ = false;
+    scaling_ = 1.0f;
+    offset_ = 0.0f;
+  }
+    
+
+  void VolumeReslicer::SetLinearFunction(float scaling,
+                                         float offset)
+  {
+    Reset();
+    hasLinearFunction_ = true;
+    scaling_ = scaling;
+    offset_ = offset;
+  }
+
+  
+  void VolumeReslicer::SetWindow(float low,
+                                 float high)
+  {
+    //printf("Range in pixel values: %f->%f\n", low, high);
+    float scaling = (GetMaxOutputValue() - GetMinOutputValue()) / (high - low);
+    float offset = GetMinOutputValue() - scaling * low;
+    
+    SetLinearFunction(scaling, offset);
+
+    /*float x = scaling_ * low + offset_;
+    float y = scaling_ * high + offset_;
+    printf("%f %f (should be %f->%f)\n", x, y, GetMinOutputValue(), GetMaxOutputValue());*/
+  }
+  
+
+  void VolumeReslicer::FitRange(const ImageBuffer3D& image)
+  {
+    float minInputValue, maxInputValue;
+
+    if (!image.GetRange(minInputValue, maxInputValue) ||
+        maxInputValue < 1)
+    {
+      ResetLinearFunction();
+    }
+    else
+    {
+      SetWindow(minInputValue, maxInputValue);
+    }
+  }  
+
+  
+  void VolumeReslicer::SetWindowing(ImageWindowing windowing,
+                                    const ImageBuffer3D& image,
+                                    float rescaleSlope,
+                                    float rescaleIntercept)
+  {
+    if (windowing == ImageWindowing_Custom ||
+        windowing == ImageWindowing_Default)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    float center, width;
+    ComputeWindowing(center, width, windowing, 0, 0);
+
+    float a = (center - width / 2.0f - rescaleIntercept) / rescaleSlope;
+    float b = (center + width / 2.0f - rescaleIntercept) / rescaleSlope;
+    SetWindow(a, b);
+  }
+
+
+  void VolumeReslicer::SetOutputFormat(Orthanc::PixelFormat format)
+  {
+    if (format != Orthanc::PixelFormat_Grayscale8 &&
+        format != Orthanc::PixelFormat_Grayscale16 &&
+        format != Orthanc::PixelFormat_BGRA32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (hasLinearFunction_)
+    {
+      LOG(WARNING) << "Calls to VolumeReslicer::SetOutputFormat() should be done before VolumeReslicer::FitRange()";
+    }
+    
+    outputFormat_ = format;
+    Reset();
+  }
+
+
+  void VolumeReslicer::SetInterpolation(ImageInterpolation interpolation)
+  {
+    if (interpolation != ImageInterpolation_Nearest &&
+        interpolation != ImageInterpolation_Bilinear &&
+        interpolation != ImageInterpolation_Trilinear)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    interpolation_ = interpolation;
+    Reset();
+  }
+
+
+  const Extent2D& VolumeReslicer::GetOutputExtent() const
+  {
+    if (success_)
+    {
+      return extent_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  
+  const Orthanc::ImageAccessor& VolumeReslicer::GetOutputSlice() const
+  {
+    if (success_)
+    {
+      assert(slice_.get() != NULL);
+      return *slice_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  
+  Orthanc::ImageAccessor* VolumeReslicer::ReleaseOutputSlice()
+  {
+    if (success_)
+    {
+      assert(slice_.get() != NULL);
+      success_ = false;
+      return slice_.release();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  
+  void VolumeReslicer::Apply(const ImageBuffer3D& source,
+                             const CoordinateSystem3D& plane)
+  {
+    // Choose the default voxel size as the finest voxel dimension
+    // of the source volumetric image
+    const OrthancStone::Vector dim = source.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
+    double voxelSize = dim[0];
+    
+    if (dim[1] < voxelSize)
+    {
+      voxelSize = dim[1];
+    }
+    
+    if (dim[2] < voxelSize)
+    {
+      voxelSize = dim[2];
+    }
+
+    if (voxelSize <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    Apply(source, plane, voxelSize);
+  }
+
+  
+  void VolumeReslicer::Apply(const ImageBuffer3D& source,
+                             const CoordinateSystem3D& plane,
+                             double voxelSize)
+  {
+    Reset();
+
+    // 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);
+
+    if (!box.ComputeExtent(extent_, plane))
+    {
+      // The plane does not intersect with the bounding box of the volume
+      slice_.reset(new Orthanc::Image(outputFormat_, 0, 0, false));
+      success_ = true;
+      return;
+    }
+
+    // Secondly, the extent together with the voxel size gives the
+    // size of the output image
+    unsigned int width = boost::math::iround(extent_.GetWidth() / voxelSize);
+    unsigned int height = boost::math::iround(extent_.GetHeight() / voxelSize);
+
+    slice_.reset(new Orthanc::Image(outputFormat_, width, height, false));
+
+    //CheckIterators(source, plane, box);
+      
+    if (fastMode_)
+    {
+      ProcessImage<FastRowIterator>(*slice_, extent_, source, plane, box,
+                                    interpolation_, hasLinearFunction_, scaling_, offset_);
+    }
+    else
+    {
+      ProcessImage<SlowRowIterator>(*slice_, extent_, source, plane, box,
+                                    interpolation_, hasLinearFunction_, scaling_, offset_);
+    }
+
+    success_ = true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Volumes/VolumeReslicer.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,120 @@
+/**
+ * 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 "../Toolbox/Extent2D.h"
+#include "../Toolbox/OrientedBoundingBox.h"
+#include "ImageBuffer3D.h"
+
+namespace OrthancStone
+{
+  // Hypothesis: The output voxels always have square size
+  class VolumeReslicer : public boost::noncopyable
+  {
+  private:
+    // Input parameters
+    Orthanc::PixelFormat           outputFormat_;
+    bool                           hasLinearFunction_;
+    float                          scaling_;  // "a" in "f(x) = a * x + b"
+    float                          offset_;   // "b" in "f(x) = a * x + b"
+    ImageInterpolation             interpolation_;
+    bool                           fastMode_;
+
+    // Output of reslicing
+    bool                           success_;
+    Extent2D                       extent_;
+    std::auto_ptr<Orthanc::Image>  slice_;
+
+    void CheckIterators(const ImageBuffer3D& source,
+                        const CoordinateSystem3D& plane,
+                        const OrientedBoundingBox& box) const;
+
+    void Reset();
+
+    float GetMinOutputValue() const;
+
+    float GetMaxOutputValue() const;
+
+    void SetWindow(float low,
+                   float high);
+    
+  public:
+    VolumeReslicer();
+
+    void GetLinearFunction(float& scaling,
+                           float& offset) const;
+
+    void ResetLinearFunction();
+    
+    void SetLinearFunction(float scaling,
+                           float offset);
+
+    void FitRange(const ImageBuffer3D& image);
+
+    void SetWindowing(ImageWindowing windowing,
+                      const ImageBuffer3D& image,
+                      float rescaleSlope,
+                      float rescaleIntercept);
+
+    Orthanc::PixelFormat GetOutputFormat() const
+    {
+      return outputFormat_;
+    }
+
+    void SetOutputFormat(Orthanc::PixelFormat format);
+
+    ImageInterpolation GetInterpolation() const
+    {
+      return interpolation_;
+    }
+
+    void SetInterpolation(ImageInterpolation interpolation);
+
+    bool IsFastMode() const
+    {
+      return fastMode_;
+    }
+
+    void EnableFastMode(bool enabled)
+    {
+      fastMode_ = enabled;
+    }
+
+    bool IsSuccess() const
+    {
+      return success_;
+    }
+
+    const Extent2D& GetOutputExtent() const;
+
+    const Orthanc::ImageAccessor& GetOutputSlice() const;
+
+    Orthanc::ImageAccessor* ReleaseOutputSlice();
+
+    void Apply(const ImageBuffer3D& source,
+               const CoordinateSystem3D& plane);
+
+    void Apply(const ImageBuffer3D& source,
+               const CoordinateSystem3D& plane,
+               double voxelSize);
+  };
+}
--- a/Framework/Widgets/CairoWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/CairoWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #include "CairoWidget.h"
 
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
--- a/Framework/Widgets/EmptyWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/EmptyWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,14 +21,24 @@
 
 #include "EmptyWidget.h"
 
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
 
 namespace OrthancStone
 {
-  bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
+  namespace Samples
   {
-    // Note: This call is slow
-    Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
-    return true;
+    bool EmptyWidget::Render(Orthanc::ImageAccessor& surface)
+    {
+      // Note: This call is slow
+      Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255);
+      return true;
+    }
+
+  
+    void EmptyWidget::UpdateContent()
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
   }
 }
--- a/Framework/Widgets/EmptyWidget.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/EmptyWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -25,82 +25,93 @@
 
 namespace OrthancStone
 {
-  /**
-   * This is a test widget that simply fills its surface with an
-   * uniform color.
-   **/
-  class EmptyWidget : public IWidget
+  namespace Samples
   {
-  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 SetStatusBar(IStatusBar& statusBar)
-    {
-    }
-
-    virtual void ResetStatusBar()
-    {
-    }
-
-    virtual void Register(IChangeObserver& observer)
-    {
-    }
-
-    virtual void Unregister(IChangeObserver& observer)
-    {
-    }
-
-    virtual void Start()
+    /**
+     * 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_;
 
-    virtual void Stop()
-    {
-    }
+    public:
+      EmptyWidget(uint8_t red,
+                  uint8_t green,
+                  uint8_t blue) :
+        red_(red),
+        green_(green),
+        blue_(blue)
+      {
+      }
 
-    virtual void SetSize(unsigned int width, 
-                         unsigned int height)
-    {
-    }
- 
-    virtual bool Render(Orthanc::ImageAccessor& surface);
+      virtual void SetDefaultView()
+      {
+      }
+  
+      virtual void SetParent(OrthancStone::IWidget& widget)
+      {
+      }
+    
+      virtual void SetViewport(IViewport& viewport)
+      {
+      }
+
+      virtual void NotifyChange()
+      {
+      }
+
+      virtual void SetStatusBar(IStatusBar& statusBar)
+      {
+      }
 
-    virtual IMouseTracker* CreateMouseTracker(MouseButton button,
-                                              int x,
-                                              int y,
-                                              KeyboardModifiers modifiers)
-    {
-      return NULL;
-    }
+      virtual void SetSize(unsigned int width, 
+                           unsigned int height)
+      {
+      }
+ 
+      virtual bool Render(Orthanc::ImageAccessor& surface);
 
-    virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
-                                 int x,
-                                 int y)
-    {
-    }
+      virtual IMouseTracker* CreateMouseTracker(MouseButton button,
+                                                int x,
+                                                int y,
+                                                KeyboardModifiers modifiers)
+      {
+        return NULL;
+      }
+
+      virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
+                                   int x,
+                                   int y)
+      {
+      }
 
-    virtual void MouseWheel(MouseWheelDirection direction,
-                            int x,
-                            int y,
-                            KeyboardModifiers modifiers)
-    {
-    }
+      virtual void MouseWheel(MouseWheelDirection direction,
+                              int x,
+                              int y,
+                              KeyboardModifiers modifiers)
+      {
+      }
+
+      virtual void KeyPressed(char key,
+                              KeyboardModifiers modifiers)
+      {
+      }
 
-    virtual void KeyPressed(char key,
-                            KeyboardModifiers modifiers)
-    {
-    }
-  };
+      virtual bool HasUpdateContent() const
+      {
+        return false;
+      }
+
+      virtual void UpdateContent();
+
+      virtual bool HasRenderMouseOver()
+      {
+        return false;
+      }
+    };
+  }
 }
--- a/Framework/Widgets/IWidget.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/IWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,37 +21,28 @@
 
 #pragma once
 
-#include "../Enumerations.h"
+#include "../StoneEnumerations.h"
 #include "../Viewport/IMouseTracker.h"
 #include "../Viewport/IStatusBar.h"
+#include "../Viewport/IViewport.h"
 
 namespace OrthancStone
 {
-  class IWidget : public IThreadUnsafe
+  class IWidget : public boost::noncopyable
   {
   public:
-    class IChangeObserver : public boost::noncopyable
+    virtual ~IWidget()
     {
-    public:
-      virtual ~IChangeObserver()
-      {
-      }
-      
-      virtual void NotifyChange(const IWidget& widget) = 0;
-    };
+    }
+
+    virtual void SetDefaultView() = 0;
+
+    virtual void SetParent(IWidget& parent) = 0;
+    
+    virtual void SetViewport(IViewport& viewport) = 0;
 
     virtual void SetStatusBar(IStatusBar& statusBar) = 0;
 
-    virtual void ResetStatusBar() = 0;
-
-    virtual void Register(IChangeObserver& observer) = 0;
-
-    virtual void Unregister(IChangeObserver& observer) = 0;
-
-    virtual void Start() = 0;
-
-    virtual void Stop() = 0;
-
     virtual void SetSize(unsigned int width, 
                          unsigned int height) = 0;
  
@@ -66,6 +57,8 @@
                                  int x,
                                  int y) = 0;
 
+    virtual bool HasRenderMouseOver() = 0;
+
     virtual void MouseWheel(MouseWheelDirection direction,
                             int x,
                             int y,
@@ -73,5 +66,13 @@
 
     virtual void KeyPressed(char key,
                             KeyboardModifiers modifiers) = 0;
+
+    virtual bool HasUpdateContent() const = 0;
+
+    virtual void UpdateContent() = 0;
+
+    // Subclasses can call this method to signal the display of the
+    // widget must be refreshed
+    virtual void NotifyChange() = 0;
   };
 }
--- a/Framework/Widgets/IWorldSceneInteractor.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/IWorldSceneInteractor.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,20 +23,22 @@
 
 #include "IWorldSceneMouseTracker.h"
 
-#include "../Toolbox/SliceGeometry.h"
 #include "../Toolbox/ViewportGeometry.h"
-#include "../Enumerations.h"
+#include "../StoneEnumerations.h"
 #include "../Viewport/IStatusBar.h"
 
 namespace OrthancStone
 {
   class WorldSceneWidget;
 
-  class IWorldSceneInteractor : public IThreadSafe
+  class IWorldSceneInteractor : public boost::noncopyable
   {
   public:
+    virtual ~IWorldSceneInteractor()
+    {
+    }
+    
     virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                        const SliceGeometry& slice,
                                                         const ViewportGeometry& view,
                                                         MouseButton button,
                                                         double x,
@@ -45,7 +47,6 @@
 
     virtual void MouseOver(CairoContext& context,
                            WorldSceneWidget& widget,
-                           const SliceGeometry& slice,
                            const ViewportGeometry& view,
                            double x,
                            double y,
--- a/Framework/Widgets/IWorldSceneMouseTracker.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/IWorldSceneMouseTracker.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,14 +21,17 @@
 
 #pragma once
 
-#include "../Toolbox/IThreadSafety.h"
 #include "../Viewport/CairoContext.h"
 
 namespace OrthancStone
 {
-  class IWorldSceneMouseTracker : public IThreadUnsafe
+  class IWorldSceneMouseTracker : public boost::noncopyable
   {
   public:
+    virtual ~IWorldSceneMouseTracker()
+    {
+    }
+    
     virtual void Render(CairoContext& context,
                         double zoom) = 0;
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayerWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,585 @@
+/**
+ * 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 "LayerWidget.h"
+
+#include "../Layers/SliceOutlineRenderer.h"
+#include "../Toolbox/GeometryToolbox.h"
+
+#include <Core/Logging.h>
+
+static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon();
+
+namespace OrthancStone
+{
+  class LayerWidget::Scene : public boost::noncopyable
+  {
+  private:
+    CoordinateSystem3D            slice_;
+    double                        thickness_;
+    size_t                        countMissing_;
+    std::vector<ILayerRenderer*>  renderers_;
+
+    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_++;
+      }
+    }
+      
+  public:
+    Scene(const CoordinateSystem3D& slice,
+          double thickness,
+          size_t countLayers) :
+      slice_(slice),
+      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 CoordinateSystem3D& GetSlice() const
+    {
+      return slice_;
+    }
+
+    bool HasRenderer(size_t index)
+    {
+      return renderers_[index] != NULL;
+    }
+
+    bool IsComplete() const
+    {
+      return countMissing_ == 0;
+    }
+
+    unsigned int GetCountMissing() const
+    {
+      return countMissing_;
+    }
+
+    bool RenderScene(CairoContext& context,
+                     const ViewportGeometry& view,
+                     const CoordinateSystem3D& viewportSlice)
+    {
+      bool fullQuality = true;
+      cairo_t *cr = context.GetObject();
+
+      for (size_t i = 0; i < renderers_.size(); i++)
+      {
+        if (renderers_[i] != NULL)
+        {
+          const CoordinateSystem3D& frameSlice = renderers_[i]->GetLayerSlice();
+          
+          double x0, y0, x1, y1, x2, y2;
+          viewportSlice.ProjectPoint(x0, y0, frameSlice.GetOrigin());
+          viewportSlice.ProjectPoint(x1, y1, frameSlice.GetOrigin() + frameSlice.GetAxisX());
+          viewportSlice.ProjectPoint(x2, y2, frameSlice.GetOrigin() + frameSlice.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 circles makes WebAssembly crash!
+        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI);
+#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 CoordinateSystem3D& slice) const
+    {
+      bool isOpposite;
+      if (!GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                 slice.GetNormal(),
+                                                 slice_.GetNormal()))
+      {
+        return false;
+      }
+      else
+      {
+        double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) -
+                    slice_.ProjectAlongNormal(slice_.GetOrigin()));
+      
+        if (z < 0)
+        {
+          z = -z;
+        }
+
+        return z <= thickness_;
+      }
+    }
+
+    double GetThickness() const
+    {
+      return thickness_;
+    }
+  };
+
+  
+  bool LayerWidget::LookupLayer(size_t& index /* out */,
+                                const ILayerSource& 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 LayerWidget::GetLayerExtent(Extent2D& extent,
+                                   ILayerSource& source) const
+  {
+    extent.Reset();
+    
+    std::vector<Vector> points;
+    if (source.GetExtent(points, slice_))
+    {
+      for (size_t i = 0; i < points.size(); i++)
+      {
+        double x, y;
+        slice_.ProjectPoint(x, y, points[i]);
+        extent.AddPoint(x, y);
+      }
+    }
+  }
+
+        
+  Extent2D LayerWidget::GetSceneExtent()
+  {
+    Extent2D sceneExtent;
+
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      Extent2D layerExtent;
+      GetLayerExtent(layerExtent, *layers_[i]);
+
+      sceneExtent.Union(layerExtent);
+    }
+
+    return sceneExtent;
+  }
+
+  
+  bool LayerWidget::RenderScene(CairoContext& context,
+                                const ViewportGeometry& view)
+  {
+    if (currentScene_.get() != NULL)
+    {
+      return currentScene_->RenderScene(context, view, slice_);
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  
+  void LayerWidget::ResetPendingScene()
+  {
+    double thickness;
+    if (pendingScene_.get() == NULL)
+    {
+      thickness = 1.0;
+    }
+    else
+    {
+      thickness = pendingScene_->GetThickness();
+    }
+    
+    pendingScene_.reset(new Scene(slice_, thickness, layers_.size()));
+  }
+  
+
+  void LayerWidget::UpdateLayer(size_t index,
+                                ILayerRenderer* renderer,
+                                const CoordinateSystem3D& slice)
+  {
+    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(slice))
+    {
+      currentScene_->SetLayer(index, tmp.release());
+      NotifyChange();
+    }
+    else if (pendingScene_.get() != NULL &&
+             pendingScene_->ContainsPlane(slice))
+    {
+      pendingScene_->SetLayer(index, tmp.release());
+
+      if (currentScene_.get() == NULL ||
+          !currentScene_->IsComplete() ||
+          pendingScene_->IsComplete())
+      {
+        currentScene_ = pendingScene_;
+        NotifyChange();
+      }
+    }
+  }
+
+  
+  LayerWidget::LayerWidget() :
+    started_(false)
+  {
+    SetBackgroundCleared(true);
+  }
+  
+  
+  LayerWidget::~LayerWidget()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      delete layers_[i];
+    }
+  }
+  
+
+  size_t LayerWidget::AddLayer(ILayerSource* 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();
+    layer->Register(*this);
+
+    ResetChangedLayers();
+
+    return index;
+  }
+
+  
+  const RenderStyle& LayerWidget::GetLayerStyle(size_t layer) const
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_.size() == styles_.size());
+    return styles_[layer];
+  }
+  
+
+  void LayerWidget::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);
+    }
+
+    NotifyChange();
+  }
+  
+
+  void LayerWidget::SetSlice(const CoordinateSystem3D& slice)
+  {
+    LOG(INFO) << "Setting slice origin: (" << slice.GetOrigin()[0]
+              << "," << slice.GetOrigin()[1]
+              << "," << slice.GetOrigin()[2] << ")";
+    
+    Slice displayedSlice(slice_, THIN_SLICE_THICKNESS);
+
+    //if (!displayedSlice.ContainsPlane(slice))
+    {
+      if (currentScene_.get() == NULL ||
+          (pendingScene_.get() != NULL &&
+           pendingScene_->IsComplete()))
+      {
+        currentScene_ = pendingScene_;
+      }
+
+      slice_ = slice;
+      ResetPendingScene();
+
+      InvalidateAllLayers();   // TODO Removing this line avoid loading twice the image in WASM
+    }
+  }
+
+
+  void LayerWidget::NotifyGeometryReady(const ILayerSource& source)
+  {
+    size_t i;
+    if (LookupLayer(i, source))
+    {
+      LOG(INFO) << "Geometry ready for layer " << i;
+
+      changedLayers_[i] = true;
+      //layers_[i]->ScheduleLayerCreation(slice_);
+    }
+  }
+  
+
+  void LayerWidget::NotifyGeometryError(const ILayerSource& source)
+  {
+    LOG(ERROR) << "Cannot get geometry";
+  }
+  
+
+  void LayerWidget::InvalidateAllLayers()
+  {
+    for (size_t i = 0; i < layers_.size(); i++)
+    {
+      assert(layers_[i] != NULL);
+      changedLayers_[i] = true;
+      
+      //layers_[i]->ScheduleLayerCreation(slice_);
+    }
+  }
+
+
+  void LayerWidget::InvalidateLayer(size_t layer)
+  {
+    if (layer >= layers_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(layers_[layer] != NULL);
+    changedLayers_[layer] = true;
+
+    //layers_[layer]->ScheduleLayerCreation(slice_);
+  }
+
+
+  void LayerWidget::NotifyContentChange(const ILayerSource& source)
+  {
+    size_t index;
+    if (LookupLayer(index, source))
+    {
+      InvalidateLayer(index);
+    }
+  }
+  
+
+  void LayerWidget::NotifySliceChange(const ILayerSource& source,
+                                      const Slice& slice)
+  {
+    if (slice.ContainsPlane(slice_))
+    {
+      size_t index;
+      if (LookupLayer(index, source))
+      {
+        InvalidateLayer(index);
+      }
+    }
+  }
+  
+  
+  void LayerWidget::NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+                                     const ILayerSource& source,
+                                     const CoordinateSystem3D& slice,
+                                     bool isError)
+  {
+    size_t index;
+    if (LookupLayer(index, source))
+    {
+      if (isError)
+      {
+        LOG(ERROR) << "Using error renderer on layer " << index;
+      }
+      else
+      {
+        LOG(INFO) << "Renderer ready for layer " << index;
+      }
+      
+      if (renderer.get() != NULL)
+      {
+        UpdateLayer(index, renderer.release(), slice);
+      }
+      else if (isError)
+      {
+        // TODO
+        //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
+      }
+    }
+  }
+
+
+  void LayerWidget::ResetChangedLayers()
+  {
+    changedLayers_.resize(layers_.size());
+
+    for (size_t i = 0; i < changedLayers_.size(); i++)
+    {
+      changedLayers_[i] = false;
+    }
+  }
+
+
+  void LayerWidget::UpdateContent()
+  {
+    assert(changedLayers_.size() <= layers_.size());
+    
+    for (size_t i = 0; i < changedLayers_.size(); i++)
+    {
+      if (changedLayers_[i])
+      {
+        layers_[i]->ScheduleLayerCreation(slice_);
+      }
+    }
+    
+    ResetChangedLayers();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/LayerWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,120 @@
+/**
+ * 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 "WorldSceneWidget.h"
+#include "../Layers/ILayerSource.h"
+#include "../Toolbox/Extent2D.h"
+
+#include <map>
+
+namespace OrthancStone
+{
+  class LayerWidget :
+    public WorldSceneWidget,
+    private ILayerSource::IObserver
+  {
+  private:
+    class Scene;
+    
+    typedef std::map<const ILayerSource*, size_t>  LayersIndex;
+
+    bool                        started_;
+    LayersIndex                 layersIndex_;
+    std::vector<ILayerSource*>  layers_;
+    std::vector<RenderStyle>    styles_;
+    CoordinateSystem3D          slice_;
+    std::auto_ptr<Scene>        currentScene_;
+    std::auto_ptr<Scene>        pendingScene_;
+    std::vector<bool>           changedLayers_;
+
+    bool LookupLayer(size_t& index /* out */,
+                     const ILayerSource& layer) const;
+
+    void GetLayerExtent(Extent2D& extent,
+                        ILayerSource& source) const;
+
+    virtual void NotifyGeometryReady(const ILayerSource& source);
+
+    virtual void NotifyGeometryError(const ILayerSource& source);
+
+    virtual void NotifyContentChange(const ILayerSource& source);
+
+    virtual void NotifySliceChange(const ILayerSource& source,
+                                   const Slice& slice);
+
+    virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+                                  const ILayerSource& source,
+                                  const CoordinateSystem3D& slice,
+                                  bool isError);
+
+    void ResetChangedLayers();
+
+  public:
+    virtual Extent2D GetSceneExtent();
+ 
+  protected:
+    virtual bool RenderScene(CairoContext& context,
+                             const ViewportGeometry& view);
+
+    void ResetPendingScene();
+
+    void UpdateLayer(size_t index,
+                     ILayerRenderer* renderer,
+                     const CoordinateSystem3D& slice);
+
+    void InvalidateAllLayers();
+
+    void InvalidateLayer(size_t layer);
+    
+  public:
+    LayerWidget();
+
+    virtual ~LayerWidget();
+
+    size_t AddLayer(ILayerSource* layer);  // Takes ownership
+
+    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 CoordinateSystem3D& slice);
+
+    const CoordinateSystem3D& GetSlice() const
+    {
+      return slice_;
+    }
+
+    virtual bool HasUpdateContent() const
+    {
+      return true;
+    }
+
+    virtual void UpdateContent();
+  };
+}
--- a/Framework/Widgets/LayeredSceneWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,628 +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/>.
- **/
-
-
-#define _USE_MATH_DEFINES  // To access M_PI in Visual Studio
-#include <cmath>
-
-#include "LayeredSceneWidget.h"
-
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  class LayeredSceneWidget::Renderers : public boost::noncopyable
-  {
-  private:
-    boost::mutex                  mutex_;
-    std::vector<ILayerRenderer*>  renderers_;
-    std::vector<bool>             assigned_;
-      
-    void Assign(size_t index,
-                ILayerRenderer* renderer)
-    {
-      if (index >= renderers_.size())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-        
-      if (renderers_[index] != NULL)
-      {
-        delete renderers_[index];
-      }
-
-      renderers_[index] = renderer;
-      assigned_[index] = true;
-    }
-
-  public:
-    Renderers(size_t size)
-    {
-      renderers_.resize(size);
-      assigned_.resize(size, false);
-    }
-
-    ~Renderers()
-    {
-      for (size_t i = 0; i < renderers_.size(); i++)
-      {
-        Assign(i, NULL);
-      }
-    }
-
-    static void Merge(Renderers& target,
-                      Renderers& source)
-    {
-      boost::mutex::scoped_lock lockSource(source.mutex_);
-      boost::mutex::scoped_lock lockTarget(target.mutex_);
-
-      size_t count = target.renderers_.size();
-      if (count != source.renderers_.size())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      for (size_t i = 0; i < count; i++)
-      {
-        if (source.assigned_[i])
-        {
-          target.Assign(i, source.renderers_[i]);  // Transfers ownership
-          source.renderers_[i] = NULL;
-          source.assigned_[i] = false;
-        }
-      }
-    }
-
-    void SetRenderer(size_t index,
-                     ILayerRenderer* renderer)  // Takes ownership
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      Assign(index, renderer);
-    }
-
-    bool RenderScene(CairoContext& context,
-                     const ViewportGeometry& view)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      bool fullQuality = true;
-
-      for (size_t i = 0; i < renderers_.size(); i++)
-      {
-        if (renderers_[i] != NULL &&
-            !renderers_[i]->RenderLayer(context, view))
-        {
-          return false;
-        }
-
-        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_t *cr = context.GetObject();
-        cairo_translate(cr, x, y);
-        cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI);
-        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)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-        
-      if (renderers_[index] != NULL)
-      {
-        renderers_[index]->SetLayerStyle(style);
-      }
-    }
-  };
-
-
-
-  class LayeredSceneWidget::PendingLayers : public boost::noncopyable
-  {
-  private:
-    boost::mutex               mutex_;
-    boost::condition_variable  elementAvailable_;
-    size_t                     layerCount_;
-    std::list<size_t>          queue_;
-    std::vector<bool>          layersToUpdate_;
-    bool                       continue_;
-
-    void TagAllLayers()
-    {
-      queue_.clear();
-
-      for (unsigned int i = 0; i < layerCount_; i++)
-      {
-        queue_.push_back(i);
-        layersToUpdate_[i] = true;
-      }
-
-      if (layerCount_ != 0)
-      {
-        elementAvailable_.notify_one();
-      }
-    }
-      
-  public:
-    PendingLayers() : 
-      layerCount_(0), 
-      continue_(true)
-    {
-    }
-
-    void Stop()
-    {
-      continue_ = false;
-      elementAvailable_.notify_one();
-    }
-
-    void SetLayerCount(size_t count)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      layerCount_ = count;
-      layersToUpdate_.resize(count);
-
-      TagAllLayers();
-    }
-
-    void InvalidateAllLayers()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      TagAllLayers();
-    }
-
-    void InvalidateLayer(size_t layer)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (layer < layerCount_)
-      {
-        if (layersToUpdate_[layer])
-        {
-          // The layer is already scheduled for update, ignore this
-          // invalidation
-        }
-        else
-        {
-          queue_.push_back(layer);
-          layersToUpdate_[layer] = true;
-          elementAvailable_.notify_one();
-        }
-      }
-    }
-
-    bool Dequeue(size_t& layer,
-                 bool& isLast)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      // WARNING: Do NOT use "timed_wait" on condition variables, as
-      // sleeping is not properly supported by Boost for Google NaCl
-      while (queue_.empty() && 
-             continue_)
-      {
-        elementAvailable_.wait(lock);
-      }
-
-      if (!continue_)
-      {
-        return false;
-      }
-
-      layer = queue_.front();
-      layersToUpdate_[layer] = false;
-      queue_.pop_front();
-
-      isLast = queue_.empty();
-
-      return true;
-    }
-  };
-
-
-  class LayeredSceneWidget::Layer : public ISliceableVolume::IChangeObserver
-  {
-  private:
-    boost::mutex                          mutex_;
-    std::auto_ptr<ILayerRendererFactory>  factory_;
-    PendingLayers&                        layers_;
-    size_t                                index_;
-    std::auto_ptr<RenderStyle>            style_;
-
-  public:
-    Layer(ILayerRendererFactory*  factory,
-          PendingLayers& layers,
-          size_t index) :
-      factory_(factory),
-      layers_(layers),
-      index_(index)
-    {
-      if (factory == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    virtual void NotifyChange(const OrthancStone::ISliceableVolume&)
-    {
-      layers_.InvalidateLayer(index_);
-    }
-
-    void Start()
-    {
-      if (factory_->HasSourceVolume())
-      {
-        factory_->GetSourceVolume().Register(*this);
-      }
-    }
-
-    void Stop()
-    {
-      if (factory_->HasSourceVolume())
-      {
-        factory_->GetSourceVolume().Unregister(*this);
-      }
-    }
-
-    bool GetExtent(double& x1,
-                   double& y1,
-                   double& x2,
-                   double& y2,
-                   const SliceGeometry& displaySlice) 
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      assert(factory_.get() != NULL);
-      return factory_->GetExtent(x1, y1, x2, y2, displaySlice);
-    }
-
-    RenderStyle GetStyle() 
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (style_.get() == NULL)
-      {
-        return RenderStyle();
-      }
-      else
-      {
-        return *style_;
-      }
-    }
-
-    void SetStyle(const RenderStyle& style)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      style_.reset(new RenderStyle(style));
-    }
-
-
-    ILayerRenderer* CreateRenderer(const SliceGeometry& displaySlice)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      assert(factory_.get() != NULL);
-
-      std::auto_ptr<ILayerRenderer> renderer(factory_->CreateLayerRenderer(displaySlice));
-        
-      if (renderer.get() != NULL &&
-          style_.get() != NULL)
-      {
-        renderer->SetLayerStyle(*style_);
-      }
-
-      return renderer.release();
-    }
-  };
-
-
-
-  SliceGeometry LayeredSceneWidget::GetSlice()
-  {
-    boost::mutex::scoped_lock lock(sliceMutex_);
-    return slice_;
-  }
-
-
-  void LayeredSceneWidget::UpdateStep()
-  {
-    size_t layer = 0;
-    bool isLast = true;
-    if (!pendingLayers_->Dequeue(layer, isLast))
-    {
-      return;
-    }
-
-    SliceGeometry slice = GetSlice();
-
-    std::auto_ptr<ILayerRenderer> renderer;
-    renderer.reset(layers_[layer]->CreateRenderer(slice));
-
-    if (renderer.get() != NULL)
-    {
-      pendingRenderers_->SetRenderer(layer, renderer.release());
-    }
-    else
-    {
-      pendingRenderers_->SetRenderer(layer, NULL);
-    }
-
-    if (isLast)
-    {
-      Renderers::Merge(*renderers_, *pendingRenderers_);
-      NotifyChange();
-    }
-
-    // TODO Add sleep at this point
-  }
-    
-
-  bool LayeredSceneWidget::RenderScene(CairoContext& context,
-                                       const ViewportGeometry& view) 
-  {
-    assert(IsStarted());
-    return renderers_->RenderScene(context, view);
-  }
-
-
-  LayeredSceneWidget::LayeredSceneWidget() 
-  {
-    pendingLayers_.reset(new PendingLayers);
-    SetBackgroundCleared(true);
-  }
-
-
-  LayeredSceneWidget::~LayeredSceneWidget()
-  {
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      assert(layers_[i] != NULL);
-      delete layers_[i];
-    }
-  }
-
-
-  void LayeredSceneWidget::GetSceneExtent(double& x1,
-                                          double& y1,
-                                          double& x2,
-                                          double& y2)
-  {
-    boost::mutex::scoped_lock lock(sliceMutex_);
-
-    bool first = true;
-
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      double ax, ay, bx, by;
-
-      assert(layers_[i] != NULL);
-      if (layers_[i]->GetExtent(ax, ay, bx, by, slice_))
-      {
-        if (ax > bx)
-        {
-          std::swap(ax, bx);
-        }
-
-        if (ay > by)
-        {
-          std::swap(ay, by);
-        }
-
-        if (first)
-        {
-          x1 = ax;
-          y1 = ay;
-          x2 = bx;
-          y2 = by;
-          first = false;
-        }
-        else
-        {
-          x1 = std::min(x1, ax);
-          y1 = std::min(y1, ay);
-          x2 = std::max(x2, bx);
-          y2 = std::max(y2, by);
-        }
-      }
-    }
-
-    if (first)
-    {
-      x1 = -1;
-      y1 = -1;
-      x2 = 1;
-      y2 = 1;
-    }
-
-    // Ensure the extent is non-empty
-    if (x1 >= x2)
-    {
-      double tmp = x1;
-      x1 = tmp - 0.5;
-      x2 = tmp + 0.5;
-    }
-
-    if (y1 >= y2)
-    {
-      double tmp = y1;
-      y1 = tmp - 0.5;
-      y2 = tmp + 0.5;
-    }
-  }
-
-
-
-  ILayerRendererFactory& LayeredSceneWidget::AddLayer(size_t& layerIndex,
-                                                      ILayerRendererFactory* factory)
-  {
-    if (IsStarted())
-    {
-      // Start() has already been invoked
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    layerIndex = layers_.size();
-    layers_.push_back(new Layer(factory, *pendingLayers_, layers_.size()));
-
-    return *factory;
-  }
-
-
-  void LayeredSceneWidget::AddLayer(ILayerRendererFactory* factory)
-  {
-    size_t layerIndex;  // Ignored
-    AddLayer(layerIndex, factory);
-  }
-
-
-  RenderStyle LayeredSceneWidget::GetLayerStyle(size_t layer)
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    return layers_[layer]->GetStyle();
-  }
-
-
-  void LayeredSceneWidget::SetLayerStyle(size_t layer,
-                                         const RenderStyle& style)
-  {
-    if (layer >= layers_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    layers_[layer]->SetStyle(style);
-
-    if (renderers_.get() != NULL)
-    {
-      renderers_->SetLayerStyle(layer, style);
-    }
-
-    InvalidateLayer(layer);
-  }
-
-
-
-  struct LayeredSceneWidget::SliceChangeFunctor
-  {
-    const SliceGeometry& slice_;
-
-    SliceChangeFunctor(const SliceGeometry& slice) :
-      slice_(slice)
-    {
-    }
-
-    void operator() (ISliceObserver& observer,
-                     const LayeredSceneWidget& source)
-    {
-      observer.NotifySliceChange(source, slice_);
-    }
-  };
-
-
-  void LayeredSceneWidget::SetSlice(const SliceGeometry& slice)
-  {
-    { 
-      boost::mutex::scoped_lock lock(sliceMutex_);
-      slice_ = slice;
-    }
-
-    InvalidateAllLayers();
-
-    SliceChangeFunctor functor(slice);
-    observers_.Notify(this, functor);
-  }
-
-
-  void LayeredSceneWidget::InvalidateLayer(unsigned int layer)
-  {
-    pendingLayers_->InvalidateLayer(layer);
-    //NotifyChange();  // TODO Understand why this makes the SDL engine not update the display subsequently
-  }
-
-
-  void LayeredSceneWidget::InvalidateAllLayers()
-  {
-    pendingLayers_->InvalidateAllLayers();
-    //NotifyChange();  // TODO Understand why this makes the SDL engine not update the display subsequently
-  }
-
-
-  void LayeredSceneWidget::Start()
-  {
-    if (IsStarted())
-    {
-      // Start() has already been invoked
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      layers_[i]->Start();
-    }
-
-    renderers_.reset(new Renderers(layers_.size()));
-    pendingRenderers_.reset(new Renderers(layers_.size()));
-
-    pendingLayers_->SetLayerCount(layers_.size());
-
-    WorldSceneWidget::Start();
-  }
-
-
-  void LayeredSceneWidget::Stop()
-  {
-    if (!IsStarted())
-    {
-      // Stop() has already been invoked
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    pendingLayers_->Stop();
-    WorldSceneWidget::Stop();
-
-    renderers_.reset(NULL);
-    pendingRenderers_.reset(NULL);
-
-    for (size_t i = 0; i < layers_.size(); i++)
-    {
-      layers_[i]->Stop();
-    }
-  }
-}
--- a/Framework/Widgets/LayeredSceneWidget.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +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 "WorldSceneWidget.h"
-
-#include "../Layers/ILayerRendererFactory.h"
-
-
-namespace OrthancStone
-{
-  class LayeredSceneWidget : public WorldSceneWidget
-  {
-  public:
-    // Must be thread-safe
-    class ISliceObserver : public boost::noncopyable
-    {
-    public:
-      virtual ~ISliceObserver()
-      {
-      }
-
-      virtual void NotifySliceChange(const LayeredSceneWidget& source,
-                                     const SliceGeometry& slice) = 0;
-    };
-
-  private:
-    struct SliceChangeFunctor;
-    class Renderers;
-    class PendingLayers;
-    class Layer;
-
-    typedef ObserversRegistry<LayeredSceneWidget, ISliceObserver>  Observers;
-
-    std::vector<Layer*>           layers_;
-    std::auto_ptr<Renderers>      renderers_;
-    std::auto_ptr<PendingLayers>  pendingLayers_;
-    std::auto_ptr<Renderers>      pendingRenderers_;
-    boost::mutex                  sliceMutex_;
-    SliceGeometry                 slice_;
-    Observers                     observers_;
-
-  protected:
-    virtual bool HasUpdateThread() const
-    {
-      return true;
-    }
-
-    virtual void UpdateStep();
-
-    virtual bool RenderScene(CairoContext& context,
-                             const ViewportGeometry& view);
-
-  public:
-    LayeredSceneWidget();
-
-    virtual ~LayeredSceneWidget();
-
-    virtual SliceGeometry GetSlice();
-
-    virtual void GetSceneExtent(double& x1,
-                                double& y1,
-                                double& x2,
-                                double& y2);
-
-    ILayerRendererFactory& AddLayer(size_t& layerIndex,
-                                    ILayerRendererFactory* factory);   // Takes ownership
-
-    // Simpler version for basic use cases
-    void AddLayer(ILayerRendererFactory* factory);   // Takes ownership
-
-    size_t GetLayerCount() const
-    {
-      return layers_.size();
-    }
-
-    RenderStyle GetLayerStyle(size_t layer);
-    
-    void SetLayerStyle(size_t layer,
-                       const RenderStyle& style);
-
-    void SetSlice(const SliceGeometry& slice);
-
-    void InvalidateLayer(unsigned int layer);
-
-    void InvalidateAllLayers();
-
-    virtual void Start();
-
-    virtual void Stop();
-
-    using WorldSceneWidget::Register;
-    using WorldSceneWidget::Unregister;
-
-    void Register(ISliceObserver& observer)
-    {
-      observers_.Register(observer);
-    }
-
-    void Unregister(ISliceObserver& observer)
-    {
-      observers_.Unregister(observer);
-    }
-  };
-}
--- a/Framework/Widgets/LayoutWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/LayoutWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,8 @@
 
 #include "LayoutWidget.h"
 
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
 
 #include <boost/math/special_functions/round.hpp>
 
@@ -82,15 +82,25 @@
     int                     top_;
     unsigned int            width_;
     unsigned int            height_;
+    bool                    hasUpdate_;
 
   public:
     ChildWidget(IWidget* widget) :
-      widget_(widget)
+      widget_(widget),
+      hasUpdate_(widget->HasUpdateContent())
     {
       assert(widget != NULL);
       SetEmpty();
     }
 
+    void UpdateContent()
+    {
+      if (hasUpdate_)
+      {
+        widget_->UpdateContent();
+      }
+    }
+
     IWidget& GetWidget() const
     {
       return *widget_;
@@ -179,6 +189,11 @@
         widget_->MouseWheel(direction, x - left_, y - top_, modifiers);
       }
     }
+    
+    bool HasRenderMouseOver()
+    {
+      return widget_->HasRenderMouseOver();
+    }
   };
 
 
@@ -252,15 +267,8 @@
   }
 
 
-  void LayoutWidget::UpdateStep()
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
   LayoutWidget::LayoutWidget() :
     isHorizontal_(true),
-    started_(false),
     width_(0),
     height_(0),
     paddingLeft_(0),
@@ -276,12 +284,20 @@
   {
     for (size_t i = 0; i < children_.size(); i++)
     {
-      children_[i]->GetWidget().Unregister(*this);
       delete children_[i];
     }
   }
 
 
+  void LayoutWidget::SetDefaultView()
+  {
+    for (size_t i = 0; i < children_.size(); i++)
+    {
+      children_[i]->GetWidget().SetDefaultView();
+    }
+  }
+  
+
   void LayoutWidget::NotifyChange(const IWidget& widget)
   {
     // One of the children has changed
@@ -329,12 +345,6 @@
 
   IWidget& LayoutWidget::AddWidget(IWidget* widget)  // Takes ownership
   {
-    if (started_)
-    {
-      LOG(ERROR) << "Cannot add child once Start() has been invoked";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
     if (widget == NULL)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
@@ -344,16 +354,17 @@
     {
       widget->SetStatusBar(*GetStatusBar());
     }
-    else
-    {
-      widget->ResetStatusBar();
-    }
 
     children_.push_back(new ChildWidget(widget));
-    widget->Register(*this);
+    widget->SetParent(*this);
 
     ComputeChildrenExtents();
 
+    if (widget->HasUpdateContent())
+    {
+      hasUpdateContent_ = true;
+    }
+
     return *widget;
   }
 
@@ -369,39 +380,6 @@
   }
 
 
-  void LayoutWidget::ResetStatusBar()
-  {
-    WidgetBase::ResetStatusBar();
-
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->GetWidget().ResetStatusBar();
-    }
-  }  
-
-
-  void LayoutWidget::Start()
-  {
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->GetWidget().Start();
-    }
-
-    WidgetBase::Start();
-  }
-
-
-  void LayoutWidget::Stop()
-  {
-    WidgetBase::Stop();
-
-    for (size_t i = 0; i < children_.size(); i++)
-    {
-      children_[i]->GetWidget().Stop();
-    }
-  }
-
-
   void LayoutWidget::SetSize(unsigned int width,
                              unsigned int height)
   {
@@ -479,4 +457,34 @@
       children_[i]->GetWidget().KeyPressed(key, modifiers);
     }
   }
+
+  
+  void LayoutWidget::UpdateContent()
+  {
+    if (hasUpdateContent_)
+    {
+      for (size_t i = 0; i < children_.size(); i++)
+      {
+        children_[i]->UpdateContent();
+      }
+    }
+    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	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/LayoutWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,12 +23,12 @@
 
 #include "WidgetBase.h"
 
+#include <vector>
+#include <memory>
 
 namespace OrthancStone
 {
-  class LayoutWidget : 
-    public WidgetBase,
-    public IWidget::IChangeObserver
+  class LayoutWidget : public WidgetBase
   {
   private:
     class LayoutMouseTracker;
@@ -36,7 +36,6 @@
 
     std::vector<ChildWidget*>     children_;
     bool                          isHorizontal_;
-    bool                          started_;
     unsigned int                  width_;
     unsigned int                  height_;
     std::auto_ptr<IMouseTracker>  mouseTracker_;
@@ -45,23 +44,17 @@
     unsigned int                  paddingRight_;
     unsigned int                  paddingBottom_;
     unsigned int                  paddingInternal_;
+    bool                          hasUpdateContent_;
 
     void ComputeChildrenExtents();
 
-  protected:
-    virtual bool HasUpdateThread() const 
-    {
-      return false;
-    }
-
-    virtual void UpdateStep();
-
-
   public:
     LayoutWidget();
 
     virtual ~LayoutWidget();
 
+    virtual void SetDefaultView();
+
     virtual void NotifyChange(const IWidget& widget);
 
     void SetHorizontal();
@@ -105,12 +98,6 @@
 
     virtual void SetStatusBar(IStatusBar& statusBar);
 
-    virtual void ResetStatusBar();
-
-    virtual void Start();
-
-    virtual void Stop();
-
     virtual void SetSize(unsigned int width,
                          unsigned int height);
 
@@ -132,5 +119,14 @@
 
     virtual void KeyPressed(char key,
                             KeyboardModifiers modifiers);
+
+    virtual bool HasUpdateContent() const
+    {
+      return hasUpdateContent_;
+    }
+
+    virtual void UpdateContent();
+
+    virtual bool HasRenderMouseOver();
   };
 }
--- a/Framework/Widgets/TestCairoWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/TestCairoWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,8 +21,6 @@
 
 #include "TestCairoWidget.h"
 
-#include "../../Resources/Orthanc/Core/SystemToolbox.h"
-
 #include <stdio.h>
 
 
@@ -30,7 +28,7 @@
 {
   namespace Samples
   {
-    void TestCairoWidget::UpdateStep() 
+    void TestCairoWidget::UpdateContent() 
     {
       value_ -= 0.01f;
       if (value_ < 0)
@@ -39,8 +37,6 @@
       }
 
       NotifyChange();
-
-      Orthanc::SystemToolbox::USleep(25000);
     }
 
 
--- a/Framework/Widgets/TestCairoWidget.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/TestCairoWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -35,13 +35,6 @@
       float         value_;
       bool          animate_;
 
-      virtual bool HasUpdateThread() const
-      {
-        return animate_;
-      }
-
-      virtual void UpdateStep();
-
     protected:
       virtual bool RenderCairo(CairoContext& context);
 
@@ -67,6 +60,18 @@
     
       virtual void KeyPressed(char key,
                               KeyboardModifiers modifiers);
+
+      virtual bool HasUpdateContent() const
+      {
+        return animate_;
+      }
+      
+      virtual void UpdateContent();
+
+      virtual bool HasRenderMouseOver()
+      {
+        return true;
+      }
     };
   }
 }
--- a/Framework/Widgets/TestWorldSceneWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/TestWorldSceneWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,6 +21,7 @@
 
 #include "TestWorldSceneWidget.h"
 
+#include <math.h>
 #include <stdio.h>
 
 namespace OrthancStone
@@ -31,7 +32,6 @@
     {
     public:
       virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                          const SliceGeometry& slice,
                                                           const ViewportGeometry& view,
                                                           MouseButton button,
                                                           double x,
@@ -50,7 +50,6 @@
 
       virtual void MouseOver(CairoContext& context,
                              WorldSceneWidget& widget,
-                             const SliceGeometry& slice,
                              const ViewportGeometry& view,
                              double x,
                              double y,
@@ -94,7 +93,7 @@
 
 
     bool TestWorldSceneWidget::RenderScene(CairoContext& context,
-                                           const ViewportGeometry& view)
+                                           const ViewportGeometry& view) 
     {
       cairo_t* cr = context.GetObject();
 
@@ -102,7 +101,8 @@
       cairo_set_source_rgb(cr, 0, 0, 0);
       cairo_paint(cr);
 
-      cairo_set_source_rgb(cr, 0, 1, 0);
+      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);
 
@@ -110,22 +110,32 @@
     }
 
 
-    TestWorldSceneWidget::TestWorldSceneWidget() :
-      interactor_(new Interactor)
+    TestWorldSceneWidget::TestWorldSceneWidget(bool animate) :
+      interactor_(new Interactor),
+      animate_(animate),
+      count_(0)
     {
       SetInteractor(*interactor_);
     }
 
 
-    void TestWorldSceneWidget::GetSceneExtent(double& x1,
-                                              double& y1,
-                                              double& x2,
-                                              double& y2)
+    Extent2D TestWorldSceneWidget::GetSceneExtent()
+    {
+      return Extent2D(-10, -.5, 10, .5);
+    }
+
+
+    void TestWorldSceneWidget::UpdateContent()
     {
-      x1 = -10;
-      x2 = 10;
-      y1 = -.5;
-      y2 = .5;
+      if (animate_)
+      {
+        count_++;
+        NotifyChange();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
     }
   }
 }
--- a/Framework/Widgets/TestWorldSceneWidget.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/TestWorldSceneWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -23,6 +23,8 @@
 
 #include "WorldSceneWidget.h"
 
+#include <memory>
+
 namespace OrthancStone
 {
   namespace Samples
@@ -33,23 +35,29 @@
       class Interactor;
 
       std::auto_ptr<Interactor>   interactor_;
+      bool                        animate_;
+      unsigned int                count_;
 
     protected:
-      virtual SliceGeometry GetSlice()
-      {
-        return SliceGeometry();
-      }
-
       virtual bool RenderScene(CairoContext& context,
                                const ViewportGeometry& view);
 
     public:
-      TestWorldSceneWidget();
+      TestWorldSceneWidget(bool animate);
+
+      virtual Extent2D GetSceneExtent();
 
-      virtual void GetSceneExtent(double& x1,
-                                  double& y1,
-                                  double& x2,
-                                  double& y2);
+      virtual bool HasUpdateContent() const
+      {
+        return animate_;
+      }
+
+      virtual void UpdateContent();
+
+      virtual bool HasRenderMouseOver()
+      {
+        return true;
+      }
     };
   }
 }
--- a/Framework/Widgets/WidgetBase.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/WidgetBase.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,12 +21,39 @@
 
 #include "WidgetBase.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
-#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
-#include "../../Resources/Orthanc/Core/Logging.h"
+#include <Core/OrthancException.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
 
 namespace OrthancStone
 {
+  void WidgetBase::NotifyChange()
+  {
+    if (parent_ != NULL)
+    {
+      parent_->NotifyChange();
+    }
+
+    if (viewport_ != NULL)
+    {
+      viewport_->NotifyChange(*this);
+    }
+  }
+
+
+  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
@@ -65,12 +92,6 @@
   }
 
 
-  void WidgetBase::NotifyChange()
-  {
-    observers_.NotifyChange(this);
-  }
-
-
   void WidgetBase::UpdateStatusBar(const std::string& message)
   {
     if (statusBar_ != NULL)
@@ -80,19 +101,12 @@
   }
 
 
-  void WidgetBase::WorkerThread(WidgetBase* that)
-  {
-    while (that->started_)
-    {
-      that->UpdateStep();
-    }
-  }
-
-
   WidgetBase::WidgetBase() :
+    parent_(NULL),
+    viewport_(NULL),
     statusBar_(NULL),
-    started_(false),
-    backgroundCleared_(false)
+    backgroundCleared_(false),
+    transmitMouseOver_(false)
   {
     backgroundColor_[0] = 0;
     backgroundColor_[1] = 0;
@@ -100,6 +114,19 @@
   }
 
 
+  void WidgetBase::SetViewport(IViewport& 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)
@@ -119,53 +146,6 @@
   }
 
 
-  void WidgetBase::Register(IChangeObserver& observer)
-  {
-    observers_.Register(observer);
-  }
-
-
-  void WidgetBase::Unregister(IChangeObserver& observer)
-  {
-    observers_.Unregister(observer);
-  }
-
-
-  void WidgetBase::Start()
-  {
-    if (started_)
-    {
-      LOG(ERROR) << "Cannot Start() twice";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    started_ = true;
-
-    if (HasUpdateThread())
-    {
-      thread_ = boost::thread(WorkerThread, this);
-    }
-  }
-
-
-  void WidgetBase::Stop()
-  {
-    if (!started_)
-    {
-      LOG(ERROR) << "Cannot Stop() if Start() has not been invoked";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    started_ = false;
-
-    if (HasUpdateThread() &&
-        thread_.joinable())
-    {
-      thread_.join();
-    }
-  }
-
-
   bool WidgetBase::Render(Orthanc::ImageAccessor& surface)
   {
 #if 0
@@ -176,4 +156,10 @@
 
     return true;
   }
+
+  
+  void WidgetBase::UpdateContent()
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
 }
--- a/Framework/Widgets/WidgetBase.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/WidgetBase.h	Tue Mar 20 20:03:02 2018 +0100
@@ -24,21 +24,18 @@
 #include "IWidget.h"
 
 #include "../Viewport/CairoContext.h"
-#include "../Toolbox/ObserversRegistry.h"
-
-#include <boost/thread.hpp>
 
 namespace OrthancStone
 {
   class WidgetBase : public IWidget
   {
   private:
-    IStatusBar*                  statusBar_;
-    ObserversRegistry<IWidget>   observers_;
-    bool                         started_;
-    boost::thread                thread_;
-    bool                         backgroundCleared_;
-    uint8_t                      backgroundColor_[3];
+    IWidget*     parent_;
+    IViewport*   viewport_;
+    IStatusBar*  statusBar_;
+    bool         backgroundCleared_;
+    uint8_t      backgroundColor_[3];
+    bool         transmitMouseOver_;
 
   protected:
     void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const;
@@ -47,28 +44,23 @@
 
     void ClearBackgroundCairo(Orthanc::ImageAccessor& target) const;
 
-    void NotifyChange();
-
     void UpdateStatusBar(const std::string& message);
 
-    static void WorkerThread(WidgetBase* that);
-
     IStatusBar* GetStatusBar() const
     {
       return statusBar_;
     }
 
-    virtual bool HasUpdateThread() const = 0;
-
-    virtual void UpdateStep() = 0;
-
   public:
     WidgetBase();
 
-    bool IsStarted() const
+    virtual void SetDefaultView()
     {
-      return started_;
     }
+  
+    virtual void SetParent(IWidget& parent);
+    
+    virtual void SetViewport(IViewport& viewport);
 
     void SetBackgroundCleared(bool clear)
     {
@@ -80,6 +72,11 @@
       return backgroundCleared_;
     }
 
+    void SetTransmitMouseOver(bool transmit)
+    {
+      transmitMouseOver_ = transmit;
+    }
+
     void SetBackgroundColor(uint8_t red,
                             uint8_t green,
                             uint8_t blue);
@@ -88,24 +85,25 @@
                             uint8_t& green,
                             uint8_t& blue) const;
 
-    virtual void Register(IChangeObserver& observer);
-
-    virtual void Unregister(IChangeObserver& observer);
-    
     virtual void SetStatusBar(IStatusBar& statusBar)
     {
       statusBar_ = &statusBar;
     }
 
-    virtual void ResetStatusBar()
+    virtual bool Render(Orthanc::ImageAccessor& surface);
+
+    virtual bool HasUpdateContent() const
     {
-      statusBar_ = NULL;
-    }    
+      return false;
+    }
 
-    virtual void Start();
+    virtual void UpdateContent();
 
-    virtual void Stop();
+    virtual bool HasRenderMouseOver()
+    {
+      return transmitMouseOver_;
+    }
 
-    virtual bool Render(Orthanc::ImageAccessor& surface);
+    virtual void NotifyChange();
   };
 }
--- a/Framework/Widgets/WorldSceneWidget.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,9 @@
 
 #include "WorldSceneWidget.h"
 
-#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include <math.h>
+#include <memory>
+#include <cassert>
 
 namespace OrthancStone
 {
@@ -40,23 +42,6 @@
   }
 
 
-  struct WorldSceneWidget::ViewChangeFunctor
-  {
-    const ViewportGeometry& view_;
-
-    ViewChangeFunctor(const ViewportGeometry& view) :
-      view_(view)
-    {
-    }
-
-    void operator() (IWorldObserver& observer,
-                     const WorldSceneWidget& source)
-    {
-      observer.NotifyViewChange(source, view_);
-    }
-  };
-
-
   struct WorldSceneWidget::SizeChangeFunctor
   {
     ViewportGeometry& view_;
@@ -129,8 +114,7 @@
       downX_(x),
       downY_(y)
     {
-      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
-      locker.GetValue().GetPan(previousPanX_, previousPanY_);
+      that_.view_.GetPan(previousPanX_, previousPanY_);
     }
 
     virtual void Render(Orthanc::ImageAccessor& surface)
@@ -144,12 +128,10 @@
     virtual void MouseMove(int x, 
                            int y)
     {
-      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
-      locker.GetValue().SetPan(previousPanX_ + x - downX_,
-                               previousPanY_ + y - downY_);
+      that_.view_.SetPan(previousPanX_ + x - downX_,
+                         previousPanY_ + y - downY_);
 
-      ViewChangeFunctor functor(locker.GetValue());
-      that_.observers_.Notify(&that_, functor);
+      that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_);
     }
   };
 
@@ -172,9 +154,8 @@
       downX_(x),
       downY_(y)
     {
-      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
-      oldZoom_ = locker.GetValue().GetZoom();
-      MapMouseToScene(centerX_, centerY_, locker.GetValue(), downX_, downY_);
+      oldZoom_ = that_.view_.GetZoom();
+      MapMouseToScene(centerX_, centerY_, that_.view_, downX_, downY_);
     }
 
     virtual void Render(Orthanc::ImageAccessor& surface)
@@ -191,15 +172,13 @@
       static const double MIN_ZOOM = -4;
       static const double MAX_ZOOM = 4;
 
-      SharedValue<ViewportGeometry>::Locker locker(that_.view_);
-
-      if (locker.GetValue().GetDisplayHeight() <= 3)
+      if (that_.view_.GetDisplayHeight() <= 3)
       {
         return;   // Cannot zoom on such a small image
       }
 
       double dy = (static_cast<double>(y - downY_) / 
-                   static_cast<double>(locker.GetValue().GetDisplayHeight() - 1)); // In the range [-1,1]
+                   static_cast<double>(that_.view_.GetDisplayHeight() - 1)); // In the range [-1,1]
       double z;
 
       // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
@@ -218,42 +197,27 @@
 
       z = pow(2.0, z);
 
-      locker.GetValue().SetZoom(oldZoom_ * z);
+      that_.view_.SetZoom(oldZoom_ * z);
 
       // Correct the pan so that the original click point is kept at
       // the same location on the display
       double panX, panY;
-      locker.GetValue().GetPan(panX, panY);
+      that_.view_.GetPan(panX, panY);
 
       int tx, ty;
-      locker.GetValue().MapSceneToDisplay(tx, ty, centerX_, centerY_);
-      locker.GetValue().SetPan(panX + static_cast<double>(downX_ - tx),
+      that_.view_.MapSceneToDisplay(tx, ty, centerX_, centerY_);
+      that_.view_.SetPan(panX + static_cast<double>(downX_ - tx),
                                panY + static_cast<double>(downY_ - ty));
 
-      ViewChangeFunctor functor(locker.GetValue());
-      that_.observers_.Notify(&that_, functor);
+      that_.observers_.Apply(that_, &IWorldObserver::NotifyViewChange, that_.view_);
     }
   };
 
 
-  void WorldSceneWidget::UpdateStep()
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-  }
-
-
   bool WorldSceneWidget::RenderCairo(CairoContext& context)
   {
-    ViewportGeometry view;
-
-    {
-      SharedValue<ViewportGeometry>::Locker locker(view_);
-      view = locker.GetValue();
-    }
-
-    view.ApplyTransform(context);
-
-    return RenderScene(context, view);
+    view_.ApplyTransform(context);
+    return RenderScene(context, view_);
   }
 
 
@@ -270,11 +234,9 @@
   }
 
 
-  void WorldSceneWidget::SetSceneExtent(SharedValue<ViewportGeometry>::Locker& locker)
+  void WorldSceneWidget::SetSceneExtent(ViewportGeometry& view)
   {
-    double x1, y1, x2, y2;
-    GetSceneExtent(x1, y1, x2, y2);
-    locker.GetValue().SetSceneExtent(x1, y1, x2, y2);
+    view.SetSceneExtent(GetSceneExtent());
   }
 
 
@@ -283,89 +245,52 @@
   {
     CairoWidget::SetSize(width, height);
 
-    {
-      SharedValue<ViewportGeometry>::Locker locker(view_);
-      locker.GetValue().SetDisplaySize(width, height);
+    view_.SetDisplaySize(width, height);
 
-      if (observers_.IsEmpty())
-      {
-        // Without a size observer, use the default view
-        locker.GetValue().SetDefaultView();
-      }
-      else
-      {
-        // With a size observer, let it decide which view to use
-        SizeChangeFunctor functor(locker.GetValue());
-        observers_.Notify(this, functor);
-      }
+    if (observers_.IsEmpty())
+    {
+      // Without a size observer, reset to the default view
+      // view_.SetDefaultView();
+    }
+    else
+    {
+      // With a size observer, let it decide which view to use
+      SizeChangeFunctor functor(view_);
+      observers_.Notify(*this, functor);
     }
   }
 
 
   void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor)
   {
-    if (IsStarted())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
     interactor_ = &interactor;
   }
 
 
-  void WorldSceneWidget::Start()
-  {
-    ViewportGeometry geometry;
-
-    {
-      SharedValue<ViewportGeometry>::Locker locker(view_);
-      SetSceneExtent(locker);
-      geometry = locker.GetValue();
-    }
-
-    WidgetBase::Start();
-
-    ViewChangeFunctor functor(geometry);
-    observers_.Notify(this, functor);
-  }
-      
-
   void WorldSceneWidget::SetDefaultView()
   {
-    ViewportGeometry geometry;
-
-    {
-      SharedValue<ViewportGeometry>::Locker locker(view_);
-      SetSceneExtent(locker);
-      locker.GetValue().SetDefaultView();
-      geometry = locker.GetValue();
-    }
+    SetSceneExtent(view_);
+    view_.SetDefaultView();
 
     NotifyChange();
 
-    ViewChangeFunctor functor(geometry);
-    observers_.Notify(this, functor);
+    observers_.Apply(*this, &IWorldObserver::NotifyViewChange, view_);
   }
 
 
   void WorldSceneWidget::SetView(const ViewportGeometry& view)
   {
-    {
-      SharedValue<ViewportGeometry>::Locker locker(view_);
-      locker.GetValue() = view;
-    }
+    view_ = view;
 
     NotifyChange();
 
-    ViewChangeFunctor functor(view);
-    observers_.Notify(this, functor);
+    observers_.Apply(*this, &IWorldObserver::NotifyViewChange, view_);
   }
 
 
   ViewportGeometry WorldSceneWidget::GetView()
   {
-    SharedValue<ViewportGeometry>::Locker locker(view_);
-    return locker.GetValue();
+    return view_;
   }
 
 
@@ -374,15 +299,15 @@
                                                       int y,
                                                       KeyboardModifiers modifiers)
   {
-    ViewportGeometry view = GetView();
+    double sceneX, sceneY;
+    MapMouseToScene(sceneX, sceneY, view_, x, y);
 
-    double sceneX, sceneY;
-    MapMouseToScene(sceneX, sceneY, view, x, y);
+    std::auto_ptr<IWorldSceneMouseTracker> tracker
+      (CreateMouseSceneTracker(view_, button, sceneX, sceneY, modifiers));
 
-    std::auto_ptr<IWorldSceneMouseTracker> tracker(CreateMouseSceneTracker(view, button, sceneX, sceneY, modifiers));
     if (tracker.get() != NULL)
     {
-      return new SceneMouseTracker(view, tracker.release());
+      return new SceneMouseTracker(view_, tracker.release());
     }
 
     switch (button)
@@ -406,7 +331,7 @@
   {
     if (interactor_)
     {
-      interactor_->MouseOver(context, *this, GetSlice(), view, x, y, GetStatusBar());
+      interactor_->MouseOver(context, *this, view, x, y, GetStatusBar());
     }
   }
 
@@ -418,7 +343,7 @@
   {
     if (interactor_)
     {
-      return interactor_->CreateMouseTracker(*this, GetSlice(), view, button, x, y, GetStatusBar());
+      return interactor_->CreateMouseTracker(*this, view, button, x, y, GetStatusBar());
     }
     else
     {
--- a/Framework/Widgets/WorldSceneWidget.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Framework/Widgets/WorldSceneWidget.h	Tue Mar 20 20:03:02 2018 +0100
@@ -24,7 +24,7 @@
 #include "CairoWidget.h"
 #include "IWorldSceneInteractor.h"
 
-#include "../Toolbox/SharedValue.h"
+#include "../Toolbox/ObserversRegistry.h"
 #include "../Toolbox/ViewportGeometry.h"
 
 namespace OrthancStone
@@ -32,7 +32,6 @@
   class WorldSceneWidget : public CairoWidget
   {
   public:
-    // Must be thread-safe
     class IWorldObserver : public boost::noncopyable
     {
     public:
@@ -48,7 +47,6 @@
     };
 
   private:
-    struct ViewChangeFunctor;
     struct SizeChangeFunctor;
 
     class SceneMouseTracker;
@@ -57,29 +55,24 @@
 
     typedef ObserversRegistry<WorldSceneWidget, IWorldObserver>  Observers;
 
-    SharedValue<ViewportGeometry>  view_;
-    Observers                      observers_;
-    IWorldSceneInteractor*         interactor_;
+    ViewportGeometry       view_;
+    Observers              observers_;
+    IWorldSceneInteractor* interactor_;
 
+  public:
+    virtual Extent2D GetSceneExtent() = 0;
 
   protected:
     virtual bool RenderScene(CairoContext& context,
                              const ViewportGeometry& view) = 0;
 
-    virtual bool HasUpdateThread() const
-    {
-      return false;
-    }
-
-    virtual void UpdateStep();
-
     virtual bool RenderCairo(CairoContext& context);
 
     virtual void RenderMouseOverCairo(CairoContext& context,
                                       int x,
                                       int y);
 
-    void SetSceneExtent(SharedValue<ViewportGeometry>::Locker& locker);
+    void SetSceneExtent(ViewportGeometry& geometry);
 
   public:
     WorldSceneWidget() :
@@ -87,9 +80,6 @@
     {
     }
 
-    using WidgetBase::Register;
-    using WidgetBase::Unregister;
-
     void Register(IWorldObserver& observer)
     {
       observers_.Register(observer);
@@ -100,21 +90,12 @@
       observers_.Unregister(observer);
     }
 
-    virtual SliceGeometry GetSlice() = 0;
-
-    virtual void GetSceneExtent(double& x1,
-                                double& y1,
-                                double& x2,
-                                double& y2) = 0;
-
     virtual void SetSize(unsigned int width,
                          unsigned int height);
 
     void SetInteractor(IWorldSceneInteractor& interactor);
 
-    virtual void Start();
-      
-    void SetDefaultView();
+    virtual void SetDefaultView();
 
     void SetView(const ViewportGeometry& view);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/dev.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,869 @@
+/**
+ * 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 "Layers/FrameRenderer.h"
+#include "Layers/LayerSourceBase.h"
+#include "Layers/SliceOutlineRenderer.h"
+#include "Layers/LineLayerRenderer.h"
+#include "Widgets/LayerWidget.h"
+#include "Toolbox/DownloadStack.h"
+#include "Toolbox/GeometryToolbox.h"
+#include "Toolbox/OrthancSlicesLoader.h"
+#include "Volumes/ImageBuffer3D.h"
+#include "Volumes/SlicedVolumeBase.h"
+
+#include <Core/Logging.h>
+#include <Core/Images/ImageProcessing.h>
+
+#include <boost/math/special_functions/round.hpp>
+
+
+namespace OrthancStone
+{
+  // TODO: Handle errors while loading
+  class OrthancVolumeImage : 
+    public SlicedVolumeBase,
+    private OrthancSlicesLoader::ICallback
+  { 
+  private:
+    OrthancSlicesLoader           loader_;
+    std::auto_ptr<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, SliceImageQuality_Jpeg90);
+      }
+    }
+
+
+    static bool IsCompatible(const Slice& a, 
+                             const Slice& b)
+    {
+      if (!GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(),
+                                       b.GetGeometry().GetNormal()))
+      {
+        LOG(ERROR) << "Some 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 width/height of the slices change across the volume image";
+        return false;
+      }
+
+      if (!LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
+          !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()));
+    }
+
+
+    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader)
+    {
+      if (loader.GetSliceCount() == 0)
+      {
+        LOG(ERROR) << "Empty volume image";
+        SlicedVolumeBase::NotifyGeometryError();
+        return;
+      }
+
+      for (size_t i = 1; i < loader.GetSliceCount(); i++)
+      {
+        if (!IsCompatible(loader.GetSlice(0), loader.GetSlice(i)))
+        {
+          SlicedVolumeBase::NotifyGeometryError();
+          return;
+        }
+      }
+
+      double spacingZ;
+
+      if (loader.GetSliceCount() > 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.GetSliceCount(); i++)
+      {
+        if (!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";
+          SlicedVolumeBase::NotifyGeometryError();
+          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.GetSliceCount() << " in " << Orthanc::EnumerationToString(format);
+
+      image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount(), computeRange_));
+      image_->SetAxialGeometry(loader.GetSlice(0).GetGeometry());
+      image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), 
+                                 loader.GetSlice(0).GetPixelSpacingY(), spacingZ);
+      image_->Clear();
+      
+      downloadStack_.reset(new DownloadStack(loader.GetSliceCount()));
+      pendingSlices_ = loader.GetSliceCount();
+
+      for (unsigned int i = 0; i < 4; i++)  // Limit to 4 simultaneous downloads
+      {
+        ScheduleSliceDownload();
+      }
+
+      // TODO Check the DicomFrameConverter are constant
+
+      SlicedVolumeBase::NotifyGeometryReady();
+    }
+
+    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader)
+    {
+      LOG(ERROR) << "Unable to download a volume image";
+      SlicedVolumeBase::NotifyGeometryError();
+    }
+
+    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
+                                       SliceImageQuality quality)
+    {
+      {
+        ImageBuffer3D::SliceWriter writer(*image_, VolumeProjection_Axial, sliceIndex);
+        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), *image);
+      }
+
+      SlicedVolumeBase::NotifySliceChange(sliceIndex, slice);     
+
+      if (pendingSlices_ == 1)
+      {
+        SlicedVolumeBase::NotifyVolumeReady();
+        pendingSlices_ = 0;
+      }
+      else if (pendingSlices_ > 1)
+      {
+        pendingSlices_ -= 1;
+      }
+
+      ScheduleSliceDownload();
+    }
+
+    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       SliceImageQuality quality)
+    {
+      LOG(ERROR) << "Cannot download slice " << sliceIndex << " in a volume image";
+      ScheduleSliceDownload();
+    }
+
+  public:
+    OrthancVolumeImage(IWebService& orthanc,
+                       bool computeRange) : 
+      loader_(*this, orthanc),
+      computeRange_(computeRange),
+      pendingSlices_(0)
+    {
+    }
+
+    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 GetSliceCount() const
+    {
+      return loader_.GetSliceCount();
+    }
+
+    virtual const Slice& GetSlice(size_t index) const
+    {
+      return loader_.GetSlice(index);
+    }
+
+    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_;
+    CoordinateSystem3D   reference_;
+    DicomFrameConverter  converter_;
+    
+    double ComputeAxialThickness(const OrthancVolumeImage& volume) const
+    {
+      double thickness;
+      
+      size_t n = volume.GetSliceCount();
+      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.GetSliceCount();
+
+      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_ = volume.GetSliceCount();
+      depth_ = axial.GetHeight();
+
+      pixelSpacingX_ = axial.GetPixelSpacingX();
+      pixelSpacingY_ = axialThickness;
+      sliceThickness_ = axial.GetPixelSpacingY();
+
+      Vector origin = axial.GetGeometry().GetOrigin();
+      origin += (static_cast<double>(volume.GetSliceCount() - 1) *
+                 axialThickness * axial.GetGeometry().GetNormal());
+      
+      reference_ = 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_ = volume.GetSliceCount();
+      depth_ = axial.GetWidth();
+
+      pixelSpacingX_ = axial.GetPixelSpacingY();
+      pixelSpacingY_ = axialThickness;
+      sliceThickness_ = axial.GetPixelSpacingX();
+
+      Vector origin = axial.GetGeometry().GetOrigin();
+      origin += (static_cast<double>(volume.GetSliceCount() - 1) *
+                 axialThickness * axial.GetGeometry().GetNormal());
+      
+      reference_ = CoordinateSystem3D(origin,
+                                      axial.GetGeometry().GetAxisY(), 
+                                      axial.GetGeometry().GetNormal());
+    }
+
+  public:
+    VolumeImageGeometry(const OrthancVolumeImage& volume,
+                        VolumeProjection projection)
+    {
+      if (volume.GetSliceCount() == 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      converter_ = volume.GetSlice(0).GetConverter();
+
+      switch (projection)
+      {
+        case VolumeProjection_Axial:
+          SetupAxial(volume);
+          break;
+
+        case VolumeProjection_Coronal:
+          SetupCoronal(volume);
+          break;
+
+        case VolumeProjection_Sagittal:
+          SetupSagittal(volume);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    size_t GetSliceCount() const
+    {
+      return depth_;
+    }
+
+    const Vector& GetNormal() const
+    {
+      return reference_.GetNormal();
+    }
+    
+    bool LookupSlice(size_t& index,
+                     const CoordinateSystem3D& slice) const
+    {
+      bool opposite;
+      if (!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 < 0 ||
+          slice >= depth_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        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 VolumeImageSource :
+    public LayerSourceBase,
+    private ISlicedVolume::IObserver
+  {
+  private:
+    OrthancVolumeImage&                 volume_;
+    std::auto_ptr<VolumeImageGeometry>  axialGeometry_;
+    std::auto_ptr<VolumeImageGeometry>  coronalGeometry_;
+    std::auto_ptr<VolumeImageGeometry>  sagittalGeometry_;
+
+    
+    bool IsGeometryReady() const
+    {
+      return axialGeometry_.get() != NULL;
+    }
+
+    
+    virtual void NotifyGeometryReady(const ISlicedVolume& volume)
+    {
+      // These 3 values are only used to speed up the ILayerSource
+      axialGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Axial));
+      coronalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Coronal));
+      sagittalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Sagittal));
+      
+      LayerSourceBase::NotifyGeometryReady();
+    }
+      
+    virtual void NotifyGeometryError(const ISlicedVolume& volume)
+    {
+      LayerSourceBase::NotifyGeometryError();
+    }
+      
+    virtual void NotifyContentChange(const ISlicedVolume& volume)
+    {
+      LayerSourceBase::NotifyContentChange();
+    }
+
+    virtual void NotifySliceChange(const ISlicedVolume& volume,
+                                   const size_t& sliceIndex,
+                                   const Slice& slice)
+    {
+      //LayerSourceBase::NotifySliceChange(slice);
+
+      // TODO Improve this?
+      LayerSourceBase::NotifyContentChange();
+    }
+
+    virtual void NotifyVolumeReady(const ISlicedVolume& volume)
+    {
+    }
+
+    const VolumeImageGeometry& GetProjectionGeometry(VolumeProjection projection)
+    {
+      if (!IsGeometryReady())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      switch (projection)
+      {
+        case VolumeProjection_Axial:
+          return *axialGeometry_;
+
+        case VolumeProjection_Sagittal:
+          return *sagittalGeometry_;
+
+        case VolumeProjection_Coronal:
+          return *coronalGeometry_;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+
+    bool DetectProjection(VolumeProjection& projection,
+                          const CoordinateSystem3D& viewportSlice)
+    {
+      bool isOpposite;  // Ignored
+
+      if (GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                viewportSlice.GetNormal(),
+                                                axialGeometry_->GetNormal()))
+      {
+        projection = VolumeProjection_Axial;
+        return true;
+      }
+      else if (GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                     viewportSlice.GetNormal(),
+                                                     sagittalGeometry_->GetNormal()))
+      {
+        projection = VolumeProjection_Sagittal;
+        return true;
+      }
+      else if (GeometryToolbox::IsParallelOrOpposite(isOpposite,
+                                                     viewportSlice.GetNormal(),
+                                                     coronalGeometry_->GetNormal()))
+      {
+        projection = VolumeProjection_Coronal;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+  public:
+    VolumeImageSource(OrthancVolumeImage&  volume) :
+      volume_(volume)
+    {
+      volume_.Register(*this);
+    }
+
+    virtual bool GetExtent(std::vector<Vector>& points,
+                           const CoordinateSystem3D& viewportSlice)
+    {
+      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 CoordinateSystem3D& viewportSlice)
+    {
+      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;
+
+          {
+            ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, 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));
+          LayerSourceBase::NotifyLayerReady(
+            FrameRenderer::CreateRenderer(frame.release(), *slice, isFullQuality),
+            //new SliceOutlineRenderer(slice),
+            slice->GetGeometry(), false);
+          return;
+        }
+      }
+
+      // Error
+      CoordinateSystem3D slice;
+      LayerSourceBase::NotifyLayerReady(NULL, slice, true);
+    }
+  };
+
+
+  class VolumeImageInteractor :
+    public IWorldSceneInteractor,
+    protected ISlicedVolume::IObserver
+  {
+  private:
+    LayerWidget&                        widget_;
+    VolumeProjection                    projection_;
+    std::auto_ptr<VolumeImageGeometry>  slices_;
+    size_t                              slice_;
+
+  protected:
+    virtual void NotifyGeometryReady(const ISlicedVolume& volume)
+    {
+      if (slices_.get() == NULL)
+      {
+        const OrthancVolumeImage& image = dynamic_cast<const OrthancVolumeImage&>(volume);
+
+        slices_.reset(new VolumeImageGeometry(image, projection_));
+        SetSlice(slices_->GetSliceCount() / 2);
+
+        widget_.SetDefaultView();
+      }
+    }
+      
+    virtual void NotifyGeometryError(const ISlicedVolume& volume)
+    {
+    }
+      
+    virtual void NotifyContentChange(const ISlicedVolume& volume)
+    {
+    }
+
+    virtual void NotifySliceChange(const ISlicedVolume& volume,
+                                   const size_t& sliceIndex,
+                                   const Slice& slice)
+    {
+    }
+
+    virtual void NotifyVolumeReady(const ISlicedVolume& volume)
+    {
+    }
+
+    virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                        const ViewportGeometry& view,
+                                                        MouseButton button,
+                                                        double x,
+                                                        double y,
+                                                        IStatusBar* statusBar)
+    {
+      return NULL;
+    }
+
+    virtual void MouseOver(CairoContext& context,
+                           WorldSceneWidget& widget,
+                           const ViewportGeometry& view,
+                           double x,
+                           double y,
+                           IStatusBar* statusBar)
+    {
+    }
+
+    virtual void MouseWheel(WorldSceneWidget& widget,
+                            MouseWheelDirection direction,
+                            KeyboardModifiers modifiers,
+                            IStatusBar* statusBar)
+    {
+      int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1);
+          
+      switch (direction)
+      {
+        case MouseWheelDirection_Up:
+          OffsetSlice(-scale);
+          break;
+
+        case MouseWheelDirection_Down:
+          OffsetSlice(scale);
+          break;
+
+        default:
+          break;
+      }
+    }
+
+    virtual void KeyPressed(WorldSceneWidget& widget,
+                            char key,
+                            KeyboardModifiers modifiers,
+                            IStatusBar* statusBar)
+    {
+      switch (key)
+      {
+        case 's':
+          widget.SetDefaultView();
+          break;
+
+        default:
+          break;
+      }
+    }
+      
+  public:
+    VolumeImageInteractor(OrthancVolumeImage& volume,
+                          LayerWidget& widget,
+                          VolumeProjection projection) :
+      widget_(widget),
+      projection_(projection)
+    {
+      volume.Register(*this);
+      widget.SetInteractor(*this);
+    }
+
+    bool IsGeometryReady() const
+    {
+      return slices_.get() != NULL;
+    }
+
+    size_t GetSliceCount() const
+    {
+      if (slices_.get() == NULL)
+      {
+        return 0;
+      }
+      else
+      {
+        return slices_->GetSliceCount();
+      }
+    }
+
+    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_->GetSliceCount()))
+        {
+          slice = slices_->GetSliceCount() - 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 SliceLocationSource : public LayerSourceBase
+  {
+  private:
+    LayerWidget&  otherPlane_;
+
+  public:
+    SliceLocationSource(LayerWidget&  otherPlane) :
+      otherPlane_(otherPlane)
+    {
+      NotifyGeometryReady();
+    }
+
+    virtual bool GetExtent(std::vector<Vector>& points,
+                           const CoordinateSystem3D& viewportSlice)
+    {
+      return false;
+    }
+
+    virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice)
+    {
+      Slice reference(viewportSlice, 0.001);
+      
+      Vector p, d;
+
+      const CoordinateSystem3D& slice = otherPlane_.GetSlice();
+
+      // Compute the line of intersection between the two slices
+      if (!GeometryToolbox::IntersectTwoPlanes(p, d, 
+                                               slice.GetOrigin(), slice.GetNormal(),
+                                               viewportSlice.GetOrigin(), viewportSlice.GetNormal()))
+      {
+        // The two slice are parallel, don't try and display the intersection
+        NotifyLayerReady(NULL, reference.GetGeometry(), false);
+      }
+      else
+      {
+        double x1, y1, x2, y2;
+        viewportSlice.ProjectPoint(x1, y1, p);
+        viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d);
+
+        const Extent2D extent = otherPlane_.GetSceneExtent();
+        
+        if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, 
+                                                 x1, y1, x2, y2,
+                                                 extent.GetX1(), extent.GetY1(),
+                                                 extent.GetX2(), extent.GetY2()))
+        {
+          NotifyLayerReady(new LineLayerRenderer(x1, y1, x2, y2, slice), reference.GetGeometry(), false);
+        }
+        else
+        {
+          // Parallel slices
+          NotifyLayerReady(NULL, reference.GetGeometry(), false);
+        }
+      }
+    }      
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/CMakeLists.txt	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancStone)
+
+
+#####################################################################
+## Build a static library containing the Orthanc Stone framework
+#####################################################################
+
+include(../../Resources/CMake/OrthancStoneParameters.cmake)
+
+LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options)
+
+SET(ORTHANC_SANDBOXED OFF)
+SET(ENABLE_CRYPTO_OPTIONS ON)
+SET(ENABLE_GOOGLE_TEST ON)
+SET(ENABLE_WEB_CLIENT ON)
+
+include(../../Resources/CMake/OrthancStoneConfiguration.cmake)
+
+add_library(OrthancStone STATIC
+  ${ORTHANC_STONE_SOURCES}
+  )
+
+
+#####################################################################
+## Build all the sample applications
+#####################################################################
+
+macro(BuildSample Target Sample)
+  add_executable(${Target}
+    ${ORTHANC_STONE_DIR}/Applications/Samples/SampleMainSdl.cpp
+    ${APPLICATIONS_SOURCES}
+    )
+  set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
+  target_link_libraries(${Target} OrthancStone)
+endmacro()
+
+
+# TODO - Re-enable all these samples!
+
+BuildSample(OrthancStoneEmpty 1)
+BuildSample(OrthancStoneTestPattern 2)
+BuildSample(OrthancStoneSingleFrame 3)
+BuildSample(OrthancStoneSingleVolume 4)
+#BuildSample(OrthancStoneBasicPetCtFusion 5)
+#BuildSample(OrthancStoneSynchronizedSeries 6)
+#BuildSample(OrthancStoneLayoutPetCtFusion 7)
+
+
+#####################################################################
+## Build the unit tests
+#####################################################################
+
+add_executable(UnitTests
+  ${GOOGLE_TEST_SOURCES}
+  ${ORTHANC_STONE_DIR}/UnitTestsSources/UnitTestsMain.cpp
+  )
+
+target_link_libraries(UnitTests OrthancStone)
+
+
+#####################################################################
+## Generate the documentation if Doxygen is present
+#####################################################################
+
+find_package(Doxygen)
+if (DOXYGEN_FOUND)
+  configure_file(
+    ${ORTHANC_STONE_DIR}/Resources/OrthancStone.doxygen
+    ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen
+    @ONLY)
+
+  add_custom_target(doc
+    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen
+    COMMENT "Generating documentation with Doxygen" VERBATIM
+    )
+else()
+  message("Doxygen not found. The documentation will not be built.")
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/IOracleCommand.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,42 @@
+/**
+ * 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 <Core/IDynamicObject.h>
+
+namespace OrthancStone
+{
+  class IOracleCommand : public Orthanc::IDynamicObject
+  {
+  public:
+    virtual ~IOracleCommand()
+    {
+    }
+
+    // This part of the command can be invoked simultaneously, and
+    // must not modify the Stone context
+    virtual void Execute() = 0;
+
+    // This part of the command must be invoked in mutual exclusion
+    virtual void Commit() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/Oracle.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,220 @@
+/**
+ * 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 "Oracle.h"
+
+#include <Core/Logging.h>
+#include <Core/MultiThreading/SharedMessageQueue.h>
+#include <Core/OrthancException.h>
+
+#include <vector>
+#include <stdio.h>
+
+namespace OrthancStone
+{
+  class Oracle::PImpl
+  {
+  private:
+    enum State
+    {
+      State_Init,
+      State_Started,
+      State_Stopped
+    };
+
+    boost::mutex*                  globalMutex_;
+    boost::mutex                   oracleMutex_;
+    State                          state_;
+    std::vector<boost::thread*>    threads_;
+    Orthanc::SharedMessageQueue    queue_;
+
+    static void Worker(PImpl* that)
+    {
+      for (;;)
+      {
+        State state;
+        
+        {
+          boost::mutex::scoped_lock lock(that->oracleMutex_);
+          state = that->state_;
+        }
+
+        if (state == State_Stopped)
+        {
+          break;
+        }
+
+        std::auto_ptr<Orthanc::IDynamicObject> item(that->queue_.Dequeue(100));
+        if (item.get() != NULL)
+        {
+          IOracleCommand& command = dynamic_cast<IOracleCommand&>(*item);
+          command.Execute();
+
+          // Random sleeping to test
+          //boost::this_thread::sleep(boost::posix_time::milliseconds(50 * (1 + rand() % 10)));
+
+          if (that->globalMutex_ != NULL)
+          {
+            boost::mutex::scoped_lock lock(*that->globalMutex_);
+            command.Commit();
+          }
+          else
+          {
+            command.Commit();
+          }
+        }
+      }
+    }
+    
+  public:
+    PImpl(boost::mutex* globalMutex,
+          unsigned int threadCount) :
+      globalMutex_(globalMutex),
+      state_(State_Init),
+      threads_(threadCount)
+    {
+    }
+
+    ~PImpl()
+    {
+      if (state_ == State_Started)
+      {
+        LOG(ERROR) << "You should have manually called Oracle::Stop()";
+        Stop();
+      }
+    }
+
+    Orthanc::SharedMessageQueue& GetQueue()
+    {
+      return queue_;
+    }
+
+    void Submit(IOracleCommand* command)
+    {
+      std::auto_ptr<IOracleCommand> protection(command);
+
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      
+      boost::mutex::scoped_lock lock(oracleMutex_);
+
+      switch (state_)
+      {
+        case State_Init:
+        case State_Started:
+          queue_.Enqueue(protection.release());
+          break;
+
+        case State_Stopped:
+          LOG(ERROR) << "Cannot schedule a request to the Oracle after having "
+                     << "called Oracle::Stop()";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      
+    }
+
+    void Start()
+    {
+      boost::mutex::scoped_lock lock(oracleMutex_);
+
+      if (state_ != State_Init)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      
+      for (size_t i = 0; i < threads_.size(); i++)
+      {
+        threads_[i] = new boost::thread(Worker, this);
+      }
+
+      state_ = State_Started;
+    }
+
+    void Stop()
+    {
+      {
+        boost::mutex::scoped_lock lock(oracleMutex_);
+
+        if (state_ != State_Started)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+
+        state_ = State_Stopped;
+      }
+      
+      for (size_t i = 0; i < threads_.size(); i++)
+      {
+        if (threads_[i] != NULL)
+        {
+          if (threads_[i]->joinable())
+          {
+            threads_[i]->join();
+          }
+
+          delete threads_[i];
+        }
+      }
+    }
+  };
+  
+
+  Oracle::Oracle(boost::mutex& globalMutex,
+                 unsigned int threadCount) :
+    pimpl_(new PImpl(&globalMutex, threadCount))
+  {
+  }
+
+
+  Oracle::Oracle(unsigned int threadCount) :
+    pimpl_(new PImpl(NULL, threadCount))
+  {
+  }
+
+
+  void Oracle::Start()
+  {
+    pimpl_->Start();
+  }
+
+
+  void Oracle::Submit(IOracleCommand* command)
+  {
+    pimpl_->Submit(command);
+  }
+     
+   
+  void Oracle::Stop()
+  {
+    pimpl_->Stop();
+  }
+
+
+  void Oracle::WaitEmpty()
+  {
+    pimpl_->GetQueue().WaitEmpty(50);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/Oracle.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,52 @@
+/**
+ * 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 "IOracleCommand.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  class Oracle : public boost::noncopyable
+  {
+  private:
+    class PImpl;
+  
+    boost::shared_ptr<PImpl>  pimpl_;
+
+  public:
+    Oracle(boost::mutex& globalMutex,
+           unsigned int threadCount);
+
+    Oracle(unsigned int threadCount);
+
+    void Start();
+
+    void Submit(IOracleCommand* command);
+        
+    void WaitEmpty();  // For unit tests
+
+    void Stop();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/OracleWebService.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,60 @@
+/**
+ * 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/Toolbox/IWebService.h"
+#include "Oracle.h"
+#include "WebServiceGetCommand.h"
+#include "WebServicePostCommand.h"
+
+namespace OrthancStone
+{
+  class OracleWebService : public IWebService
+  {
+  private:
+    Oracle&                        oracle_;
+    Orthanc::WebServiceParameters  parameters_;
+
+  public:
+    OracleWebService(Oracle& oracle,
+                     const Orthanc::WebServiceParameters& parameters) : 
+      oracle_(oracle),
+      parameters_(parameters)
+    {
+    }
+
+    virtual void ScheduleGetRequest(ICallback& callback,
+                                    const std::string& uri,
+                                    Orthanc::IDynamicObject* payload)
+    {
+      oracle_.Submit(new WebServiceGetCommand(callback, parameters_, uri, payload));
+    }
+
+    virtual void SchedulePostRequest(ICallback& callback,
+                                     const std::string& uri,
+                                     const std::string& body,
+                                     Orthanc::IDynamicObject* payload)
+    {
+      oracle_.Submit(new WebServicePostCommand(callback, parameters_, uri, body, payload));
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,60 @@
+/**
+ * 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 "WebServiceGetCommand.h"
+
+#include <Core/HttpClient.h>
+
+namespace OrthancStone
+{
+  WebServiceGetCommand::WebServiceGetCommand(IWebService::ICallback& callback,
+                                             const Orthanc::WebServiceParameters& parameters,
+                                             const std::string& uri,
+                                             Orthanc::IDynamicObject* payload /* takes ownership */) :
+    callback_(callback),
+    parameters_(parameters),
+    uri_(uri),
+    payload_(payload)
+  {
+  }
+
+
+  void WebServiceGetCommand::Execute()
+  {
+    Orthanc::HttpClient client(parameters_, uri_);
+    client.SetTimeout(60);
+    client.SetMethod(Orthanc::HttpMethod_Get);
+    success_ = client.Apply(answer_);
+  }
+
+
+  void WebServiceGetCommand::Commit()
+  {
+    if (success_)
+    {
+      callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release());
+    }
+    else
+    {
+      callback_.NotifyError(uri_, payload_.release());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceGetCommand.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,54 @@
+/**
+ * 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 "IOracleCommand.h"
+
+#include "../../Framework/Toolbox/IWebService.h"
+
+#include <Core/WebServiceParameters.h>
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class WebServiceGetCommand : public IOracleCommand
+  {
+  private:
+    IWebService::ICallback&                 callback_;
+    Orthanc::WebServiceParameters           parameters_;
+    std::string                             uri_;
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+    bool                                    success_;
+    std::string                             answer_;
+
+  public:
+    WebServiceGetCommand(IWebService::ICallback& callback,
+                         const Orthanc::WebServiceParameters& parameters,
+                         const std::string& uri,
+                         Orthanc::IDynamicObject* payload /* takes ownership */);
+
+    virtual void Execute();
+
+    virtual void Commit();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,61 @@
+/**
+ * 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 "WebServicePostCommand.h"
+
+#include <Core/HttpClient.h>
+
+namespace OrthancStone
+{
+  WebServicePostCommand::WebServicePostCommand(IWebService::ICallback& callback,
+                                               const Orthanc::WebServiceParameters& parameters,
+                                               const std::string& uri,
+                                               const std::string& body,
+                                               Orthanc::IDynamicObject* payload /* takes ownership */) :
+    callback_(callback),
+    parameters_(parameters),
+    uri_(uri),
+    body_(body),
+    payload_(payload)
+  {
+  }
+
+  void WebServicePostCommand::Execute()
+  {
+    Orthanc::HttpClient client(parameters_, uri_);
+    client.SetTimeout(60);
+    client.SetMethod(Orthanc::HttpMethod_Post);
+    client.GetBody().swap(body_);
+    success_ = client.Apply(answer_);
+  }
+
+  void WebServicePostCommand::Commit()
+  {
+    if (success_)
+    {
+      callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release());
+    }
+    else
+    {
+      callback_.NotifyError(uri_, payload_.release());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServicePostCommand.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,56 @@
+/**
+ * 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 "IOracleCommand.h"
+
+#include "../../Framework/Toolbox/IWebService.h"
+
+#include <Core/WebServiceParameters.h>
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class WebServicePostCommand : public IOracleCommand
+  {
+  private:
+    IWebService::ICallback&                 callback_;
+    Orthanc::WebServiceParameters           parameters_;
+    std::string                             uri_;
+    std::string                             body_;
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+    bool                                    success_;
+    std::string                             answer_;
+
+  public:
+    WebServicePostCommand(IWebService::ICallback& callback,
+                          const Orthanc::WebServiceParameters& parameters,
+                          const std::string& uri,
+                          const std::string& body,
+                          Orthanc::IDynamicObject* payload /* takes ownership */);
+
+    virtual void Execute();
+
+    virtual void Commit();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/WebAssembly/CMakeLists.txt	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,49 @@
+# Usage (Linux):
+# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake ..
+
+cmake_minimum_required(VERSION 2.8.3)
+
+
+#####################################################################
+## Configuration of the Emscripten compiler for WebAssembly target
+#####################################################################
+
+set(WASM_FLAGS "-s WASM=1")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_SOURCE_DIR}/library.js")
+
+# Handling of memory
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1")  # Resize
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912")  # 512MB
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912")  # 512MB + resize
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824")  # 1GB + resize
+
+# To debug exceptions
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2")
+
+
+#####################################################################
+## Build a static library containing the Orthanc Stone framework
+#####################################################################
+
+include(../../Resources/CMake/OrthancStoneParameters.cmake)
+
+SET(ORTHANC_SANDBOXED ON)
+SET(ENABLE_SDL OFF)
+
+include(../../Resources/CMake/OrthancStoneConfiguration.cmake)
+
+add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES})
+
+
+
+
+
+
+# Regenerate a dummy "library.c" file each time the "library.js" file
+# is modified, so as to force a new execution of the linking
+add_custom_command(
+    OUTPUT "${AUTOGENERATED_DIR}/library.c"
+    COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/library.c" ""
+    DEPENDS "${CMAKE_SOURCE_DIR}/library.js")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/WebAssembly/library.js	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,2 @@
+mergeInto(LibraryManager.library, {
+});
--- a/Resources/CMake/BoostExtendedConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ b/Resources/CMake/BoostExtendedConfiguration.cmake	Tue Mar 20 20:03:02 2018 +0100
@@ -17,12 +17,8 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
-SET(ORTHANC_BOOST_COMPONENTS program_options)
-
-include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
-
 if (BOOST_STATIC)
-  list(APPEND BOOST_SOURCES
+  list(APPEND BOOST_EXTENDED_SOURCES
     ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp
     ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp
     ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp
--- a/Resources/CMake/CairoConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ b/Resources/CMake/CairoConfiguration.cmake	Tue Mar 20 20:03:02 2018 +0100
@@ -21,9 +21,9 @@
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_CAIRO)
-  SET(CAIRO_SOURCES_DIR ${CMAKE_BINARY_DIR}/cairo-1.14.6)
-  SET(CAIRO_URL "http://www.orthanc-server.com/downloads/third-party/Stone/cairo-1.14.6.tar.xz")
-  SET(CAIRO_MD5 "23a0b2f0235431d35238df1d3a517fdb")
+  SET(CAIRO_SOURCES_DIR ${CMAKE_BINARY_DIR}/cairo-1.14.12)
+  SET(CAIRO_URL "http://www.orthanc-server.com/downloads/third-party/Stone/cairo-1.14.12.tar.xz")
+  SET(CAIRO_MD5 "9f0db9dbfca0966be8acd682e636d165")
 
   DownloadPackage(${CAIRO_MD5} ${CAIRO_URL} "${CAIRO_SOURCES_DIR}")
 
--- a/Resources/CMake/OrthancStone.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +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/>.
-
-
-# Version of the build, should always be "mainline" except in release branches
-set(ORTHANC_STONE_VERSION "mainline")
-
-#####################################################################
-## Parameters of the build
-#####################################################################
-
-# Generic parameters
-SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
-SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
-SET(STONE_SANDBOXED OFF CACHE BOOL "Whether Stone runs inside a sandboxed environment (such as Google NaCl)")
-
-# Optional components
-SET(ENABLE_CURL ON CACHE BOOL "Include support for libcurl")
-SET(ENABLE_SSL OFF CACHE BOOL "Include support for SSL")
-SET(ENABLE_SDL ON CACHE BOOL "Include support for SDL")
-SET(ENABLE_LOGGING ON CACHE BOOL "Enable logging facilities from Orthanc")
-
-# Advanced parameters to fine-tune linking against system libraries
-SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
-SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
-SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib")
-SET(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo")
-SET(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
-SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng")
-SET(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg")
-SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl")
-SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL")
-SET(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
-
-
-#####################################################################
-## Configure mandatory third-party components
-#####################################################################
-
-SET(ORTHANC_STONE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
-SET(ORTHANC_ROOT ${ORTHANC_STONE_DIR}/Resources/Orthanc)
-
-include(CheckIncludeFiles)
-include(CheckIncludeFileCXX)
-include(CheckLibraryExists)
-include(FindPythonInterp)
-include(FindPkgConfig)
-
-include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
-
-include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/LibJpegConfiguration.cmake)
-
-include(${CMAKE_CURRENT_LIST_DIR}/BoostExtendedConfiguration.cmake)
-include(${CMAKE_CURRENT_LIST_DIR}/CairoConfiguration.cmake)
-include(${CMAKE_CURRENT_LIST_DIR}/PixmanConfiguration.cmake)
-
-if (MSVC)
-  # Remove some warnings on Visual Studio 2015
-  add_definitions(-D_SCL_SECURE_NO_WARNINGS=1) 
-endif()
-
-
-#####################################################################
-## Configure optional third-party components
-#####################################################################
-
-if (STONE_SANDBOXED)
-  add_definitions(
-    -DORTHANC_ENABLE_CURL=0
-    -DORTHANC_ENABLE_LOGGING=0
-    -DORTHANC_ENABLE_SDL=0
-    -DORTHANC_ENABLE_SSL=0
-    -DORTHANC_SANDBOXED=1
-    )
-else()
-  list(APPEND ORTHANC_STONE_SOURCES
-    ${ORTHANC_ROOT}/Core/HttpClient.cpp
-    ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
-    ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancHttpConnection.cpp
-    )
-
-  add_definitions(
-    -DORTHANC_SANDBOXED=0
-    -DORTHANC_ENABLE_LOGGING_PLUGIN=0
-    )
-
-  if (ENABLE_LOGGING)
-    add_definitions(-DORTHANC_ENABLE_LOGGING=1)
-  else()
-    add_definitions(-DORTHANC_ENABLE_LOGGING=0)
-  endif()
-
-  if (ENABLE_SDL)
-    include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)  
-    add_definitions(-DORTHANC_ENABLE_SDL=1)
-  else()
-    add_definitions(-DORTHANC_ENABLE_SDL=0)
-  endif()
-
-  if (ENABLE_CURL)
-    add_definitions(-DORTHANC_ENABLE_CURL=1)
-    include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake)
-
-    if (ENABLE_SSL)
-      set(ENABLE_PKCS11 OFF)
-      add_definitions(-DORTHANC_ENABLE_SSL=1)
-      include(${ORTHANC_ROOT}/Resources/CMake/OpenSslConfiguration.cmake)
-    else()
-      add_definitions(-DORTHANC_ENABLE_SSL=0)
-    endif()
-  else()
-    add_definitions(
-      -DORTHANC_ENABLE_SSL=0
-      -DORTHANC_ENABLE_CURL=0
-      )
-  endif()
-endif()
-
-add_definitions(
-  -DHAS_ORTHANC_EXCEPTION=1
-  -DORTHANC_ENABLE_MD5=0
-  -DORTHANC_ENABLE_BASE64=1
-  -DORTHANC_ENABLE_PUGIXML=0
-  -DORTHANC_ENABLE_PKCS11=0
-  )
-
-
-#####################################################################
-## Link the colormaps into the binaries
-#####################################################################
-
-EmbedResources(
-  COLORMAP_HOT    ${ORTHANC_STONE_DIR}/Resources/Colormaps/hot.lut
-  COLORMAP_JET    ${ORTHANC_STONE_DIR}/Resources/Colormaps/jet.lut
-  COLORMAP_RED    ${ORTHANC_STONE_DIR}/Resources/Colormaps/red.lut
-  COLORMAP_GREEN  ${ORTHANC_STONE_DIR}/Resources/Colormaps/green.lut
-  COLORMAP_BLUE   ${ORTHANC_STONE_DIR}/Resources/Colormaps/blue.lut
-  )
-
-
-#####################################################################
-## System-specific patches
-#####################################################################
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
-    NOT MSVC AND
-    ENABLE_SDL)
-  # This is necessary when compiling EXE for Windows using MinGW
-  link_libraries(mingw32)
-endif()
-
-
-
-#####################################################################
-## All the source files required to build Stone of Orthanc
-#####################################################################
-
-list(APPEND ORTHANC_STONE_SOURCES
-  ${ORTHANC_STONE_DIR}/Applications/BasicApplicationContext.cpp
-  ${ORTHANC_STONE_DIR}/Applications/BinarySemaphore.cpp
-  ${ORTHANC_STONE_DIR}/Applications/IBasicApplication.cpp
-  ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlBuffering.cpp
-  ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlEngine.cpp
-  ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlWindow.cpp
-
-  ${ORTHANC_STONE_DIR}/Framework/Layers/CircleMeasureTracker.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/ColorFrameRenderer.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/DicomStructureSetRendererFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/FrameRenderer.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/GrayscaleFrameRenderer.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/LineLayerRenderer.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/LineMeasureTracker.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/RenderStyle.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/SeriesFrameRendererFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/SiblingSliceLocationFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Layers/SingleFrameRendererFactory.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomFrameConverter.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomStructureSet.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DownloadStack.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/GeometryToolbox.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/MessagingToolbox.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSeriesLoader.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlices.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlicesCursor.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/SliceGeometry.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ViewportGeometry.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoContext.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoFont.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoSurface.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Viewport/WidgetViewport.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Volumes/ImageBuffer3D.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImage.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImagePolicyBase.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageProgressivePolicy.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageSimplePolicy.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/CairoWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/EmptyWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayeredSceneWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayoutWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/TestCairoWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/TestWorldSceneWidget.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/WidgetBase.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Widgets/WorldSceneWidget.cpp
-
-  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
-  ${ORTHANC_ROOT}/Core/Compression/DeflateBaseCompressor.cpp
-  ${ORTHANC_ROOT}/Core/Compression/GzipCompressor.cpp
-  ${ORTHANC_ROOT}/Core/Enumerations.cpp
-  ${ORTHANC_ROOT}/Core/Images/Image.cpp
-  ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp
-  ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp
-  ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp
-  ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp
-  ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp
-  ${ORTHANC_ROOT}/Core/Images/PngReader.cpp
-  ${ORTHANC_ROOT}/Core/Logging.cpp
-  ${ORTHANC_ROOT}/Core/Toolbox.cpp
-  ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
-  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomTag.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
-  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
-
-  ${AUTOGENERATED_SOURCES}
-
-  # Mandatory components
-  ${BOOST_SOURCES}
-  ${CAIRO_SOURCES}
-  ${JSONCPP_SOURCES}
-  ${PIXMAN_SOURCES}
-  ${ZLIB_SOURCES}
-  ${LIBPNG_SOURCES}
-  ${LIBJPEG_SOURCES}
-
-  # Optional components
-  ${OPENSSL_SOURCES}
-  ${CURL_SOURCES}
-  ${SDL_SOURCES}
-  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,227 @@
+# 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/>.
+
+
+
+#####################################################################
+## Configure the Orthanc Framework
+#####################################################################
+
+SET(ENABLE_JPEG ON)
+SET(ENABLE_PNG ON)
+
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+
+include_directories(${ORTHANC_ROOT})
+
+
+#####################################################################
+## Sanity check of the configuration
+#####################################################################
+
+if (ORTHANC_SANDBOXED)
+  if (ENABLE_CURL)
+    message(FATAL_ERROR "Cannot enable curl in sandboxed environments")
+  endif()
+
+  if (ENABLE_SDL)
+    message(FATAL_ERROR "Cannot enable SDL in sandboxed environments")
+  endif()
+
+  if (ENABLE_SSL)
+    message(FATAL_ERROR "Cannot enable SSL in sandboxed environments")
+  endif()
+endif()
+  
+
+#####################################################################
+## Configure mandatory third-party components
+#####################################################################
+
+SET(ORTHANC_STONE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
+
+include(FindPkgConfig)
+include(${CMAKE_CURRENT_LIST_DIR}/BoostExtendedConfiguration.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/CairoConfiguration.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/PixmanConfiguration.cmake)
+
+
+
+#####################################################################
+## Configure optional third-party components
+#####################################################################
+
+if (NOT ORTHANC_SANDBOXED)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancHttpConnection.cpp
+    )
+endif()
+
+
+if (ENABLE_SDL)
+  include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake)  
+  add_definitions(-DORTHANC_ENABLE_SDL=1)
+else()
+  unset(USE_SYSTEM_SDL CACHE)
+  add_definitions(-DORTHANC_ENABLE_SDL=0)
+endif()
+
+
+
+#####################################################################
+## Configuration of the C/C++ macros
+#####################################################################
+
+if (MSVC)
+  # Remove some warnings on Visual Studio 2015
+  add_definitions(-D_SCL_SECURE_NO_WARNINGS=1) 
+endif()
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
+
+
+
+#####################################################################
+## Embed the colormaps into the binaries
+#####################################################################
+
+EmbedResources(
+  COLORMAP_HOT    ${ORTHANC_STONE_DIR}/Resources/Colormaps/hot.lut
+  COLORMAP_JET    ${ORTHANC_STONE_DIR}/Resources/Colormaps/jet.lut
+  COLORMAP_RED    ${ORTHANC_STONE_DIR}/Resources/Colormaps/red.lut
+  COLORMAP_GREEN  ${ORTHANC_STONE_DIR}/Resources/Colormaps/green.lut
+  COLORMAP_BLUE   ${ORTHANC_STONE_DIR}/Resources/Colormaps/blue.lut
+  )
+
+
+
+#####################################################################
+## System-specific patches
+#####################################################################
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
+    NOT MSVC AND
+    ENABLE_SDL)
+  # This is necessary when compiling EXE for Windows using MinGW
+  link_libraries(mingw32)
+endif()
+
+if (ORTHANC_SANDBOXED)
+  # Remove functions not suitable for a sandboxed environment
+  list(REMOVE_ITEM ORTHANC_CORE_SOURCES
+    ${ZLIB_SOURCES_DIR}/gzlib.c
+    ${ZLIB_SOURCES_DIR}/gzwrite.c
+    ${ZLIB_SOURCES_DIR}/gzread.c
+    )
+endif()
+
+
+
+#####################################################################
+## All the source files required to build Stone of Orthanc
+#####################################################################
+
+if (NOT ORTHANC_SANDBOXED)
+  set(PLATFORM_SOURCES
+    ${ORTHANC_STONE_DIR}/Platforms/Generic/WebServiceGetCommand.cpp
+    ${ORTHANC_STONE_DIR}/Platforms/Generic/WebServicePostCommand.cpp
+    ${ORTHANC_STONE_DIR}/Platforms/Generic/Oracle.cpp
+    )
+
+  set(APPLICATIONS_SOURCES
+    ${ORTHANC_STONE_DIR}/Applications/BasicApplicationContext.cpp
+    ${ORTHANC_STONE_DIR}/Applications/IBasicApplication.cpp
+    ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlEngine.cpp
+    ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlSurface.cpp
+    ${ORTHANC_STONE_DIR}/Applications/Sdl/SdlWindow.cpp
+    )
+endif()
+
+list(APPEND ORTHANC_STONE_SOURCES
+  #${ORTHANC_STONE_DIR}/Framework/Layers/SeriesFrameRendererFactory.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Layers/SiblingSliceLocationFactory.cpp
+  #${ORTHANC_STONE_DIR}/Framework/Layers/SingleFrameRendererFactory.cpp
+  ${ORTHANC_STONE_DIR}/Framework/StoneEnumerations.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/CircleMeasureTracker.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/ColorFrameRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/DicomStructureSetRendererFactory.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/FrameRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/GrayscaleFrameRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/LayerSourceBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/LineLayerRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/LineMeasureTracker.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/OrthancFrameLayerSource.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/RenderStyle.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Layers/SliceOutlineRenderer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/CoordinateSystem3D.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomFrameConverter.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomStructureSet.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/DownloadStack.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/Extent2D.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/FiniteProjectiveCamera.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/GeometryToolbox.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ImageGeometry.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/LinearAlgebra.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/MessagingToolbox.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrientedBoundingBox.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSlicesLoader.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlices.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlicesCursor.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/Slice.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/SlicesSorter.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/ViewportGeometry.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoContext.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoFont.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoSurface.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Viewport/WidgetViewport.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/ImageBuffer3D.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/SlicedVolumeBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/StructureSetLoader.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeLoaderBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeReslicer.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/CairoWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/EmptyWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayerWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/LayoutWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/TestCairoWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/TestWorldSceneWidget.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/WidgetBase.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Widgets/WorldSceneWidget.cpp
+
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp
+  
+  ${PLATFORM_SOURCES}
+  ${APPLICATIONS_SOURCES}
+  ${ORTHANC_CORE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+
+  # Mandatory components
+  ${CAIRO_SOURCES}
+  ${PIXMAN_SOURCES}
+
+  # Optional components
+  ${SDL_SOURCES}
+  ${BOOST_EXTENDED_SOURCES}
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,48 @@
+# 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/>.
+
+
+
+#####################################################################
+## Import the parameters of the Orthanc Framework
+#####################################################################
+
+# TODO => Import
+SET(ORTHANC_ROOT /home/jodogne/Subversion/orthanc)
+
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+
+SET(ORTHANC_STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)
+
+
+#####################################################################
+## CMake parameters tunable by the user
+#####################################################################
+
+# Advanced parameters to fine-tune linking against system libraries
+SET(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo")
+SET(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
+SET(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
+
+
+#####################################################################
+## Internal CMake parameters to enable the optional subcomponents of
+## the Stone of Orthanc
+#####################################################################
+
+SET(ENABLE_SDL ON CACHE INTERNAL "Include support for SDL")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Computations/ComputeShearOnSlice.py	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+
+from sympy import *
+import pprint
+
+init_printing(use_unicode=True)
+
+
+# Setup "T * S * M_shear" (Equation A.16)
+
+ex, ey, ew = symbols('ex ey ew')
+sx, sy = symbols('sx, sy')
+ti, tj = symbols('ti tj')
+
+T = Matrix([[ 1, 0, 0, ti ],
+            [ 0, 1, 0, tj ],
+            [ 0, 0, 1, 0  ],
+            [ 0, 0, 0, 1  ]])
+
+# Equation (A.15), if "sx == sy == f"
+S = Matrix([[ sx, 0,  0, 0 ],
+            [ 0,  sy, 0, 0 ],
+            [ 0,  0,  1, 0 ],
+            [ 0,  0,  0, 1 ]])
+
+# MM_shear, in Equation (A.14)
+M = Matrix([[ 1, 0, ex, 0 ],
+            [ 0, 1, ey, 0 ],
+            [ 0, 0, 1,  0 ],
+            [ 0, 0, ew,  1 ]])
+
+
+x, y, z, w = symbols('x y z w')
+p = Matrix([ x, y, z, w ])
+
+print("\nT =" % T)
+pprint.pprint(T);
+
+print("\nS =" % T)
+pprint.pprint(S);
+
+print("\nM'_shear =" % T)
+pprint.pprint(M);
+
+print("\nGeneral form of a Lacroute's shear matrix (Equation A.16): T * S * M'_shear =")
+pprint.pprint(T * S * M);
+
+print("\nHence, alternative parametrization:")
+a11, a13, a14, a22, a23, a24, a43 = symbols('a11 a13 a14 a22 a23 a24 a43')
+
+A = Matrix([[ a11, 0,   a13, a14 ],
+            [ 0,   a22, a23, a24 ],
+            [ 0,   0,   1,   0   ],
+            [ 0,   0,   a43, 1   ]])
+pprint.pprint(A);
+
+v = A * p
+v = v.subs(w, 1)
+
+print("\nAction of Lacroute's shear matrix A on plane z (taking w=1):\n%s\n" % v)
+
+print('Output x\' = %s\n' % (v[0]/v[3]))
+print('Output y\' = %s\n' % (v[1]/v[3]))
+print('Output z\' = %s\n' % (v[2]/v[3]))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Computations/ComputeShearParameters.py	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+
+from sympy import *
+import pprint
+
+init_printing(use_unicode=True)
+
+s13, s23, s43 = symbols('s13 s23 s43')
+x, y, z, w = symbols('x y z w')
+
+A = Matrix([[ 1, 0, s13, 0 ],
+            [ 0, 1, s23, 0 ],
+            [ 0, 0, 1,   0 ],
+            [ 0, 0, s43, 1 ]])
+
+print('\nLacroute\'s shear matrix (A.14) is:')
+pprint.pprint(A)
+
+# At this point, we can write "print(A*p)". However, we don't care
+# about the output "z" axis, as it is fixed. So we delete the 3rd row
+# of A.
+
+A.row_del(2)
+
+p = Matrix([ x, y, z, 1 ])
+
+v = A*p
+print('\nAction of Lacroute\'s shear matrix on plane z (taking w=1):\n%s\n' % v)
+
+print('Scaling = %s' % (1/v[2]))
+print('Offset X = %s' % (v[0]/v[2]).subs(x, 0))
+print('Offset Y = %s' % (v[1]/v[2]).subs(y, 0))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Computations/ComputeWarp.py	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,112 @@
+#!/usr/bin/python
+
+from sympy import *
+from sympy.solvers import solve
+import pprint
+import sys
+
+init_printing(use_unicode=True)
+
+
+# Create a test 3D vector using homogeneous coordinates
+x, y, z, w = symbols('x y z w')
+p = Matrix([ x, y, z, w ])
+
+
+# Create a shear matrix, and a scale/shift "T * S" transform as in
+# Lacroute's thesis (Equation A.16, page 209)
+ex, ey, ew = symbols('ex ey ew')
+sx, sy, tx, ty = symbols('sx sy tx ty')
+
+TS = Matrix([[ sx, 0,  0, tx ],
+             [ 0,  sy, 0, ty ],
+             [ 0,  0,  1, 0  ],
+             [ 0,  0,  0, 1  ]])
+
+pureShear = Matrix([[ 1, 0, ex, 0 ],
+                    [ 0, 1, ey, 0 ],
+                    [ 0, 0, 1,  0 ],
+                    [ 0, 0, ew, 1 ]])
+
+
+# Create a general warp matrix, that corresponds to "M_warp" in
+# Equation (A.17) of Lacroute's thesis:
+ww11, ww12, ww13, ww14, ww21, ww22, ww23, ww24, ww31, ww32, ww33, ww34, ww41, ww42, ww43, ww44 = symbols('ww11 ww12 ww13 ww14 ww21 ww22 ww23 ww24 ww31 ww32 ww33 ww34 ww41 ww42 ww43 ww44')
+
+WW = Matrix([[ ww11, ww12, ww13, ww14 ],
+             [ ww21, ww22, ww23, ww24 ],
+             [ ww31, ww32, ww33, ww34 ],
+             [ ww41, ww43, ww43, ww44 ]])
+
+
+# Create the matrix of intrinsic parameters of the camera
+k11, k22, k14, k24 = symbols('k11 k22 k14 k24')
+K = Matrix([[ k11, 0,   0, k14 ],
+            [ 0,   k22, 0, k24 ],
+            [ 0,   0,   0, 1   ]])
+
+
+# The full decomposition is:
+M_shear = TS * pureShear
+M_warp = K * WW * TS.inv()
+AA = M_warp * M_shear
+
+# Check that the central component "M_warp == K * WW * TS.inv()" that
+# is the left part of "A" is another general warp matrix (i.e. no
+# exception is thrown about incompatible matrix sizes):
+M_warp * p
+
+if (M_warp.cols != 4 or
+    M_warp.rows != 3):
+    raise Exception('Invalid matrix size')
+
+
+# We've just shown that "M_warp" is a general 3x4 matrix. Let's call
+# it W:
+w11, w12, w13, w14, w21, w22, w23, w24, w41, w42, w43, w44 = symbols('w11 w12 w13 w14 w21 w22 w23 w24 w41 w42 w43 w44')
+
+W = Matrix([[ w11, w12, w13, w14 ],
+            [ w21, w22, w23, w24 ],
+            [ w41, w43, w43, w44 ]])
+
+# This shows that it is sufficient to study a decomposition of the
+# following form:
+A = W * M_shear
+print('\nA = W * M_shear =')
+pprint.pprint(A)
+
+sys.stdout.write('\nW = ')
+pprint.pprint(W)
+
+sys.stdout.write('\nM_shear = ')
+pprint.pprint(M_shear)
+
+
+
+# Let's consider one fixed 2D point (i,j) in the intermediate
+# image. The 3D points (x,y,z,1) that are mapped to (i,j) must satisfy
+# the equation "(i,j) == M_shear * (x,y,z,w)". As "M_shear" is
+# invertible, we solve "(x,y,z,w) == inv(M_shear) * (i,j,k,1)".
+
+i, j, k = symbols('i j k')
+l = M_shear.inv() * Matrix([ i, j, k, 1 ])
+
+print('\nLocus for points imaged to some fixed (i,j,k,l) point in the intermediate image:')
+print('x = %s' % l[0])
+print('y = %s' % l[1])
+print('z = %s' % l[2])
+print('w = %s' % l[3])
+
+
+# By inspecting the 4 equations above, we see that the locus entirely
+# depends upon the "k" value that encodes the Z-axis
+
+print('\nGlobal effect of the shear-warp transform on this locus:')
+q = expand(A * l)
+pprint.pprint(q)
+
+print("\nWe can arbitrarily fix the value of 'k', so let's choose 'k=0':")
+pprint.pprint(q.subs(k, 0))
+
+print("\nThis gives the warp transform.")
+print("QED: line after Equation (A.17) on page 209.\n")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Computations/IntersectSegmentAndHorizontalLine.py	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+from sympy import *
+
+# Intersection between the 2D line segment (prevX,prevY)-(curX,curY) and the
+# horizontal line "y = y0" using homogeneous coordinates
+
+prevX, prevY, curX, curY, y0 = symbols('prevX prevY curX curY y0')
+
+p1 = Matrix([prevX, prevY, 1])
+p2 = Matrix([curX, curY, 1])
+l1 = p1.cross(p2)
+
+h1 = Matrix([0, y0, 1])
+h2 = Matrix([1, y0, 1])
+l2 = h1.cross(h2)
+
+a = l1.cross(l2)
+
+#pprint(cse(a/a[2], symbols = symbols('a b')))
+pprint(a / a[2])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Computations/IntersectSegmentAndVerticalLine.py	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+from sympy import *
+
+# Intersection between the 2D line segment (prevX,prevY)-(curX,curY) and the
+# vertical line "x = x0" using homogeneous coordinates
+
+prevX, prevY, curX, curY, x0 = symbols('prevX prevY curX curY x0')
+
+p1 = Matrix([prevX, prevY, 1])
+p2 = Matrix([curX, curY, 1])
+l1 = p1.cross(p2)
+
+h1 = Matrix([x0, 0, 1])
+h2 = Matrix([x0, 1, 1])
+l2 = h1.cross(h2)
+
+a = l1.cross(l2)
+
+pprint(a / a[2])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/Threading/BinarySemaphore.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,50 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "BinarySemaphore.h"
+
+namespace OrthancStone
+{
+  BinarySemaphore::BinarySemaphore() : 
+    proceed_(false)
+  {
+  }
+
+  void BinarySemaphore::Signal()
+  {
+    //boost::mutex::scoped_lock lock(mutex_);
+
+    proceed_ = true;
+    condition_.notify_one(); 
+  }
+
+  void BinarySemaphore::Wait()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (!proceed_)
+    {
+      condition_.wait(lock);
+    }
+
+    proceed_ = false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/Threading/BinarySemaphore.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,43 @@
+/**
+ * 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/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+
+namespace OrthancStone
+{
+  class BinarySemaphore : public boost::noncopyable
+  {
+  private:
+    bool proceed_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit BinarySemaphore();
+
+    void Signal();
+
+    void Wait();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/Threading/IThreadSafety.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,57 @@
+/**
+ * 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/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  /**
+   * Dummy interface to explicitely tag the interfaces whose derived
+   * class must be thread-safe. The different methods of such classes
+   * might be simlultaneously invoked by several threads, and should
+   * be properly protected by mutexes.
+   **/
+  class IThreadSafe : public boost::noncopyable
+  {
+  public:
+    virtual ~IThreadSafe()
+    {
+    }
+  };
+
+
+  /**
+   * Dummy interface to explicitely tag the interfaces that are NOT
+   * expected to be thread-safe. The Orthanc Stone framework ensures
+   * that at most one method of such classes will be invoked at a
+   * given time. Such classes are automatically protected by the
+   * Orthanc Stone framework wherever required.
+   **/
+  class IThreadUnsafe : public boost::noncopyable
+  {
+  public:
+    virtual ~IThreadUnsafe()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/Threading/SdlBuffering.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,134 @@
+/**
+ * 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 "SdlBuffering.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  SdlBuffering::SdlBuffering() :
+    sdlSurface_(NULL),
+    pendingFrame_(false)
+  {
+  }
+
+
+  SdlBuffering::~SdlBuffering()
+  {
+    if (sdlSurface_)
+    {
+      SDL_FreeSurface(sdlSurface_);
+    }
+  }
+
+
+  void SdlBuffering::SetSize(unsigned int width,
+                             unsigned int height,
+                             IViewport& viewport)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    viewport.SetSize(width, height);
+
+    if (offscreenSurface_.get() == NULL ||
+        offscreenSurface_->GetWidth() != width ||
+        offscreenSurface_->GetHeight() != height)
+    {
+      offscreenSurface_.reset(new CairoSurface(width, height));
+    }
+
+    if (onscreenSurface_.get() == NULL ||
+        onscreenSurface_->GetWidth() != width ||
+        onscreenSurface_->GetHeight() != height)
+    {
+      onscreenSurface_.reset(new CairoSurface(width, height));
+
+      // TODO Big endian?
+      static const uint32_t rmask = 0x00ff0000;
+      static const uint32_t gmask = 0x0000ff00;
+      static const uint32_t bmask = 0x000000ff;
+
+      if (sdlSurface_)
+      {
+        SDL_FreeSurface(sdlSurface_);
+      }
+
+      sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32,
+                                             onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0);
+      if (!sdlSurface_)
+      {
+        LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }    
+    }
+
+    pendingFrame_ = false;
+  }
+
+
+  bool SdlBuffering::RenderOffscreen(IViewport& viewport)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (offscreenSurface_.get() == NULL)
+    {
+      return false;
+    }
+
+    Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor();
+
+    if (viewport.Render(target) &&
+        !pendingFrame_)
+    {
+      pendingFrame_ = true;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void SdlBuffering::SwapToScreen(SdlWindow& window)
+  {
+    if (!pendingFrame_ ||
+        offscreenSurface_.get() == NULL ||
+        onscreenSurface_.get() == NULL)
+    {
+      return;
+    }
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      onscreenSurface_->Copy(*offscreenSurface_);
+    }
+    
+    window.Render(sdlSurface_);
+    pendingFrame_ = false;
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/Threading/SdlBuffering.h	Tue Mar 20 20:03:02 2018 +0100
@@ -0,0 +1,60 @@
+/**
+ * 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
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "SdlWindow.h"
+#include "../../Framework/Viewport/CairoSurface.h"
+#include "../../Framework/Viewport/IViewport.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  class SdlBuffering : public boost::noncopyable
+  {
+  private:
+    boost::mutex                 mutex_;
+    std::auto_ptr<CairoSurface>  offscreenSurface_;
+    std::auto_ptr<CairoSurface>  onscreenSurface_;
+    SDL_Surface*                 sdlSurface_;
+    bool                         pendingFrame_;
+
+  public:
+    SdlBuffering();
+
+    ~SdlBuffering();
+
+    void SetSize(unsigned int width,
+                 unsigned int height,
+                 IViewport& viewport);
+
+    // Returns "true" if a new refresh of the display should be
+    // triggered afterwards
+    bool RenderOffscreen(IViewport& viewport);
+
+    void SwapToScreen(SdlWindow& window);
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/Threading/SharedValue.h	Tue Mar 20 20:03:02 2018 +0100
@@ -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 <boost/noncopyable.hpp>
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancStone
+{
+  // A value that is protected by a mutex, in order to be shared by
+  // multiple threads
+  template <typename T>
+  class SharedValue : public boost::noncopyable
+  {
+  private:
+    boost::mutex   mutex_;
+    T              value_;
+    
+  public:
+    class Locker : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      T&                         value_;
+
+    public:
+      Locker(SharedValue& shared) :
+        lock_(shared.mutex_),
+        value_(shared.value_)
+      {
+      }
+
+      T& GetValue() const
+      {
+        return value_;
+      }
+    };
+  };
+}
--- a/Resources/Graveyard/Toolbox/DicomDataset.h	Tue Jan 02 09:51:36 2018 +0100
+++ b/Resources/Graveyard/Toolbox/DicomDataset.h	Tue Mar 20 20:03:02 2018 +0100
@@ -21,7 +21,6 @@
 
 #pragma once
 
-#include "GeometryToolbox.h"
 #include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h"
 
 #include <map>
--- a/Resources/Orthanc/Core/ChunkedBuffer.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "ChunkedBuffer.h"
-
-#include <cassert>
-#include <string.h>
-
-
-namespace Orthanc
-{
-  void ChunkedBuffer::Clear()
-  {
-    numBytes_ = 0;
-
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  void ChunkedBuffer::AddChunk(const void* chunkData,
-                               size_t chunkSize)
-  {
-    if (chunkSize == 0)
-    {
-      return;
-    }
-    else
-    {
-      assert(chunkData != NULL);
-      chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize));
-      numBytes_ += chunkSize;
-    }
-  }
-
-
-  void ChunkedBuffer::AddChunk(const std::string& chunk)
-  {
-    if (chunk.size() > 0)
-    {
-      AddChunk(&chunk[0], chunk.size());
-    }
-  }
-
-
-  void ChunkedBuffer::Flatten(std::string& result)
-  {
-    result.resize(numBytes_);
-
-    size_t pos = 0;
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      assert(*it != NULL);
-
-      size_t s = (*it)->size();
-      if (s != 0)
-      {
-        memcpy(&result[pos], (*it)->c_str(), s);
-        pos += s;
-      }
-
-      delete *it;
-    }
-
-    chunks_.clear();
-    numBytes_ = 0;
-  }
-}
--- a/Resources/Orthanc/Core/ChunkedBuffer.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <list>
-#include <string>
-
-namespace Orthanc
-{
-  class ChunkedBuffer
-  {
-  private:
-    typedef std::list<std::string*>  Chunks;
-    size_t numBytes_;
-    Chunks chunks_;
-  
-    void Clear();
-
-  public:
-    ChunkedBuffer() : numBytes_(0)
-    {
-    }
-
-    ~ChunkedBuffer()
-    {
-      Clear();
-    }
-
-    size_t GetNumBytes() const
-    {
-      return numBytes_;
-    }
-
-    void AddChunk(const void* chunkData,
-                  size_t chunkSize);
-
-    void AddChunk(const std::string& chunk);
-
-    void Flatten(std::string& result);
-  };
-}
--- a/Resources/Orthanc/Core/Compression/DeflateBaseCompressor.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "DeflateBaseCompressor.h"
-
-#include "../OrthancException.h"
-#include "../Logging.h"
-
-#include <string.h>
-
-namespace Orthanc
-{
-  void DeflateBaseCompressor::SetCompressionLevel(uint8_t level)
-  {
-    if (level >= 10)
-    {
-      LOG(ERROR) << "Zlib compression level must be between 0 (no compression) and 9 (highest compression)";
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    compressionLevel_ = level;
-  }
-
-
-  uint64_t DeflateBaseCompressor::ReadUncompressedSizePrefix(const void* compressed,
-                                                             size_t compressedSize)
-  {
-    if (compressedSize == 0)
-    {
-      return 0;
-    }
-
-    if (compressedSize < sizeof(uint64_t))
-    {
-      LOG(ERROR) << "The compressed buffer is ill-formed";
-      throw OrthancException(ErrorCode_CorruptedFile);
-    }
-
-    uint64_t size;
-    memcpy(&size, compressed, sizeof(uint64_t));
-
-    return size;
-  }
-
-}
--- a/Resources/Orthanc/Core/Compression/DeflateBaseCompressor.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IBufferCompressor.h"
-
-#include <stdint.h>
-
-namespace Orthanc
-{
-  class DeflateBaseCompressor : public IBufferCompressor
-  {
-  private:
-    uint8_t compressionLevel_;
-    bool    prefixWithUncompressedSize_;
-
-  protected:
-    uint64_t ReadUncompressedSizePrefix(const void* compressed,
-                                        size_t compressedSize);
-
-  public:
-    DeflateBaseCompressor() : 
-      compressionLevel_(6),
-      prefixWithUncompressedSize_(false)
-    {
-    }
-
-    void SetCompressionLevel(uint8_t level);
-    
-    void SetPrefixWithUncompressedSize(bool prefix)
-    {
-      prefixWithUncompressedSize_ = prefix;
-    }
-
-    bool HasPrefixWithUncompressedSize() const
-    {
-      return prefixWithUncompressedSize_;
-    }
-
-    uint8_t GetCompressionLevel() const
-    {
-      return compressionLevel_;
-    }
-  };
-}
--- a/Resources/Orthanc/Core/Compression/GzipCompressor.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,278 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "GzipCompressor.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <zlib.h>
-
-#include "../OrthancException.h"
-#include "../Logging.h"
-
-namespace Orthanc
-{
-  uint64_t GzipCompressor::GuessUncompressedSize(const void* compressed,
-                                                 size_t compressedSize)
-  {
-    /**
-     * "Is there a way to find out the size of the original file which
-     * is inside a GZIP file? [...] There is no truly reliable way,
-     * other than gunzipping the stream. You do not need to save the
-     * result of the decompression, so you can determine the size by
-     * simply reading and decoding the entire file without taking up
-     * space with the decompressed result.
-     *
-     * There is an unreliable way to determine the uncompressed size,
-     * which is to look at the last four bytes of the gzip file, which
-     * is the uncompressed length of that entry modulo 232 in little
-     * endian order.
-     * 
-     * It is unreliable because a) the uncompressed data may be longer
-     * than 2^32 bytes, and b) the gzip file may consist of multiple
-     * gzip streams, in which case you would find the length of only
-     * the last of those streams.
-     * 
-     * If you are in control of the source of the gzip files, you know
-     * that they consist of single gzip streams, and you know that
-     * they are less than 2^32 bytes uncompressed, then and only then
-     * can you use those last four bytes with confidence."
-     *
-     * http://stackoverflow.com/a/9727599/881731
-     **/
-
-    if (compressedSize < 4)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    const uint8_t* p = reinterpret_cast<const uint8_t*>(compressed) + compressedSize - 4;
-
-    return ((static_cast<uint32_t>(p[0]) << 0) +
-            (static_cast<uint32_t>(p[1]) << 8) +
-            (static_cast<uint32_t>(p[2]) << 16) +
-            (static_cast<uint32_t>(p[3]) << 24));            
-  }
-
-
-
-  void GzipCompressor::Compress(std::string& compressed,
-                                const void* uncompressed,
-                                size_t uncompressedSize)
-  {
-    uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */;
-    if (compressedSize == 0)
-    {
-      compressedSize = 1;
-    }
-
-    uint8_t* target;
-    if (HasPrefixWithUncompressedSize())
-    {
-      compressed.resize(compressedSize + sizeof(uint64_t));
-      target = reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(uint64_t);
-    }
-    else
-    {
-      compressed.resize(compressedSize);
-      target = reinterpret_cast<uint8_t*>(&compressed[0]);
-    }
-
-    z_stream stream;
-    memset(&stream, 0, sizeof(stream));
-
-    stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(uncompressed));
-    stream.next_out = reinterpret_cast<Bytef*>(target);
-
-    stream.avail_in = static_cast<uInt>(uncompressedSize);
-    stream.avail_out = static_cast<uInt>(compressedSize);
-
-    // Ensure no overflow (if the buffer is too large for the current archicture)
-    if (static_cast<size_t>(stream.avail_in) != uncompressedSize ||
-        static_cast<size_t>(stream.avail_out) != compressedSize)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-    
-    // Initialize the compression engine
-    int error = deflateInit2(&stream, 
-                             GetCompressionLevel(), 
-                             Z_DEFLATED,
-                             MAX_WBITS + 16,      // ask for gzip output
-                             8,                   // default memory level
-                             Z_DEFAULT_STRATEGY);
-
-    if (error != Z_OK)
-    {
-      // Cannot initialize zlib
-      compressed.clear();
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Compress the input buffer
-    error = deflate(&stream, Z_FINISH);
-
-    if (error != Z_STREAM_END)
-    {
-      deflateEnd(&stream);
-      compressed.clear();
-
-      switch (error)
-      {
-      case Z_MEM_ERROR:
-        throw OrthancException(ErrorCode_NotEnoughMemory);
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-      }  
-    }
-
-    size_t size = stream.total_out;
-
-    if (deflateEnd(&stream) != Z_OK)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // The compression was successful
-    if (HasPrefixWithUncompressedSize())
-    {
-      uint64_t s = static_cast<uint64_t>(uncompressedSize);
-      memcpy(&compressed[0], &s, sizeof(uint64_t));
-      compressed.resize(size + sizeof(uint64_t));
-    }
-    else
-    {
-      compressed.resize(size);
-    }
-  }
-
-
-  void GzipCompressor::Uncompress(std::string& uncompressed,
-                                  const void* compressed,
-                                  size_t compressedSize)
-  {
-    uint64_t uncompressedSize;
-    const uint8_t* source = reinterpret_cast<const uint8_t*>(compressed);
-
-    if (HasPrefixWithUncompressedSize())
-    {
-      uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize);
-      source += sizeof(uint64_t);
-      compressedSize -= sizeof(uint64_t);
-    }
-    else
-    {
-      uncompressedSize = GuessUncompressedSize(compressed, compressedSize);
-    }
-
-    try
-    {
-      uncompressed.resize(static_cast<size_t>(uncompressedSize));
-    }
-    catch (...)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    z_stream stream;
-    memset(&stream, 0, sizeof(stream));
-
-    char dummy = '\0';  // zlib does not like NULL output buffers (even if the uncompressed data is empty)
-    stream.next_in = const_cast<Bytef*>(source);
-    stream.next_out = reinterpret_cast<Bytef*>(uncompressedSize == 0 ? &dummy : &uncompressed[0]);
-
-    stream.avail_in = static_cast<uInt>(compressedSize);
-    stream.avail_out = static_cast<uInt>(uncompressedSize);
-
-    // Ensure no overflow (if the buffer is too large for the current archicture)
-    if (static_cast<size_t>(stream.avail_in) != compressedSize ||
-        static_cast<size_t>(stream.avail_out) != uncompressedSize)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    // Initialize the compression engine
-    int error = inflateInit2(&stream, 
-                             MAX_WBITS + 16);  // this is a gzip input
-
-    if (error != Z_OK)
-    {
-      // Cannot initialize zlib
-      uncompressed.clear();
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Uncompress the input buffer
-    error = inflate(&stream, Z_FINISH);
-
-    if (error != Z_STREAM_END)
-    {
-      inflateEnd(&stream);
-      uncompressed.clear();
-
-      switch (error)
-      {
-        case Z_MEM_ERROR:
-          throw OrthancException(ErrorCode_NotEnoughMemory);
-          
-        case Z_BUF_ERROR:
-        case Z_NEED_DICT:
-          throw OrthancException(ErrorCode_BadFileFormat);
-          
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    size_t size = stream.total_out;
-
-    if (inflateEnd(&stream) != Z_OK)
-    {
-      uncompressed.clear();
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    if (size != uncompressedSize)
-    {
-      uncompressed.clear();
-
-      // The uncompressed size was not that properly guess, presumably
-      // because of a file size over 4GB. Should fallback to
-      // stream-based decompression.
-      LOG(ERROR) << "The uncompressed size of a gzip-encoded buffer was not properly guessed";
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-}
--- a/Resources/Orthanc/Core/Compression/GzipCompressor.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DeflateBaseCompressor.h"
-
-namespace Orthanc
-{
-  class GzipCompressor : public DeflateBaseCompressor
-  {
-  private:
-    uint64_t GuessUncompressedSize(const void* compressed,
-                                   size_t compressedSize);
-
-  public:
-    GzipCompressor()
-    {
-      SetPrefixWithUncompressedSize(false);
-    }
-
-    virtual void Compress(std::string& compressed,
-                          const void* uncompressed,
-                          size_t uncompressedSize);
-
-    virtual void Uncompress(std::string& uncompressed,
-                            const void* compressed,
-                            size_t compressedSize);
-  };
-}
--- a/Resources/Orthanc/Core/Compression/IBufferCompressor.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <string>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class IBufferCompressor : public boost::noncopyable
-  {
-  public:
-    virtual ~IBufferCompressor()
-    {
-    }
-
-    virtual void Compress(std::string& compressed,
-                          const void* uncompressed,
-                          size_t uncompressedSize) = 0;
-
-    virtual void Uncompress(std::string& uncompressed,
-                            const void* compressed,
-                            size_t compressedSize) = 0;
-
-    static void Compress(std::string& compressed,
-                         IBufferCompressor& compressor,
-                         const std::string& uncompressed)
-    {
-      compressor.Compress(compressed, 
-                          uncompressed.size() == 0 ? NULL : uncompressed.c_str(), 
-                          uncompressed.size());
-    }
-
-    static void Uncompress(std::string& uncompressed,
-                           IBufferCompressor& compressor,
-                           const std::string& compressed)
-    {
-      compressor.Uncompress(uncompressed, 
-                            compressed.size() == 0 ? NULL : compressed.c_str(), 
-                            compressed.size());
-    }
-  };
-}
--- a/Resources/Orthanc/Core/Enumerations.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1405 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "Enumerations.h"
-
-#include "OrthancException.h"
-#include "Toolbox.h"
-#include "Logging.h"
-
-#include <string.h>
-#include <cassert>
-
-namespace Orthanc
-{
-  // This function is autogenerated by the script
-  // "Resources/GenerateErrorCodes.py"
-  const char* EnumerationToString(ErrorCode error)
-  {
-    switch (error)
-    {
-      case ErrorCode_InternalError:
-        return "Internal error";
-
-      case ErrorCode_Success:
-        return "Success";
-
-      case ErrorCode_Plugin:
-        return "Error encountered within the plugin engine";
-
-      case ErrorCode_NotImplemented:
-        return "Not implemented yet";
-
-      case ErrorCode_ParameterOutOfRange:
-        return "Parameter out of range";
-
-      case ErrorCode_NotEnoughMemory:
-        return "The server hosting Orthanc is running out of memory";
-
-      case ErrorCode_BadParameterType:
-        return "Bad type for a parameter";
-
-      case ErrorCode_BadSequenceOfCalls:
-        return "Bad sequence of calls";
-
-      case ErrorCode_InexistentItem:
-        return "Accessing an inexistent item";
-
-      case ErrorCode_BadRequest:
-        return "Bad request";
-
-      case ErrorCode_NetworkProtocol:
-        return "Error in the network protocol";
-
-      case ErrorCode_SystemCommand:
-        return "Error while calling a system command";
-
-      case ErrorCode_Database:
-        return "Error with the database engine";
-
-      case ErrorCode_UriSyntax:
-        return "Badly formatted URI";
-
-      case ErrorCode_InexistentFile:
-        return "Inexistent file";
-
-      case ErrorCode_CannotWriteFile:
-        return "Cannot write to file";
-
-      case ErrorCode_BadFileFormat:
-        return "Bad file format";
-
-      case ErrorCode_Timeout:
-        return "Timeout";
-
-      case ErrorCode_UnknownResource:
-        return "Unknown resource";
-
-      case ErrorCode_IncompatibleDatabaseVersion:
-        return "Incompatible version of the database";
-
-      case ErrorCode_FullStorage:
-        return "The file storage is full";
-
-      case ErrorCode_CorruptedFile:
-        return "Corrupted file (e.g. inconsistent MD5 hash)";
-
-      case ErrorCode_InexistentTag:
-        return "Inexistent tag";
-
-      case ErrorCode_ReadOnly:
-        return "Cannot modify a read-only data structure";
-
-      case ErrorCode_IncompatibleImageFormat:
-        return "Incompatible format of the images";
-
-      case ErrorCode_IncompatibleImageSize:
-        return "Incompatible size of the images";
-
-      case ErrorCode_SharedLibrary:
-        return "Error while using a shared library (plugin)";
-
-      case ErrorCode_UnknownPluginService:
-        return "Plugin invoking an unknown service";
-
-      case ErrorCode_UnknownDicomTag:
-        return "Unknown DICOM tag";
-
-      case ErrorCode_BadJson:
-        return "Cannot parse a JSON document";
-
-      case ErrorCode_Unauthorized:
-        return "Bad credentials were provided to an HTTP request";
-
-      case ErrorCode_BadFont:
-        return "Badly formatted font file";
-
-      case ErrorCode_DatabasePlugin:
-        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
-
-      case ErrorCode_StorageAreaPlugin:
-        return "Error in the plugin implementing a custom storage area";
-
-      case ErrorCode_EmptyRequest:
-        return "The request is empty";
-
-      case ErrorCode_NotAcceptable:
-        return "Cannot send a response which is acceptable according to the Accept HTTP header";
-
-      case ErrorCode_NullPointer:
-        return "Cannot handle a NULL pointer";
-
-      case ErrorCode_SQLiteNotOpened:
-        return "SQLite: The database is not opened";
-
-      case ErrorCode_SQLiteAlreadyOpened:
-        return "SQLite: Connection is already open";
-
-      case ErrorCode_SQLiteCannotOpen:
-        return "SQLite: Unable to open the database";
-
-      case ErrorCode_SQLiteStatementAlreadyUsed:
-        return "SQLite: This cached statement is already being referred to";
-
-      case ErrorCode_SQLiteExecute:
-        return "SQLite: Cannot execute a command";
-
-      case ErrorCode_SQLiteRollbackWithoutTransaction:
-        return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)";
-
-      case ErrorCode_SQLiteCommitWithoutTransaction:
-        return "SQLite: Committing a nonexistent transaction";
-
-      case ErrorCode_SQLiteRegisterFunction:
-        return "SQLite: Unable to register a function";
-
-      case ErrorCode_SQLiteFlush:
-        return "SQLite: Unable to flush the database";
-
-      case ErrorCode_SQLiteCannotRun:
-        return "SQLite: Cannot run a cached statement";
-
-      case ErrorCode_SQLiteCannotStep:
-        return "SQLite: Cannot step over a cached statement";
-
-      case ErrorCode_SQLiteBindOutOfRange:
-        return "SQLite: Bing a value while out of range (serious error)";
-
-      case ErrorCode_SQLitePrepareStatement:
-        return "SQLite: Cannot prepare a cached statement";
-
-      case ErrorCode_SQLiteTransactionAlreadyStarted:
-        return "SQLite: Beginning the same transaction twice";
-
-      case ErrorCode_SQLiteTransactionCommit:
-        return "SQLite: Failure when committing the transaction";
-
-      case ErrorCode_SQLiteTransactionBegin:
-        return "SQLite: Cannot start a transaction";
-
-      case ErrorCode_DirectoryOverFile:
-        return "The directory to be created is already occupied by a regular file";
-
-      case ErrorCode_FileStorageCannotWrite:
-        return "Unable to create a subdirectory or a file in the file storage";
-
-      case ErrorCode_DirectoryExpected:
-        return "The specified path does not point to a directory";
-
-      case ErrorCode_HttpPortInUse:
-        return "The TCP port of the HTTP server is privileged or already in use";
-
-      case ErrorCode_DicomPortInUse:
-        return "The TCP port of the DICOM server is privileged or already in use";
-
-      case ErrorCode_BadHttpStatusInRest:
-        return "This HTTP status is not allowed in a REST API";
-
-      case ErrorCode_RegularFileExpected:
-        return "The specified path does not point to a regular file";
-
-      case ErrorCode_PathToExecutable:
-        return "Unable to get the path to the executable";
-
-      case ErrorCode_MakeDirectory:
-        return "Cannot create a directory";
-
-      case ErrorCode_BadApplicationEntityTitle:
-        return "An application entity title (AET) cannot be empty or be longer than 16 characters";
-
-      case ErrorCode_NoCFindHandler:
-        return "No request handler factory for DICOM C-FIND SCP";
-
-      case ErrorCode_NoCMoveHandler:
-        return "No request handler factory for DICOM C-MOVE SCP";
-
-      case ErrorCode_NoCStoreHandler:
-        return "No request handler factory for DICOM C-STORE SCP";
-
-      case ErrorCode_NoApplicationEntityFilter:
-        return "No application entity filter";
-
-      case ErrorCode_NoSopClassOrInstance:
-        return "DicomUserConnection: Unable to find the SOP class and instance";
-
-      case ErrorCode_NoPresentationContext:
-        return "DicomUserConnection: No acceptable presentation context for modality";
-
-      case ErrorCode_DicomFindUnavailable:
-        return "DicomUserConnection: The C-FIND command is not supported by the remote SCP";
-
-      case ErrorCode_DicomMoveUnavailable:
-        return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP";
-
-      case ErrorCode_CannotStoreInstance:
-        return "Cannot store an instance";
-
-      case ErrorCode_CreateDicomNotString:
-        return "Only string values are supported when creating DICOM instances";
-
-      case ErrorCode_CreateDicomOverrideTag:
-        return "Trying to override a value inherited from a parent module";
-
-      case ErrorCode_CreateDicomUseContent:
-        return "Use \"Content\" to inject an image into a new DICOM instance";
-
-      case ErrorCode_CreateDicomNoPayload:
-        return "No payload is present for one instance in the series";
-
-      case ErrorCode_CreateDicomUseDataUriScheme:
-        return "The payload of the DICOM instance must be specified according to Data URI scheme";
-
-      case ErrorCode_CreateDicomBadParent:
-        return "Trying to attach a new DICOM instance to an inexistent resource";
-
-      case ErrorCode_CreateDicomParentIsInstance:
-        return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)";
-
-      case ErrorCode_CreateDicomParentEncoding:
-        return "Unable to get the encoding of the parent resource";
-
-      case ErrorCode_UnknownModality:
-        return "Unknown modality";
-
-      case ErrorCode_BadJobOrdering:
-        return "Bad ordering of filters in a job";
-
-      case ErrorCode_JsonToLuaTable:
-        return "Cannot convert the given JSON object to a Lua table";
-
-      case ErrorCode_CannotCreateLua:
-        return "Cannot create the Lua context";
-
-      case ErrorCode_CannotExecuteLua:
-        return "Cannot execute a Lua command";
-
-      case ErrorCode_LuaAlreadyExecuted:
-        return "Arguments cannot be pushed after the Lua function is executed";
-
-      case ErrorCode_LuaBadOutput:
-        return "The Lua function does not give the expected number of outputs";
-
-      case ErrorCode_NotLuaPredicate:
-        return "The Lua function is not a predicate (only true/false outputs allowed)";
-
-      case ErrorCode_LuaReturnsNoString:
-        return "The Lua function does not return a string";
-
-      case ErrorCode_StorageAreaAlreadyRegistered:
-        return "Another plugin has already registered a custom storage area";
-
-      case ErrorCode_DatabaseBackendAlreadyRegistered:
-        return "Another plugin has already registered a custom database back-end";
-
-      case ErrorCode_DatabaseNotInitialized:
-        return "Plugin trying to call the database during its initialization";
-
-      case ErrorCode_SslDisabled:
-        return "Orthanc has been built without SSL support";
-
-      case ErrorCode_CannotOrderSlices:
-        return "Unable to order the slices of the series";
-
-      case ErrorCode_NoWorklistHandler:
-        return "No request handler factory for DICOM C-Find Modality SCP";
-
-      case ErrorCode_AlreadyExistingTag:
-        return "Cannot override the value of a tag that already exists";
-
-      default:
-        if (error >= ErrorCode_START_PLUGINS)
-        {
-          return "Error encountered within some plugin";
-        }
-        else
-        {
-          return "Unknown error code";
-        }
-    }
-  }
-
-
-  const char* EnumerationToString(HttpMethod method)
-  {
-    switch (method)
-    {
-      case HttpMethod_Get:
-        return "GET";
-
-      case HttpMethod_Post:
-        return "POST";
-
-      case HttpMethod_Delete:
-        return "DELETE";
-
-      case HttpMethod_Put:
-        return "PUT";
-
-      default:
-        return "?";
-    }
-  }
-
-
-  const char* EnumerationToString(HttpStatus status)
-  {
-    switch (status)
-    {
-    case HttpStatus_100_Continue:
-      return "Continue";
-
-    case HttpStatus_101_SwitchingProtocols:
-      return "Switching Protocols";
-
-    case HttpStatus_102_Processing:
-      return "Processing";
-
-    case HttpStatus_200_Ok:
-      return "OK";
-
-    case HttpStatus_201_Created:
-      return "Created";
-
-    case HttpStatus_202_Accepted:
-      return "Accepted";
-
-    case HttpStatus_203_NonAuthoritativeInformation:
-      return "Non-Authoritative Information";
-
-    case HttpStatus_204_NoContent:
-      return "No Content";
-
-    case HttpStatus_205_ResetContent:
-      return "Reset Content";
-
-    case HttpStatus_206_PartialContent:
-      return "Partial Content";
-
-    case HttpStatus_207_MultiStatus:
-      return "Multi-Status";
-
-    case HttpStatus_208_AlreadyReported:
-      return "Already Reported";
-
-    case HttpStatus_226_IMUsed:
-      return "IM Used";
-
-    case HttpStatus_300_MultipleChoices:
-      return "Multiple Choices";
-
-    case HttpStatus_301_MovedPermanently:
-      return "Moved Permanently";
-
-    case HttpStatus_302_Found:
-      return "Found";
-
-    case HttpStatus_303_SeeOther:
-      return "See Other";
-
-    case HttpStatus_304_NotModified:
-      return "Not Modified";
-
-    case HttpStatus_305_UseProxy:
-      return "Use Proxy";
-
-    case HttpStatus_307_TemporaryRedirect:
-      return "Temporary Redirect";
-
-    case HttpStatus_400_BadRequest:
-      return "Bad Request";
-
-    case HttpStatus_401_Unauthorized:
-      return "Unauthorized";
-
-    case HttpStatus_402_PaymentRequired:
-      return "Payment Required";
-
-    case HttpStatus_403_Forbidden:
-      return "Forbidden";
-
-    case HttpStatus_404_NotFound:
-      return "Not Found";
-
-    case HttpStatus_405_MethodNotAllowed:
-      return "Method Not Allowed";
-
-    case HttpStatus_406_NotAcceptable:
-      return "Not Acceptable";
-
-    case HttpStatus_407_ProxyAuthenticationRequired:
-      return "Proxy Authentication Required";
-
-    case HttpStatus_408_RequestTimeout:
-      return "Request Timeout";
-
-    case HttpStatus_409_Conflict:
-      return "Conflict";
-
-    case HttpStatus_410_Gone:
-      return "Gone";
-
-    case HttpStatus_411_LengthRequired:
-      return "Length Required";
-
-    case HttpStatus_412_PreconditionFailed:
-      return "Precondition Failed";
-
-    case HttpStatus_413_RequestEntityTooLarge:
-      return "Request Entity Too Large";
-
-    case HttpStatus_414_RequestUriTooLong:
-      return "Request-URI Too Long";
-
-    case HttpStatus_415_UnsupportedMediaType:
-      return "Unsupported Media Type";
-
-    case HttpStatus_416_RequestedRangeNotSatisfiable:
-      return "Requested Range Not Satisfiable";
-
-    case HttpStatus_417_ExpectationFailed:
-      return "Expectation Failed";
-
-    case HttpStatus_422_UnprocessableEntity:
-      return "Unprocessable Entity";
-
-    case HttpStatus_423_Locked:
-      return "Locked";
-
-    case HttpStatus_424_FailedDependency:
-      return "Failed Dependency";
-
-    case HttpStatus_426_UpgradeRequired:
-      return "Upgrade Required";
-
-    case HttpStatus_500_InternalServerError:
-      return "Internal Server Error";
-
-    case HttpStatus_501_NotImplemented:
-      return "Not Implemented";
-
-    case HttpStatus_502_BadGateway:
-      return "Bad Gateway";
-
-    case HttpStatus_503_ServiceUnavailable:
-      return "Service Unavailable";
-
-    case HttpStatus_504_GatewayTimeout:
-      return "Gateway Timeout";
-
-    case HttpStatus_505_HttpVersionNotSupported:
-      return "HTTP Version Not Supported";
-
-    case HttpStatus_506_VariantAlsoNegotiates:
-      return "Variant Also Negotiates";
-
-    case HttpStatus_507_InsufficientStorage:
-      return "Insufficient Storage";
-
-    case HttpStatus_509_BandwidthLimitExceeded:
-      return "Bandwidth Limit Exceeded";
-
-    case HttpStatus_510_NotExtended:
-      return "Not Extended";
-
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return "Patient";
-
-      case ResourceType_Study:
-        return "Study";
-
-      case ResourceType_Series:
-        return "Series";
-
-      case ResourceType_Instance:
-        return "Instance";
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(ImageFormat format)
-  {
-    switch (format)
-    {
-      case ImageFormat_Png:
-        return "Png";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(Encoding encoding)
-  {
-    switch (encoding)
-    {
-      case Encoding_Ascii:
-        return "Ascii";
-
-      case Encoding_Utf8:
-        return "Utf8";
-
-      case Encoding_Latin1:
-        return "Latin1";
-
-      case Encoding_Latin2:
-        return "Latin2";
-
-      case Encoding_Latin3:
-        return "Latin3";
-
-      case Encoding_Latin4:
-        return "Latin4";
-
-      case Encoding_Latin5:
-        return "Latin5";
-
-      case Encoding_Cyrillic:
-        return "Cyrillic";
-
-      case Encoding_Windows1251:
-        return "Windows1251";
-
-      case Encoding_Arabic:
-        return "Arabic";
-
-      case Encoding_Greek:
-        return "Greek";
-
-      case Encoding_Hebrew:
-        return "Hebrew";
-
-      case Encoding_Thai:
-        return "Thai";
-
-      case Encoding_Japanese:
-        return "Japanese";
-
-      case Encoding_Chinese:
-        return "Chinese";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(PhotometricInterpretation photometric)
-  {
-    switch (photometric)
-    {
-      case PhotometricInterpretation_RGB:
-        return "RGB";
-
-      case PhotometricInterpretation_Monochrome1:
-        return "Monochrome1";
-
-      case PhotometricInterpretation_Monochrome2:
-        return "Monochrome2";
-
-      case PhotometricInterpretation_ARGB:
-        return "ARGB";
-
-      case PhotometricInterpretation_CMYK:
-        return "CMYK";
-
-      case PhotometricInterpretation_HSV:
-        return "HSV";
-
-      case PhotometricInterpretation_Palette:
-        return "Palette color";
-
-      case PhotometricInterpretation_YBRFull:
-        return "YBR full";
-
-      case PhotometricInterpretation_YBRFull422:
-        return "YBR full 422";
-
-      case PhotometricInterpretation_YBRPartial420:
-        return "YBR partial 420"; 
-
-      case PhotometricInterpretation_YBRPartial422:
-        return "YBR partial 422"; 
-
-      case PhotometricInterpretation_YBR_ICT:
-        return "YBR ICT"; 
-
-      case PhotometricInterpretation_YBR_RCT:
-        return "YBR RCT"; 
-
-      case PhotometricInterpretation_Unknown:
-        return "Unknown";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(RequestOrigin origin)
-  {
-    switch (origin)
-    {
-      case RequestOrigin_Unknown:
-        return "Unknown";
-
-      case RequestOrigin_DicomProtocol:
-        return "DicomProtocol";
-
-      case RequestOrigin_RestApi:
-        return "RestApi";
-
-      case RequestOrigin_Plugins:
-        return "Plugins";
-
-      case RequestOrigin_Lua:
-        return "Lua";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(LogLevel level)
-  {
-    switch (level)
-    {
-      case LogLevel_Error:
-        return "ERROR";
-
-      case LogLevel_Warning:
-        return "WARNING";
-
-      case LogLevel_Info:
-        return "INFO";
-
-      case LogLevel_Trace:
-        return "TRACE";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(PixelFormat format)
-  {
-    switch (format)
-    {
-      case PixelFormat_RGB24:
-        return "RGB24";
-
-      case PixelFormat_RGBA32:
-        return "RGBA32";
-
-      case PixelFormat_BGRA32:
-        return "BGRA32";
-
-      case PixelFormat_Grayscale8:
-        return "Grayscale (unsigned 8bpp)";
-
-      case PixelFormat_Grayscale16:
-        return "Grayscale (unsigned 16bpp)";
-
-      case PixelFormat_SignedGrayscale16:
-        return "Grayscale (signed 16bpp)";
-
-      case PixelFormat_Float32:
-        return "Grayscale (float 32bpp)";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  Encoding StringToEncoding(const char* encoding)
-  {
-    std::string s(encoding);
-    Toolbox::ToUpperCase(s);
-
-    if (s == "UTF8")
-    {
-      return Encoding_Utf8;
-    }
-
-    if (s == "ASCII")
-    {
-      return Encoding_Ascii;
-    }
-
-    if (s == "LATIN1")
-    {
-      return Encoding_Latin1;
-    }
-
-    if (s == "LATIN2")
-    {
-      return Encoding_Latin2;
-    }
-
-    if (s == "LATIN3")
-    {
-      return Encoding_Latin3;
-    }
-
-    if (s == "LATIN4")
-    {
-      return Encoding_Latin4;
-    }
-
-    if (s == "LATIN5")
-    {
-      return Encoding_Latin5;
-    }
-
-    if (s == "CYRILLIC")
-    {
-      return Encoding_Cyrillic;
-    }
-
-    if (s == "WINDOWS1251")
-    {
-      return Encoding_Windows1251;
-    }
-
-    if (s == "ARABIC")
-    {
-      return Encoding_Arabic;
-    }
-
-    if (s == "GREEK")
-    {
-      return Encoding_Greek;
-    }
-
-    if (s == "HEBREW")
-    {
-      return Encoding_Hebrew;
-    }
-
-    if (s == "THAI")
-    {
-      return Encoding_Thai;
-    }
-
-    if (s == "JAPANESE")
-    {
-      return Encoding_Japanese;
-    }
-
-    if (s == "CHINESE")
-    {
-      return Encoding_Chinese;
-    }
-
-    throw OrthancException(ErrorCode_ParameterOutOfRange);
-  }
-
-
-  ResourceType StringToResourceType(const char* type)
-  {
-    std::string s(type);
-    Toolbox::ToUpperCase(s);
-
-    if (s == "PATIENT" || s == "PATIENTS")
-    {
-      return ResourceType_Patient;
-    }
-    else if (s == "STUDY" || s == "STUDIES")
-    {
-      return ResourceType_Study;
-    }
-    else if (s == "SERIES")
-    {
-      return ResourceType_Series;
-    }
-    else if (s == "INSTANCE"  || s == "IMAGE" || 
-             s == "INSTANCES" || s == "IMAGES")
-    {
-      return ResourceType_Instance;
-    }
-
-    throw OrthancException(ErrorCode_ParameterOutOfRange);
-  }
-
-
-  ImageFormat StringToImageFormat(const char* format)
-  {
-    std::string s(format);
-    Toolbox::ToUpperCase(s);
-
-    if (s == "PNG")
-    {
-      return ImageFormat_Png;
-    }
-
-    throw OrthancException(ErrorCode_ParameterOutOfRange);
-  }
-
-
-  LogLevel StringToLogLevel(const char *level)
-  {
-    if (strcmp(level, "ERROR") == 0)
-    {
-      return LogLevel_Error;
-    }
-    else if (strcmp(level, "WARNING") == 0)
-    {
-      return LogLevel_Warning;
-    }
-    else if (strcmp(level, "INFO") == 0)
-    {
-      return LogLevel_Info;
-    }
-    else if (strcmp(level, "TRACE") == 0)
-    {
-      return LogLevel_Trace;
-    }
-    else 
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
-  ValueRepresentation StringToValueRepresentation(const std::string& vr,
-                                                  bool throwIfUnsupported)
-  {
-    if (vr == "AE")
-    {
-      return ValueRepresentation_ApplicationEntity;
-    }
-    else if (vr == "AS")
-    {
-      return ValueRepresentation_AgeString;
-    }
-    else if (vr == "AT")
-    {
-      return ValueRepresentation_AttributeTag;
-    }
-    else if (vr == "CS")
-    {
-      return ValueRepresentation_CodeString;
-    }
-    else if (vr == "DA")
-    {
-      return ValueRepresentation_Date;
-    }
-    else if (vr == "DS")
-    {
-      return ValueRepresentation_DecimalString;
-    }
-    else if (vr == "DT")
-    {
-      return ValueRepresentation_DateTime;
-    }
-    else if (vr == "FL")
-    {
-      return ValueRepresentation_FloatingPointSingle;
-    }
-    else if (vr == "FD")
-    {
-      return ValueRepresentation_FloatingPointDouble;
-    }
-    else if (vr == "IS")
-    {
-      return ValueRepresentation_IntegerString;
-    }
-    else if (vr == "LO")
-    {
-      return ValueRepresentation_LongString;
-    }
-    else if (vr == "LT")
-    {
-      return ValueRepresentation_LongText;
-    }
-    else if (vr == "OB")
-    {
-      return ValueRepresentation_OtherByte;
-    }
-    else if (vr == "OD")
-    {
-      return ValueRepresentation_OtherDouble;
-    }
-    else if (vr == "OF")
-    {
-      return ValueRepresentation_OtherFloat;
-    }
-    else if (vr == "OL")
-    {
-      return ValueRepresentation_OtherLong;
-    }
-    else if (vr == "OW")
-    {
-      return ValueRepresentation_OtherWord;
-    }
-    else if (vr == "PN")
-    {
-      return ValueRepresentation_PersonName;
-    }
-    else if (vr == "SH")
-    {
-      return ValueRepresentation_ShortString;
-    }
-    else if (vr == "SL")
-    {
-      return ValueRepresentation_SignedLong;
-    }
-    else if (vr == "SQ")
-    {
-      return ValueRepresentation_Sequence;
-    }
-    else if (vr == "SS")
-    {
-      return ValueRepresentation_SignedShort;
-    }
-    else if (vr == "ST")
-    {
-      return ValueRepresentation_ShortText;
-    }
-    else if (vr == "TM")
-    {
-      return ValueRepresentation_Time;
-    }
-    else if (vr == "UC")
-    {
-      return ValueRepresentation_UnlimitedCharacters;
-    }
-    else if (vr == "UI")
-    {
-      return ValueRepresentation_UniqueIdentifier;
-    }
-    else if (vr == "UL")
-    {
-      return ValueRepresentation_UnsignedLong;
-    }
-    else if (vr == "UN")
-    {
-      return ValueRepresentation_Unknown;
-    }
-    else if (vr == "UR")
-    {
-      return ValueRepresentation_UniversalResource;
-    }
-    else if (vr == "US")
-    {
-      return ValueRepresentation_UnsignedShort;
-    }
-    else if (vr == "UT")
-    {
-      return ValueRepresentation_UnlimitedText;
-    }
-    else
-    {
-      std::string s = "Unsupported value representation encountered: " + vr;
-
-      if (throwIfUnsupported)
-      {
-        LOG(ERROR) << s;
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        LOG(INFO) << s;
-        return ValueRepresentation_NotSupported;
-      }
-    }
-  }
-
-
-  unsigned int GetBytesPerPixel(PixelFormat format)
-  {
-    switch (format)
-    {
-      case PixelFormat_Grayscale8:
-        return 1;
-
-      case PixelFormat_Grayscale16:
-      case PixelFormat_SignedGrayscale16:
-        return 2;
-
-      case PixelFormat_RGB24:
-        return 3;
-
-      case PixelFormat_RGBA32:
-      case PixelFormat_BGRA32:
-        return 4;
-
-      case PixelFormat_Float32:
-        assert(sizeof(float) == 4);
-        return 4;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  bool GetDicomEncoding(Encoding& encoding,
-                        const char* specificCharacterSet)
-  {
-    std::string s = Toolbox::StripSpaces(specificCharacterSet);
-    Toolbox::ToUpperCase(s);
-
-    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
-    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
-    if (s == "ISO_IR 6" ||
-        s == "ISO 2022 IR 6")
-    {
-      encoding = Encoding_Ascii;
-    }
-    else if (s == "ISO_IR 192")
-    {
-      encoding = Encoding_Utf8;
-    }
-    else if (s == "ISO_IR 100" ||
-             s == "ISO 2022 IR 100")
-    {
-      encoding = Encoding_Latin1;
-    }
-    else if (s == "ISO_IR 101" ||
-             s == "ISO 2022 IR 101")
-    {
-      encoding = Encoding_Latin2;
-    }
-    else if (s == "ISO_IR 109" ||
-             s == "ISO 2022 IR 109")
-    {
-      encoding = Encoding_Latin3;
-    }
-    else if (s == "ISO_IR 110" ||
-             s == "ISO 2022 IR 110")
-    {
-      encoding = Encoding_Latin4;
-    }
-    else if (s == "ISO_IR 148" ||
-             s == "ISO 2022 IR 148")
-    {
-      encoding = Encoding_Latin5;
-    }
-    else if (s == "ISO_IR 144" ||
-             s == "ISO 2022 IR 144")
-    {
-      encoding = Encoding_Cyrillic;
-    }
-    else if (s == "ISO_IR 127" ||
-             s == "ISO 2022 IR 127")
-    {
-      encoding = Encoding_Arabic;
-    }
-    else if (s == "ISO_IR 126" ||
-             s == "ISO 2022 IR 126")
-    {
-      encoding = Encoding_Greek;
-    }
-    else if (s == "ISO_IR 138" ||
-             s == "ISO 2022 IR 138")
-    {
-      encoding = Encoding_Hebrew;
-    }
-    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
-    {
-      encoding = Encoding_Thai;
-    }
-    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
-    {
-      encoding = Encoding_Japanese;
-    }
-    else if (s == "GB18030")
-    {
-      encoding = Encoding_Chinese;
-    }
-    /*
-      else if (s == "ISO 2022 IR 149")
-      {
-      TODO
-      }
-      else if (s == "ISO 2022 IR 159")
-      {
-      TODO
-      }
-      else if (s == "ISO 2022 IR 87")
-      {
-      TODO
-      }
-    */
-    else
-    {
-      return false;
-    }
-
-    // The encoding was properly detected
-    return true;
-  }
-
-
-  ResourceType GetChildResourceType(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return ResourceType_Study;
-
-      case ResourceType_Study:
-        return ResourceType_Series;
-        
-      case ResourceType_Series:
-        return ResourceType_Instance;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  ResourceType GetParentResourceType(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Study:
-        return ResourceType_Patient;
-        
-      case ResourceType_Series:
-        return ResourceType_Study;
-
-      case ResourceType_Instance:
-        return ResourceType_Series;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  DicomModule GetModule(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return DicomModule_Patient;
-
-      case ResourceType_Study:
-        return DicomModule_Study;
-        
-      case ResourceType_Series:
-        return DicomModule_Series;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-
-  const char* GetDicomSpecificCharacterSet(Encoding encoding)
-  {
-    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
-    switch (encoding)
-    {
-      case Encoding_Ascii:
-        return "ISO_IR 6";
-
-      case Encoding_Utf8:
-        return "ISO_IR 192";
-
-      case Encoding_Latin1:
-        return "ISO_IR 100";
-
-      case Encoding_Latin2:
-        return "ISO_IR 101";
-
-      case Encoding_Latin3:
-        return "ISO_IR 109";
-
-      case Encoding_Latin4:
-        return "ISO_IR 110";
-
-      case Encoding_Latin5:
-        return "ISO_IR 148";
-
-      case Encoding_Cyrillic:
-        return "ISO_IR 144";
-
-      case Encoding_Arabic:
-        return "ISO_IR 127";
-
-      case Encoding_Greek:
-        return "ISO_IR 126";
-
-      case Encoding_Hebrew:
-        return "ISO_IR 138";
-
-      case Encoding_Japanese:
-        return "ISO_IR 13";
-
-      case Encoding_Chinese:
-        return "GB18030";
-
-      case Encoding_Thai:
-        return "ISO_IR 166";
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  // This function is autogenerated by the script
-  // "Resources/GenerateErrorCodes.py"
-  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error)
-  {
-    switch (error)
-    {
-      case ErrorCode_Success:
-        return HttpStatus_200_Ok;
-
-      case ErrorCode_ParameterOutOfRange:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_BadParameterType:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_InexistentItem:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_BadRequest:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_UriSyntax:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_InexistentFile:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_BadFileFormat:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_UnknownResource:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_InexistentTag:
-        return HttpStatus_404_NotFound;
-
-      case ErrorCode_BadJson:
-        return HttpStatus_400_BadRequest;
-
-      case ErrorCode_Unauthorized:
-        return HttpStatus_401_Unauthorized;
-
-      case ErrorCode_NotAcceptable:
-        return HttpStatus_406_NotAcceptable;
-
-      default:
-        return HttpStatus_500_InternalServerError;
-    }
-  }
-
-
-  bool IsUserContentType(FileContentType type)
-  {
-    return (type >= FileContentType_StartUser &&
-            type <= FileContentType_EndUser);
-  }
-
-
-  bool IsBinaryValueRepresentation(ValueRepresentation vr)
-  {
-    // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
-
-    switch (vr)
-    {
-      case ValueRepresentation_ApplicationEntity:     // AE
-      case ValueRepresentation_AgeString:             // AS
-      case ValueRepresentation_CodeString:            // CS
-      case ValueRepresentation_Date:                  // DA
-      case ValueRepresentation_DecimalString:         // DS
-      case ValueRepresentation_DateTime:              // DT
-      case ValueRepresentation_IntegerString:         // IS
-      case ValueRepresentation_LongString:            // LO
-      case ValueRepresentation_LongText:              // LT
-      case ValueRepresentation_PersonName:            // PN
-      case ValueRepresentation_ShortString:           // SH
-      case ValueRepresentation_ShortText:             // ST
-      case ValueRepresentation_Time:                  // TM
-      case ValueRepresentation_UnlimitedCharacters:   // UC
-      case ValueRepresentation_UniqueIdentifier:      // UI (UID)
-      case ValueRepresentation_UniversalResource:     // UR (URI or URL)
-      case ValueRepresentation_UnlimitedText:         // UT
-      {
-        return false;
-      }
-
-      /**
-       * Below are all the VR whose character repertoire is tagged as
-       * "not applicable"
-       **/
-      case ValueRepresentation_AttributeTag:          // AT (2 x uint16_t)
-      case ValueRepresentation_FloatingPointSingle:   // FL (float)
-      case ValueRepresentation_FloatingPointDouble:   // FD (double)
-      case ValueRepresentation_OtherByte:             // OB
-      case ValueRepresentation_OtherDouble:           // OD
-      case ValueRepresentation_OtherFloat:            // OF
-      case ValueRepresentation_OtherLong:             // OL
-      case ValueRepresentation_OtherWord:             // OW
-      case ValueRepresentation_SignedLong:            // SL (int32_t)
-      case ValueRepresentation_Sequence:              // SQ
-      case ValueRepresentation_SignedShort:           // SS (int16_t)
-      case ValueRepresentation_UnsignedLong:          // UL (uint32_t)
-      case ValueRepresentation_Unknown:               // UN
-      case ValueRepresentation_UnsignedShort:         // US (uint16_t)
-      {
-        return true;
-      }
-
-      case ValueRepresentation_NotSupported:
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-}
--- a/Resources/Orthanc/Core/Enumerations.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,545 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <string>
-
-namespace Orthanc
-{
-  enum Endianness
-  {
-    Endianness_Unknown,
-    Endianness_Big,
-    Endianness_Little
-  };
-
-  // This enumeration is autogenerated by the script
-  // "Resources/GenerateErrorCodes.py"
-  enum ErrorCode
-  {
-    ErrorCode_InternalError = -1    /*!< Internal error */,
-    ErrorCode_Success = 0    /*!< Success */,
-    ErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
-    ErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
-    ErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
-    ErrorCode_NotEnoughMemory = 4    /*!< The server hosting Orthanc is running out of memory */,
-    ErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
-    ErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
-    ErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
-    ErrorCode_BadRequest = 8    /*!< Bad request */,
-    ErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
-    ErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
-    ErrorCode_Database = 11    /*!< Error with the database engine */,
-    ErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
-    ErrorCode_InexistentFile = 13    /*!< Inexistent file */,
-    ErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
-    ErrorCode_BadFileFormat = 15    /*!< Bad file format */,
-    ErrorCode_Timeout = 16    /*!< Timeout */,
-    ErrorCode_UnknownResource = 17    /*!< Unknown resource */,
-    ErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
-    ErrorCode_FullStorage = 19    /*!< The file storage is full */,
-    ErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
-    ErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
-    ErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
-    ErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
-    ErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
-    ErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
-    ErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
-    ErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
-    ErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
-    ErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
-    ErrorCode_BadFont = 30    /*!< Badly formatted font file */,
-    ErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
-    ErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
-    ErrorCode_EmptyRequest = 33    /*!< The request is empty */,
-    ErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
-    ErrorCode_NullPointer = 35    /*!< Cannot handle a NULL pointer */,
-    ErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
-    ErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
-    ErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
-    ErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
-    ErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
-    ErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
-    ErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
-    ErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
-    ErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
-    ErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
-    ErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
-    ErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
-    ErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
-    ErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
-    ErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
-    ErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
-    ErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
-    ErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
-    ErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
-    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
-    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
-    ErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
-    ErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
-    ErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
-    ErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
-    ErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
-    ErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
-    ErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
-    ErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
-    ErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
-    ErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
-    ErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
-    ErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
-    ErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
-    ErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
-    ErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
-    ErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
-    ErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
-    ErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
-    ErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
-    ErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
-    ErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
-    ErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
-    ErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
-    ErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
-    ErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
-    ErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
-    ErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
-    ErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
-    ErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
-    ErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
-    ErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
-    ErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
-    ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
-    ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
-    ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
-    ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
-    ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
-    ErrorCode_START_PLUGINS = 1000000
-  };
-
-  enum LogLevel
-  {
-    LogLevel_Error,
-    LogLevel_Warning,
-    LogLevel_Info,
-    LogLevel_Trace
-  };
-
-
-  /**
-   * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
-   **/
-  enum PixelFormat
-  {
-    /**
-     * {summary}{Color image in RGB24 format.}
-     * {description}{This format describes a color image. The pixels are stored in 3
-     * consecutive bytes. The memory layout is RGB.}
-     **/
-    PixelFormat_RGB24 = 1,
-
-    /**
-     * {summary}{Color image in RGBA32 format.}
-     * {description}{This format describes a color image. The pixels are stored in 4
-     * consecutive bytes. The memory layout is RGBA.}
-     **/
-    PixelFormat_RGBA32 = 2,
-
-    /**
-     * {summary}{Graylevel 8bpp image.}
-     * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
-     **/
-    PixelFormat_Grayscale8 = 3,
-      
-    /**
-     * {summary}{Graylevel, unsigned 16bpp image.}
-     * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.}
-     **/
-    PixelFormat_Grayscale16 = 4,
-      
-    /**
-     * {summary}{Graylevel, signed 16bpp image.}
-     * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.}
-     **/
-    PixelFormat_SignedGrayscale16 = 5,
-      
-    /**
-     * {summary}{Graylevel, floating-point image.}
-     * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.}
-     **/
-    PixelFormat_Float32 = 6,
-
-    // This is the memory layout for Cairo
-    PixelFormat_BGRA32 = 7
-  };
-
-
-  /**
-   * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.}
-   **/
-  enum ImageExtractionMode
-  {
-    /**
-     * {summary}{Rescaled to 8bpp.}
-     * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.}
-     **/
-    ImageExtractionMode_Preview = 1,
-
-    /**
-     * {summary}{Truncation to the [0, 255] range.}
-     **/
-    ImageExtractionMode_UInt8 = 2,
-
-    /**
-     * {summary}{Truncation to the [0, 65535] range.}
-     **/
-    ImageExtractionMode_UInt16 = 3,
-
-    /**
-     * {summary}{Truncation to the [-32768, 32767] range.}
-     **/
-    ImageExtractionMode_Int16 = 4
-  };
-
-
-  /**
-   * Most common, non-joke and non-experimental HTTP status codes
-   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-   **/
-  enum HttpStatus
-  {
-    HttpStatus_None = -1,
-
-    // 1xx Informational
-    HttpStatus_100_Continue = 100,
-    HttpStatus_101_SwitchingProtocols = 101,
-    HttpStatus_102_Processing = 102,
-
-    // 2xx Success
-    HttpStatus_200_Ok = 200,
-    HttpStatus_201_Created = 201,
-    HttpStatus_202_Accepted = 202,
-    HttpStatus_203_NonAuthoritativeInformation = 203,
-    HttpStatus_204_NoContent = 204,
-    HttpStatus_205_ResetContent = 205,
-    HttpStatus_206_PartialContent = 206,
-    HttpStatus_207_MultiStatus = 207,
-    HttpStatus_208_AlreadyReported = 208,
-    HttpStatus_226_IMUsed = 226,
-
-    // 3xx Redirection
-    HttpStatus_300_MultipleChoices = 300,
-    HttpStatus_301_MovedPermanently = 301,
-    HttpStatus_302_Found = 302,
-    HttpStatus_303_SeeOther = 303,
-    HttpStatus_304_NotModified = 304,
-    HttpStatus_305_UseProxy = 305,
-    HttpStatus_307_TemporaryRedirect = 307,
-
-    // 4xx Client Error
-    HttpStatus_400_BadRequest = 400,
-    HttpStatus_401_Unauthorized = 401,
-    HttpStatus_402_PaymentRequired = 402,
-    HttpStatus_403_Forbidden = 403,
-    HttpStatus_404_NotFound = 404,
-    HttpStatus_405_MethodNotAllowed = 405,
-    HttpStatus_406_NotAcceptable = 406,
-    HttpStatus_407_ProxyAuthenticationRequired = 407,
-    HttpStatus_408_RequestTimeout = 408,
-    HttpStatus_409_Conflict = 409,
-    HttpStatus_410_Gone = 410,
-    HttpStatus_411_LengthRequired = 411,
-    HttpStatus_412_PreconditionFailed = 412,
-    HttpStatus_413_RequestEntityTooLarge = 413,
-    HttpStatus_414_RequestUriTooLong = 414,
-    HttpStatus_415_UnsupportedMediaType = 415,
-    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
-    HttpStatus_417_ExpectationFailed = 417,
-    HttpStatus_422_UnprocessableEntity = 422,
-    HttpStatus_423_Locked = 423,
-    HttpStatus_424_FailedDependency = 424,
-    HttpStatus_426_UpgradeRequired = 426,
-
-    // 5xx Server Error
-    HttpStatus_500_InternalServerError = 500,
-    HttpStatus_501_NotImplemented = 501,
-    HttpStatus_502_BadGateway = 502,
-    HttpStatus_503_ServiceUnavailable = 503,
-    HttpStatus_504_GatewayTimeout = 504,
-    HttpStatus_505_HttpVersionNotSupported = 505,
-    HttpStatus_506_VariantAlsoNegotiates = 506,
-    HttpStatus_507_InsufficientStorage = 507,
-    HttpStatus_509_BandwidthLimitExceeded = 509,
-    HttpStatus_510_NotExtended = 510
-  };
-
-
-  enum HttpMethod
-  {
-    HttpMethod_Get = 0,
-    HttpMethod_Post = 1,
-    HttpMethod_Delete = 2,
-    HttpMethod_Put = 3
-  };
-
-
-  enum ImageFormat
-  {
-    ImageFormat_Png = 1
-  };
-
-
-  // https://en.wikipedia.org/wiki/HTTP_compression
-  enum HttpCompression
-  {
-    HttpCompression_None,
-    HttpCompression_Deflate,
-    HttpCompression_Gzip
-  };
-
-
-  // Specific Character Sets
-  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
-  enum Encoding
-  {
-    Encoding_Ascii,
-    Encoding_Utf8,
-    Encoding_Latin1,
-    Encoding_Latin2,
-    Encoding_Latin3,
-    Encoding_Latin4,
-    Encoding_Latin5,                        // Turkish
-    Encoding_Cyrillic,
-    Encoding_Windows1251,                   // Windows-1251 (commonly used for Cyrillic)
-    Encoding_Arabic,
-    Encoding_Greek,
-    Encoding_Hebrew,
-    Encoding_Thai,                          // TIS 620-2533
-    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
-    Encoding_Chinese                        // GB18030 - Chinese simplified
-    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
-    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
-    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
-  };
-
-
-  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.2
-  enum PhotometricInterpretation
-  {
-    PhotometricInterpretation_ARGB,  // Retired
-    PhotometricInterpretation_CMYK,  // Retired
-    PhotometricInterpretation_HSV,   // Retired
-    PhotometricInterpretation_Monochrome1,
-    PhotometricInterpretation_Monochrome2,
-    PhotometricInterpretation_Palette,
-    PhotometricInterpretation_RGB,
-    PhotometricInterpretation_YBRFull,
-    PhotometricInterpretation_YBRFull422,
-    PhotometricInterpretation_YBRPartial420,
-    PhotometricInterpretation_YBRPartial422,
-    PhotometricInterpretation_YBR_ICT,
-    PhotometricInterpretation_YBR_RCT,
-    PhotometricInterpretation_Unknown
-  };
-
-  enum DicomModule
-  {
-    DicomModule_Patient,
-    DicomModule_Study,
-    DicomModule_Series,
-    DicomModule_Instance,
-    DicomModule_Image
-  };
-
-  enum RequestOrigin
-  {
-    RequestOrigin_Unknown,
-    RequestOrigin_DicomProtocol,
-    RequestOrigin_RestApi,
-    RequestOrigin_Plugins,
-    RequestOrigin_Lua
-  };
-
-  enum ServerBarrierEvent
-  {
-    ServerBarrierEvent_Stop,
-    ServerBarrierEvent_Reload  // SIGHUP signal: reload configuration file
-  };
-
-  enum FileMode
-  {
-    FileMode_ReadBinary,
-    FileMode_WriteBinary
-  };
-
-  /**
-   * The value representations Orthanc knows about. They correspond to
-   * the DICOM 2016b version of the standard.
-   * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
-   **/
-  enum ValueRepresentation
-  {
-    ValueRepresentation_ApplicationEntity = 1,     // AE
-    ValueRepresentation_AgeString = 2,             // AS
-    ValueRepresentation_AttributeTag = 3,          // AT (2 x uint16_t)
-    ValueRepresentation_CodeString = 4,            // CS
-    ValueRepresentation_Date = 5,                  // DA
-    ValueRepresentation_DecimalString = 6,         // DS
-    ValueRepresentation_DateTime = 7,              // DT
-    ValueRepresentation_FloatingPointSingle = 8,   // FL (float)
-    ValueRepresentation_FloatingPointDouble = 9,   // FD (double)
-    ValueRepresentation_IntegerString = 10,        // IS
-    ValueRepresentation_LongString = 11,           // LO
-    ValueRepresentation_LongText = 12,             // LT
-    ValueRepresentation_OtherByte = 13,            // OB
-    ValueRepresentation_OtherDouble = 14,          // OD
-    ValueRepresentation_OtherFloat = 15,           // OF
-    ValueRepresentation_OtherLong = 16,            // OL
-    ValueRepresentation_OtherWord = 17,            // OW
-    ValueRepresentation_PersonName = 18,           // PN
-    ValueRepresentation_ShortString = 19,          // SH
-    ValueRepresentation_SignedLong = 20,           // SL (int32_t)
-    ValueRepresentation_Sequence = 21,             // SQ
-    ValueRepresentation_SignedShort = 22,          // SS (int16_t)
-    ValueRepresentation_ShortText = 23,            // ST
-    ValueRepresentation_Time = 24,                 // TM
-    ValueRepresentation_UnlimitedCharacters = 25,  // UC
-    ValueRepresentation_UniqueIdentifier = 26,     // UI (UID)
-    ValueRepresentation_UnsignedLong = 27,         // UL (uint32_t)
-    ValueRepresentation_Unknown = 28,              // UN
-    ValueRepresentation_UniversalResource = 29,    // UR (URI or URL)
-    ValueRepresentation_UnsignedShort = 30,        // US (uint16_t)
-    ValueRepresentation_UnlimitedText = 31,        // UT
-    ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
-  };
-
-
-  /**
-   * WARNING: Do not change the explicit values in the enumerations
-   * below this point. This would result in incompatible databases
-   * between versions of Orthanc!
-   **/
-
-  enum CompressionType
-  {
-    /**
-     * Buffer/file that is stored as-is, in a raw fashion, without
-     * compression.
-     **/
-    CompressionType_None = 1,
-
-    /**
-     * Buffer that is compressed using the "deflate" algorithm (RFC
-     * 1951), wrapped inside the zlib data format (RFC 1950), prefixed
-     * with a "uint64_t" (8 bytes) that encodes the size of the
-     * uncompressed buffer. If the compressed buffer is empty, its
-     * represents an empty uncompressed buffer. This format is
-     * internal to Orthanc. If the 8 first bytes are skipped AND the
-     * buffer is non-empty, the buffer is compatible with the
-     * "deflate" HTTP compression.
-     **/
-    CompressionType_ZlibWithSize = 2
-  };
-
-  enum FileContentType
-  {
-    // If you add a value below, insert it in "PluginStorageArea" in
-    // the file "Plugins/Engine/OrthancPlugins.cpp"
-    FileContentType_Unknown = 0,
-    FileContentType_Dicom = 1,
-    FileContentType_DicomAsJson = 2,
-
-    // Make sure that the value "65535" can be stored into this enumeration
-    FileContentType_StartUser = 1024,
-    FileContentType_EndUser = 65535
-  };
-
-  enum ResourceType
-  {
-    ResourceType_Patient = 1,
-    ResourceType_Study = 2,
-    ResourceType_Series = 3,
-    ResourceType_Instance = 4
-  };
-
-
-  const char* EnumerationToString(ErrorCode code);
-
-  const char* EnumerationToString(HttpMethod method);
-
-  const char* EnumerationToString(HttpStatus status);
-
-  const char* EnumerationToString(ResourceType type);
-
-  const char* EnumerationToString(ImageFormat format);
-
-  const char* EnumerationToString(Encoding encoding);
-
-  const char* EnumerationToString(PhotometricInterpretation photometric);
-
-  const char* EnumerationToString(LogLevel level);
-
-  const char* EnumerationToString(RequestOrigin origin);
-
-  const char* EnumerationToString(PixelFormat format);
-
-  Encoding StringToEncoding(const char* encoding);
-
-  ResourceType StringToResourceType(const char* type);
-
-  ImageFormat StringToImageFormat(const char* format);
-
-  LogLevel StringToLogLevel(const char* level);
-
-  ValueRepresentation StringToValueRepresentation(const std::string& vr,
-                                                  bool throwIfUnsupported);
-
-  unsigned int GetBytesPerPixel(PixelFormat format);
-
-  bool GetDicomEncoding(Encoding& encoding,
-                        const char* specificCharacterSet);
-
-  ResourceType GetChildResourceType(ResourceType type);
-
-  ResourceType GetParentResourceType(ResourceType type);
-
-  DicomModule GetModule(ResourceType type);
-
-  const char* GetDicomSpecificCharacterSet(Encoding encoding);
-
-  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
-
-  bool IsUserContentType(FileContentType type);
-
-  bool IsBinaryValueRepresentation(ValueRepresentation vr);
-}
--- a/Resources/Orthanc/Core/HttpClient.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,840 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "HttpClient.h"
-
-#include "Toolbox.h"
-#include "OrthancException.h"
-#include "Logging.h"
-#include "ChunkedBuffer.h"
-#include "SystemToolbox.h"
-
-#include <string.h>
-#include <curl/curl.h>
-#include <boost/algorithm/string/predicate.hpp>
-#include <boost/thread/mutex.hpp>
-
-
-#if ORTHANC_ENABLE_SSL == 1
-// For OpenSSL initialization and finalization
-#  include <openssl/conf.h>
-#  include <openssl/engine.h>
-#  include <openssl/err.h>
-#  include <openssl/evp.h>
-#  include <openssl/ssl.h>
-#endif
-
-
-#if ORTHANC_ENABLE_PKCS11 == 1
-#  include "Pkcs11.h"
-#endif
-
-
-extern "C"
-{
-  static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
-  {
-    if (code == CURLE_OK)
-    {
-      code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
-      return code;
-    }
-    else
-    {
-      *status = 0;
-      return code;
-    }
-  }
-
-  // This is a dummy wrapper function to suppress any OpenSSL-related
-  // problem in valgrind. Inlining is prevented.
-#if defined(__GNUC__) || defined(__clang__)
-    __attribute__((noinline)) 
-#endif
-    static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status)
-  {
-    return GetHttpStatus(curl_easy_perform(curl), curl, status);
-  }
-}
-
-
-
-namespace Orthanc
-{
-  class HttpClient::GlobalParameters
-  {
-  private:
-    boost::mutex    mutex_;
-    bool            httpsVerifyPeers_;
-    std::string     httpsCACertificates_;
-    std::string     proxy_;
-    long            timeout_;
-
-    GlobalParameters() : 
-      httpsVerifyPeers_(true),
-      timeout_(0)
-    {
-    }
-
-  public:
-    // Singleton pattern
-    static GlobalParameters& GetInstance()
-    {
-      static GlobalParameters parameters;
-      return parameters;
-    }
-
-    void ConfigureSsl(bool httpsVerifyPeers,
-                      const std::string& httpsCACertificates)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      httpsVerifyPeers_ = httpsVerifyPeers;
-      httpsCACertificates_ = httpsCACertificates;
-    }
-
-    void GetSslConfiguration(bool& httpsVerifyPeers,
-                             std::string& httpsCACertificates)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      httpsVerifyPeers = httpsVerifyPeers_;
-      httpsCACertificates = httpsCACertificates_;
-    }
-
-    void SetDefaultProxy(const std::string& proxy)
-    {
-      LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy;
-
-      {
-        boost::mutex::scoped_lock lock(mutex_);
-        proxy_ = proxy;
-      }
-    }
-
-    void GetDefaultProxy(std::string& target)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      target = proxy_;
-    }
-
-    void SetDefaultTimeout(long seconds)
-    {
-      LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds";
-
-      {
-        boost::mutex::scoped_lock lock(mutex_);
-        timeout_ = seconds;
-      }
-    }
-
-    long GetDefaultTimeout()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      return timeout_;
-    }
-
-#if ORTHANC_ENABLE_PKCS11 == 1
-    bool IsPkcs11Initialized()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      return Pkcs11::IsInitialized();
-    }
-
-    void InitializePkcs11(const std::string& module,
-                          const std::string& pin,
-                          bool verbose)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      Pkcs11::Initialize(module, pin, verbose);
-    }
-#endif
-  };
-
-
-  struct HttpClient::PImpl
-  {
-    CURL* curl_;
-    struct curl_slist *defaultPostHeaders_;
-    struct curl_slist *userHeaders_;
-  };
-
-
-  static void ThrowException(HttpStatus status)
-  {
-    switch (status)
-    {
-      case HttpStatus_400_BadRequest:
-        throw OrthancException(ErrorCode_BadRequest);
-
-      case HttpStatus_401_Unauthorized:
-      case HttpStatus_403_Forbidden:
-        throw OrthancException(ErrorCode_Unauthorized);
-
-      case HttpStatus_404_NotFound:
-        throw OrthancException(ErrorCode_UnknownResource);
-
-      default:
-        throw OrthancException(ErrorCode_NetworkProtocol);
-    }
-  }
-
-
-  static CURLcode CheckCode(CURLcode code)
-  {
-    if (code == CURLE_NOT_BUILT_IN)
-    {
-      LOG(ERROR) << "Your libcurl does not contain a required feature, "
-                 << "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF";
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    if (code != CURLE_OK)
-    {
-      LOG(ERROR) << "libCURL error: " + std::string(curl_easy_strerror(code));
-      throw OrthancException(ErrorCode_NetworkProtocol);
-    }
-
-    return code;
-  }
-
-
-  static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload)
-  {
-    ChunkedBuffer& target = *(static_cast<ChunkedBuffer*>(payload));
-
-    size_t length = size * nmemb;
-    if (length == 0)
-    {
-      return 0;
-    }
-    else
-    {
-      target.AddChunk(buffer, length);
-      return length;
-    }
-  }
-
-
-  struct CurlHeaderParameters
-  {
-    bool lowerCase_;
-    HttpClient::HttpHeaders* headers_;
-  };
-
-
-  static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload)
-  {
-    CurlHeaderParameters& parameters = *(static_cast<CurlHeaderParameters*>(payload));
-    assert(parameters.headers_ != NULL);
-
-    size_t length = size * nmemb;
-    if (length == 0)
-    {
-      return 0;
-    }
-    else
-    {
-      std::string s(reinterpret_cast<const char*>(buffer), length);
-      std::size_t colon = s.find(':');
-      std::size_t eol = s.find("\r\n");
-      if (colon != std::string::npos &&
-          eol != std::string::npos)
-      {
-        std::string tmp(s.substr(0, colon));
-
-        if (parameters.lowerCase_)
-        {
-          Toolbox::ToLowerCase(tmp);
-        }
-
-        std::string key = Toolbox::StripSpaces(tmp);
-
-        if (!key.empty())
-        {
-          std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol));
-          (*parameters.headers_) [key] = value;
-        }
-      }
-
-      return length;
-    }
-  }
-
-
-  void HttpClient::Setup()
-  {
-    pimpl_->userHeaders_ = NULL;
-    pimpl_->defaultPostHeaders_ = NULL;
-    if ((pimpl_->defaultPostHeaders_ = curl_slist_append(pimpl_->defaultPostHeaders_, "Expect:")) == NULL)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    pimpl_->curl_ = curl_easy_init();
-    if (!pimpl_->curl_)
-    {
-      curl_slist_free_all(pimpl_->defaultPostHeaders_);
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1));
-
-    // This fixes the "longjmp causes uninitialized stack frame" crash
-    // that happens on modern Linux versions.
-    // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1));
-
-    url_ = "";
-    method_ = HttpMethod_Get;
-    lastStatus_ = HttpStatus_200_Ok;
-    SetVerbose(false);
-    timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout();
-    GlobalParameters::GetInstance().GetDefaultProxy(proxy_);
-    GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_);    
-  }
-
-
-  HttpClient::HttpClient() : 
-    pimpl_(new PImpl), 
-    verifyPeers_(true),
-    pkcs11Enabled_(false),
-    headersToLowerCase_(true),
-    redirectionFollowed_(true)
-  {
-    Setup();
-  }
-
-
-  HttpClient::HttpClient(const WebServiceParameters& service,
-                         const std::string& uri) : 
-    pimpl_(new PImpl), 
-    verifyPeers_(true),
-    headersToLowerCase_(true),
-    redirectionFollowed_(true)
-  {
-    Setup();
-
-    if (service.GetUsername().size() != 0 && 
-        service.GetPassword().size() != 0)
-    {
-      SetCredentials(service.GetUsername().c_str(), 
-                     service.GetPassword().c_str());
-    }
-
-    if (!service.GetCertificateFile().empty())
-    {
-      SetClientCertificate(service.GetCertificateFile(),
-                           service.GetCertificateKeyFile(),
-                           service.GetCertificateKeyPassword());
-    }
-
-    SetPkcs11Enabled(service.IsPkcs11Enabled());
-
-    SetUrl(service.GetUrl() + uri);
-  }
-
-
-  HttpClient::~HttpClient()
-  {
-    curl_easy_cleanup(pimpl_->curl_);
-    curl_slist_free_all(pimpl_->defaultPostHeaders_);
-    ClearHeaders();
-  }
-
-
-  void HttpClient::SetVerbose(bool isVerbose)
-  {
-    isVerbose_ = isVerbose;
-
-    if (isVerbose_)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1));
-    }
-    else
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0));
-    }
-  }
-
-
-  void HttpClient::AddHeader(const std::string& key,
-                             const std::string& value)
-  {
-    if (key.empty())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    std::string s = key + ": " + value;
-
-    if ((pimpl_->userHeaders_ = curl_slist_append(pimpl_->userHeaders_, s.c_str())) == NULL)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-  }
-
-
-  void HttpClient::ClearHeaders()
-  {
-    if (pimpl_->userHeaders_ != NULL)
-    {
-      curl_slist_free_all(pimpl_->userHeaders_);
-      pimpl_->userHeaders_ = NULL;
-    }
-  }
-
-
-  bool HttpClient::ApplyInternal(std::string& answerBody,
-                                 HttpHeaders* answerHeaders)
-  {
-    answerBody.clear();
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
-
-    CurlHeaderParameters headerParameters;
-
-    if (answerHeaders == NULL)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL));
-    }
-    else
-    {
-      headerParameters.lowerCase_ = headersToLowerCase_;
-      headerParameters.headers_ = answerHeaders;
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters));
-    }
-
-#if ORTHANC_ENABLE_SSL == 1
-    // Setup HTTPS-related options
-
-    if (verifyPeers_)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2));  // libcurl default is strict verifyhost
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); 
-    }
-    else
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); 
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); 
-    }
-#endif
-
-    // Setup the HTTPS client certificate
-    if (!clientCertificateFile_.empty() &&
-        pkcs11Enabled_)
-    {
-      LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication";
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (pkcs11Enabled_)
-    {
-#if ORTHANC_ENABLE_PKCS11 == 1
-      if (GlobalParameters::GetInstance().IsPkcs11Initialized())
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier()));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG"));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG"));
-      }
-      else
-      {
-        LOG(ERROR) << "Cannot use PKCS#11 for a HTTPS request, because it has not been initialized";
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
-#else
-      LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11";
-      throw OrthancException(ErrorCode_InternalError);
-#endif
-    }
-    else if (!clientCertificateFile_.empty())
-    {
-#if ORTHANC_ENABLE_SSL == 1
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
-
-      if (!clientCertificateKeyPassword_.empty())
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
-      }
-
-      // NB: If no "clientKeyFile_" is provided, the key must be
-      // prepended to the certificate file
-      if (!clientCertificateKeyFile_.empty())
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
-      }
-#else
-      LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication";
-      throw OrthancException(ErrorCode_InternalError);
-#endif
-    }
-
-    // Reset the parameters from previous calls to Apply()
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0L));
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL));
-
-    if (redirectionFollowed_)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1L));
-    }
-    else
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 0L));
-    }
-
-    // Set timeouts
-    if (timeout_ <= 0)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, 10));  /* default: 10 seconds */
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, 10));  /* default: 10 seconds */
-    }
-    else
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, timeout_));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, timeout_));
-    }
-
-    if (credentials_.size() != 0)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str()));
-    }
-
-    if (proxy_.size() != 0)
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str()));
-    }
-
-    switch (method_)
-    {
-    case HttpMethod_Get:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L));
-      break;
-
-    case HttpMethod_Post:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
-
-      if (pimpl_->userHeaders_ == NULL)
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_));
-      }
-
-      break;
-
-    case HttpMethod_Delete:
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE"));
-      break;
-
-    case HttpMethod_Put:
-      // http://stackoverflow.com/a/7570281/881731: Don't use
-      // CURLOPT_PUT if there is a body
-
-      // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L));
-
-      curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */
-
-      if (pimpl_->userHeaders_ == NULL)
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_));
-      }
-
-      break;
-
-    default:
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-
-    if (method_ == HttpMethod_Post ||
-        method_ == HttpMethod_Put)
-    {
-      if (body_.size() > 0)
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str()));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size()));
-      }
-      else
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0));
-      }
-    }
-
-
-    // Do the actual request
-    CURLcode code;
-    long status = 0;
-
-    ChunkedBuffer buffer;
-    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer));
-
-    if (boost::starts_with(url_, "https://"))
-    {
-      code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status);
-    }
-    else
-    {
-      code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status);
-    }
-
-    CheckCode(code);
-
-    if (status == 0)
-    {
-      // This corresponds to a call to an inexistent host
-      lastStatus_ = HttpStatus_500_InternalServerError;
-    }
-    else
-    {
-      lastStatus_ = static_cast<HttpStatus>(status);
-    }
-
-    bool success = (status >= 200 && status < 300);
-
-    if (success)
-    {
-      buffer.Flatten(answerBody);
-    }
-    else
-    {
-      answerBody.clear();
-      LOG(INFO) << "Error in HTTP request, received HTTP status " << status 
-                << " (" << EnumerationToString(lastStatus_) << ")";
-    }
-
-    return success;
-  }
-
-
-  bool HttpClient::ApplyInternal(Json::Value& answerBody,
-                                 HttpClient::HttpHeaders* answerHeaders)
-  {
-    std::string s;
-    if (ApplyInternal(s, answerHeaders))
-    {
-      Json::Reader reader;
-      return reader.parse(s, answerBody);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  void HttpClient::SetCredentials(const char* username,
-                                  const char* password)
-  {
-    credentials_ = std::string(username) + ":" + std::string(password);
-  }
-
-
-  void HttpClient::ConfigureSsl(bool httpsVerifyPeers,
-                                const std::string& httpsVerifyCertificates)
-  {
-#if ORTHANC_ENABLE_SSL == 1
-    if (httpsVerifyPeers)
-    {
-      if (httpsVerifyCertificates.empty())
-      {
-        LOG(WARNING) << "No certificates are provided to validate peers, "
-                     << "set \"HttpsCACertificates\" if you need to do HTTPS requests";
-      }
-      else
-      {
-        LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates;
-      }
-    }
-    else
-    {
-      LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled";
-    }
-#endif
-
-    GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates);
-  }
-
-  
-  void HttpClient::GlobalInitialize()
-  {
-#if ORTHANC_ENABLE_SSL == 1
-    CheckCode(curl_global_init(CURL_GLOBAL_ALL));
-#else
-    CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL));
-#endif
-  }
-
-
-  void HttpClient::GlobalFinalize()
-  {
-    curl_global_cleanup();
-
-#if ORTHANC_ENABLE_PKCS11 == 1
-    Pkcs11::Finalize();
-#endif
-  }
-  
-
-  void HttpClient::SetDefaultProxy(const std::string& proxy)
-  {
-    GlobalParameters::GetInstance().SetDefaultProxy(proxy);
-  }
-
-
-  void HttpClient::SetDefaultTimeout(long timeout)
-  {
-    GlobalParameters::GetInstance().SetDefaultTimeout(timeout);
-  }
-
-
-  void HttpClient::ApplyAndThrowException(std::string& answerBody)
-  {
-    if (!Apply(answerBody))
-    {
-      ThrowException(GetLastStatus());
-    }
-  }
-
-  
-  void HttpClient::ApplyAndThrowException(Json::Value& answerBody)
-  {
-    if (!Apply(answerBody))
-    {
-      ThrowException(GetLastStatus());
-    }
-  }
-
-
-  void HttpClient::ApplyAndThrowException(std::string& answerBody,
-                                          HttpHeaders& answerHeaders)
-  {
-    if (!Apply(answerBody, answerHeaders))
-    {
-      ThrowException(GetLastStatus());
-    }
-  }
-  
-
-  void HttpClient::ApplyAndThrowException(Json::Value& answerBody,
-                                          HttpHeaders& answerHeaders)
-  {
-    if (!Apply(answerBody, answerHeaders))
-    {
-      ThrowException(GetLastStatus());
-    }
-  }
-
-
-  void HttpClient::SetClientCertificate(const std::string& certificateFile,
-                                        const std::string& certificateKeyFile,
-                                        const std::string& certificateKeyPassword)
-  {
-    if (certificateFile.empty())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (!SystemToolbox::IsRegularFile(certificateFile))
-    {
-      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    if (!certificateKeyFile.empty() && 
-        !SystemToolbox::IsRegularFile(certificateKeyFile))
-    {
-      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    clientCertificateFile_ = certificateFile;
-    clientCertificateKeyFile_ = certificateKeyFile;
-    clientCertificateKeyPassword_ = certificateKeyPassword;
-  }
-
-
-  void HttpClient::InitializePkcs11(const std::string& module,
-                                    const std::string& pin,
-                                    bool verbose)
-  {
-#if ORTHANC_ENABLE_PKCS11 == 1
-    LOG(INFO) << "Initializing PKCS#11 using " << module 
-              << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)");
-    GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose);    
-#else
-    LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11";
-    throw OrthancException(ErrorCode_InternalError);
-#endif
-  }
-
-
-  void HttpClient::InitializeOpenSsl()
-  {
-#if ORTHANC_ENABLE_SSL == 1
-    // https://wiki.openssl.org/index.php/Library_Initialization
-    SSL_library_init();
-    SSL_load_error_strings();
-    OpenSSL_add_all_algorithms();
-    ERR_load_crypto_strings();
-#endif
-  }
-
-
-  void HttpClient::FinalizeOpenSsl()
-  {
-#if ORTHANC_ENABLE_SSL == 1
-    // Finalize OpenSSL
-    // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup
-#ifdef FIPS_mode_set
-    FIPS_mode_set(0);
-#endif
-    ENGINE_cleanup();
-    CONF_modules_unload(1);
-    EVP_cleanup();
-    CRYPTO_cleanup_all_ex_data();
-    ERR_remove_state(0);
-    ERR_free_strings();
-#endif
-  }
-}
--- a/Resources/Orthanc/Core/HttpClient.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,296 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Enumerations.h"
-#include "WebServiceParameters.h"
-
-#include <string>
-#include <boost/shared_ptr.hpp>
-#include <json/json.h>
-
-#if !defined(ORTHANC_ENABLE_SSL)
-#  error The macro ORTHANC_ENABLE_SSL must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_PKCS11)
-#  error The macro ORTHANC_ENABLE_PKCS11 must be defined
-#endif
-
-
-namespace Orthanc
-{
-  class HttpClient
-  {
-  public:
-    typedef std::map<std::string, std::string>  HttpHeaders;
-
-  private:
-    class GlobalParameters;
-
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    std::string url_;
-    std::string credentials_;
-    HttpMethod method_;
-    HttpStatus lastStatus_;
-    std::string body_;  // This only makes sense for POST and PUT requests
-    bool isVerbose_;
-    long timeout_;
-    std::string proxy_;
-    bool verifyPeers_;
-    std::string caCertificates_;
-    std::string clientCertificateFile_;
-    std::string clientCertificateKeyFile_;
-    std::string clientCertificateKeyPassword_;
-    bool pkcs11Enabled_;
-    bool headersToLowerCase_;
-    bool redirectionFollowed_;
-
-    void Setup();
-
-    void operator= (const HttpClient&);  // Assignment forbidden
-    HttpClient(const HttpClient& base);  // Copy forbidden
-
-    bool ApplyInternal(std::string& answerBody,
-                       HttpHeaders* answerHeaders);
-
-    bool ApplyInternal(Json::Value& answerBody,
-                       HttpHeaders* answerHeaders);
-
-  public:
-    HttpClient();
-
-    HttpClient(const WebServiceParameters& service,
-               const std::string& uri);
-
-    ~HttpClient();
-
-    void SetUrl(const char* url)
-    {
-      url_ = std::string(url);
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    const std::string& GetUrl() const
-    {
-      return url_;
-    }
-
-    void SetMethod(HttpMethod method)
-    {
-      method_ = method;
-    }
-
-    HttpMethod GetMethod() const
-    {
-      return method_;
-    }
-
-    void SetTimeout(long seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    long GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    void SetBody(const std::string& data)
-    {
-      body_ = data;
-    }
-
-    std::string& GetBody()
-    {
-      return body_;
-    }
-
-    const std::string& GetBody() const
-    {
-      return body_;
-    }
-
-    void SetVerbose(bool isVerbose);
-
-    bool IsVerbose() const
-    {
-      return isVerbose_;
-    }
-
-    void AddHeader(const std::string& key,
-                   const std::string& value);
-
-    void ClearHeaders();
-
-    bool Apply(std::string& answerBody)
-    {
-      return ApplyInternal(answerBody, NULL);
-    }
-
-    bool Apply(Json::Value& answerBody)
-    {
-      return ApplyInternal(answerBody, NULL);
-    }
-
-    bool Apply(std::string& answerBody,
-               HttpHeaders& answerHeaders)
-    {
-      return ApplyInternal(answerBody, &answerHeaders);
-    }
-
-    bool Apply(Json::Value& answerBody,
-               HttpHeaders& answerHeaders)
-    {
-      return ApplyInternal(answerBody, &answerHeaders);
-    }
-
-    HttpStatus GetLastStatus() const
-    {
-      return lastStatus_;
-    }
-
-    void SetCredentials(const char* username,
-                        const char* password);
-
-    void SetProxy(const std::string& proxy)
-    {
-      proxy_ = proxy;
-    }
-
-    void SetHttpsVerifyPeers(bool verify)
-    {
-      verifyPeers_ = verify;
-    }
-
-    bool IsHttpsVerifyPeers() const
-    {
-      return verifyPeers_;
-    }
-
-    void SetHttpsCACertificates(const std::string& certificates)
-    {
-      caCertificates_ = certificates;
-    }
-
-    const std::string& GetHttpsCACertificates() const
-    {
-      return caCertificates_;
-    }
-
-    void SetClientCertificate(const std::string& certificateFile,
-                              const std::string& certificateKeyFile,
-                              const std::string& certificateKeyPassword);
-
-    void SetPkcs11Enabled(bool enabled)
-    {
-      pkcs11Enabled_ = enabled;
-    }
-
-    bool IsPkcs11Enabled() const
-    {
-      return pkcs11Enabled_;
-    }
-
-    const std::string& GetClientCertificateFile() const
-    {
-      return clientCertificateFile_;
-    }
-
-    const std::string& GetClientCertificateKeyFile() const
-    {
-      return clientCertificateKeyFile_;
-    }
-
-    const std::string& GetClientCertificateKeyPassword() const
-    {
-      return clientCertificateKeyPassword_;
-    }
-
-    void SetConvertHeadersToLowerCase(bool lowerCase)
-    {
-      headersToLowerCase_ = lowerCase;
-    }
-
-    bool IsConvertHeadersToLowerCase() const
-    {
-      return headersToLowerCase_;
-    }
-
-    void SetRedirectionFollowed(bool follow)
-    {
-      redirectionFollowed_ = follow;
-    }
-
-    bool IsRedirectionFollowed() const
-    {
-      return redirectionFollowed_;
-    }
-
-    static void GlobalInitialize();
-  
-    static void GlobalFinalize();
-
-    static void InitializeOpenSsl();
-
-    static void FinalizeOpenSsl();
-
-    static void InitializePkcs11(const std::string& module,
-                                 const std::string& pin,
-                                 bool verbose);
-
-    static void ConfigureSsl(bool httpsVerifyPeers,
-                             const std::string& httpsCACertificates);
-
-    static void SetDefaultProxy(const std::string& proxy);
-
-    static void SetDefaultTimeout(long timeout);
-
-    void ApplyAndThrowException(std::string& answerBody);
-
-    void ApplyAndThrowException(Json::Value& answerBody);
-
-    void ApplyAndThrowException(std::string& answerBody,
-                                HttpHeaders& answerHeaders);
-
-    void ApplyAndThrowException(Json::Value& answerBody,
-                                HttpHeaders& answerHeaders);
-  };
-}
--- a/Resources/Orthanc/Core/Images/Image.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Image.h"
-
-#include "ImageProcessing.h"
-
-#include <memory>
-
-namespace Orthanc
-{
-  Image::Image(PixelFormat format,
-               unsigned int width,
-               unsigned int height,
-               bool forceMinimalPitch) :
-    image_(format, width, height, forceMinimalPitch)
-  {
-    ImageAccessor accessor = image_.GetAccessor();
-    AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer());
-  }
-
-
-  Image* Image::Clone(const ImageAccessor& source)
-  {
-    std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false));
-    ImageProcessing::Copy(*target, source);
-    return target.release();
-  }
-}
--- a/Resources/Orthanc/Core/Images/Image.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ImageAccessor.h"
-#include "ImageBuffer.h"
-
-namespace Orthanc
-{
-  class Image : public ImageAccessor
-  {
-  private:
-    ImageBuffer  image_;
-
-  public:
-    Image(PixelFormat format,
-          unsigned int width,
-          unsigned int height,
-          bool forceMinimalPitch);
-
-    static Image* Clone(const ImageAccessor& source);
-  };
-}
--- a/Resources/Orthanc/Core/Images/ImageAccessor.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,297 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "ImageAccessor.h"
-
-#include "../Logging.h"
-#include "../OrthancException.h"
-#include "../ChunkedBuffer.h"
-
-#include <stdint.h>
-#include <cassert>
-#include <boost/lexical_cast.hpp>
-
-
-
-namespace Orthanc
-{
-  template <typename PixelType>
-  static void ToMatlabStringInternal(ChunkedBuffer& target,
-                                     const ImageAccessor& source)
-  {
-    target.AddChunk("double([ ");
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
-
-      std::string s;
-      if (y > 0)
-      {
-        s = "; ";
-      }
-
-      s.reserve(source.GetWidth() * 8);
-
-      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
-      {
-        s += boost::lexical_cast<std::string>(static_cast<double>(*p)) + " ";
-      }
-
-      target.AddChunk(s);
-    }
-
-    target.AddChunk("])");
-  }
-
-
-  static void RGB24ToMatlabString(ChunkedBuffer& target,
-                                  const ImageAccessor& source)
-  {
-    assert(source.GetFormat() == PixelFormat_RGB24);
-
-    target.AddChunk("double(permute(reshape([ ");
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-      
-      std::string s;
-      s.reserve(source.GetWidth() * 3 * 8);
-      
-      for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++)
-      {
-        s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " ";
-      }
-      
-      target.AddChunk(s);
-    }
-
-    target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) +
-                    " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))");
-  }
-
-
-  void* ImageAccessor::GetBuffer() const
-  {
-    if (readOnly_)
-    {
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "Trying to write on a read-only image";
-#endif
-
-      throw OrthancException(ErrorCode_ReadOnly);
-    }
-
-    return buffer_;
-  }
-
-
-  const void* ImageAccessor::GetConstRow(unsigned int y) const
-  {
-    if (buffer_ != NULL)
-    {
-      return buffer_ + y * pitch_;
-    }
-    else
-    {
-      return NULL;
-    }
-  }
-
-
-  void* ImageAccessor::GetRow(unsigned int y) const
-  {
-    if (readOnly_)
-    {
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "Trying to write on a read-only image";
-#endif
-
-      throw OrthancException(ErrorCode_ReadOnly);
-    }
-
-    if (buffer_ != NULL)
-    {
-      return buffer_ + y * pitch_;
-    }
-    else
-    {
-      return NULL;
-    }
-  }
-
-
-  void ImageAccessor::AssignEmpty(PixelFormat format)
-  {
-    readOnly_ = false;
-    format_ = format;
-    width_ = 0;
-    height_ = 0;
-    pitch_ = 0;
-    buffer_ = NULL;
-  }
-
-
-  void ImageAccessor::AssignReadOnly(PixelFormat format,
-                                     unsigned int width,
-                                     unsigned int height,
-                                     unsigned int pitch,
-                                     const void *buffer)
-  {
-    readOnly_ = true;
-    format_ = format;
-    width_ = width;
-    height_ = height;
-    pitch_ = pitch;
-    buffer_ = reinterpret_cast<uint8_t*>(const_cast<void*>(buffer));
-
-    if (GetBytesPerPixel() * width_ > pitch_)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  void ImageAccessor::AssignWritable(PixelFormat format,
-                                     unsigned int width,
-                                     unsigned int height,
-                                     unsigned int pitch,
-                                     void *buffer)
-  {
-    readOnly_ = false;
-    format_ = format;
-    width_ = width;
-    height_ = height;
-    pitch_ = pitch;
-    buffer_ = reinterpret_cast<uint8_t*>(buffer);
-
-    if (GetBytesPerPixel() * width_ > pitch_)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  void ImageAccessor::ToMatlabString(std::string& target) const
-  {
-    ChunkedBuffer buffer;
-
-    switch (GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-        ToMatlabStringInternal<uint8_t>(buffer, *this);
-        break;
-
-      case PixelFormat_Grayscale16:
-        ToMatlabStringInternal<uint16_t>(buffer, *this);
-        break;
-
-      case PixelFormat_SignedGrayscale16:
-        ToMatlabStringInternal<int16_t>(buffer, *this);
-        break;
-
-      case PixelFormat_Float32:
-        ToMatlabStringInternal<float>(buffer, *this);
-        break;
-
-      case PixelFormat_RGB24:
-        RGB24ToMatlabString(buffer, *this);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }   
-
-    buffer.Flatten(target);
-  }
-
-
-
-  ImageAccessor ImageAccessor::GetRegion(unsigned int x,
-                                         unsigned int y,
-                                         unsigned int width,
-                                         unsigned int height) const
-  {
-    if (x + width > width_ ||
-        y + height > height_)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    
-    ImageAccessor result;
-
-    if (width == 0 ||
-        height == 0)
-    {
-      result.AssignWritable(format_, 0, 0, 0, NULL);
-    }
-    else
-    {
-      uint8_t* p = (buffer_ + 
-                    y * pitch_ + 
-                    x * GetBytesPerPixel());
-
-      if (readOnly_)
-      {
-        result.AssignReadOnly(format_, width, height, pitch_, p);
-      }
-      else
-      {
-        result.AssignWritable(format_, width, height, pitch_, p);
-      }
-    }
-
-    return result;
-  }
-
-
-  void ImageAccessor::SetFormat(PixelFormat format)
-  {
-    if (readOnly_)
-    {
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "Trying to modify the format of a read-only image";
-#endif
-      throw OrthancException(ErrorCode_ReadOnly);
-    }
-
-    if (::Orthanc::GetBytesPerPixel(format) != ::Orthanc::GetBytesPerPixel(format_))
-    {
-      throw OrthancException(ErrorCode_IncompatibleImageFormat);
-    }
-
-    format_ = format;
-  }
-}
--- a/Resources/Orthanc/Core/Images/ImageAccessor.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Enumerations.h"
-
-#include <string>
-#include <stdint.h>
-
-namespace Orthanc
-{
-  class ImageAccessor
-  {
-  private:
-    bool readOnly_;
-    PixelFormat format_;
-    unsigned int width_;
-    unsigned int height_;
-    unsigned int pitch_;
-    uint8_t *buffer_;
-
-  public:
-    ImageAccessor()
-    {
-      AssignEmpty(PixelFormat_Grayscale8);
-    }
-
-    virtual ~ImageAccessor()
-    {
-    }
-
-    bool IsReadOnly() const
-    {
-      return readOnly_;
-    }
-
-    PixelFormat GetFormat() const
-    {
-      return format_;
-    }
-
-    unsigned int GetBytesPerPixel() const
-    {
-      return ::Orthanc::GetBytesPerPixel(format_);
-    }
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    unsigned int GetPitch() const
-    {
-      return pitch_;
-    }
-
-    unsigned int GetSize() const
-    {
-      return GetHeight() * GetPitch();
-    }
-
-    const void* GetConstBuffer() const
-    {
-      return buffer_;
-    }
-
-    void* GetBuffer() const;
-
-    const void* GetConstRow(unsigned int y) const;
-
-    void* GetRow(unsigned int y) const;
-
-    void AssignEmpty(PixelFormat format);
-
-    void AssignReadOnly(PixelFormat format,
-                        unsigned int width,
-                        unsigned int height,
-                        unsigned int pitch,
-                        const void *buffer);
-
-    void AssignWritable(PixelFormat format,
-                        unsigned int width,
-                        unsigned int height,
-                        unsigned int pitch,
-                        void *buffer);
-
-    void ToMatlabString(std::string& target) const; 
-
-    ImageAccessor GetRegion(unsigned int x,
-                            unsigned int y,
-                            unsigned int width,
-                            unsigned int height) const;
-
-    void SetFormat(PixelFormat format);
-  };
-}
--- a/Resources/Orthanc/Core/Images/ImageBuffer.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "ImageBuffer.h"
-
-#include "../OrthancException.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-
-namespace Orthanc
-{
-  void ImageBuffer::Allocate()
-  {
-    if (changed_)
-    {
-      Deallocate();
-
-      /*
-        if (forceMinimalPitch_)
-        {
-        TODO: Align pitch and memory buffer to optimal size for SIMD.
-        }
-      */
-
-      pitch_ = GetBytesPerPixel() * width_;
-      size_t size = pitch_ * height_;
-
-      if (size == 0)
-      {
-        buffer_ = NULL;
-      }
-      else
-      {
-        buffer_ = malloc(size);
-        if (buffer_ == NULL)
-        {
-          throw OrthancException(ErrorCode_NotEnoughMemory);
-        }
-      }
-
-      changed_ = false;
-    }
-  }
-
-
-  void ImageBuffer::Deallocate()
-  {
-    if (buffer_ != NULL)
-    {
-      free(buffer_);
-      buffer_ = NULL;
-      changed_ = true;
-    }
-  }
-
-
-  ImageBuffer::ImageBuffer(PixelFormat format,
-                           unsigned int width,
-                           unsigned int height,
-                           bool forceMinimalPitch) :
-    forceMinimalPitch_(forceMinimalPitch)
-  {
-    Initialize();
-    SetWidth(width);
-    SetHeight(height);
-    SetFormat(format);
-  }
-
-
-  void ImageBuffer::Initialize()
-  {
-    changed_ = false;
-    forceMinimalPitch_ = true;
-    format_ = PixelFormat_Grayscale8;
-    width_ = 0;
-    height_ = 0;
-    pitch_ = 0;
-    buffer_ = NULL;
-  }
-
-
-  void ImageBuffer::SetFormat(PixelFormat format)
-  {
-    if (format != format_)
-    {
-      changed_ = true;
-      format_ = format;
-    }
-  }
-
-
-  void ImageBuffer::SetWidth(unsigned int width)
-  {
-    if (width != width_)
-    {
-      changed_ = true;
-      width_ = width;     
-    }
-  }
-
-
-  void ImageBuffer::SetHeight(unsigned int height)
-  {
-    if (height != height_)
-    {
-      changed_ = true;
-      height_ = height;     
-    }
-  }
-
-
-  ImageAccessor ImageBuffer::GetAccessor()
-  {
-    Allocate();
-
-    ImageAccessor accessor;
-    accessor.AssignWritable(format_, width_, height_, pitch_, buffer_);
-    return accessor;
-  }
-
-
-  ImageAccessor ImageBuffer::GetConstAccessor()
-  {
-    Allocate();
-
-    ImageAccessor accessor;
-    accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_);
-    return accessor;
-  }
-
-
-  void ImageBuffer::AcquireOwnership(ImageBuffer& other)
-  {
-    // Remove the content of the current image
-    Deallocate();
-
-    // Force the allocation of the other image (if not already
-    // allocated)
-    other.Allocate();
-
-    // Transfer the content of the other image
-    changed_ = false;
-    forceMinimalPitch_ = other.forceMinimalPitch_;
-    format_ = other.format_;
-    width_ = other.width_;
-    height_ = other.height_;
-    pitch_ = other.pitch_;
-    buffer_ = other.buffer_;
-
-    // Force the reinitialization of the other image
-    other.Initialize();
-  }
-}
--- a/Resources/Orthanc/Core/Images/ImageBuffer.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ImageAccessor.h"
-
-#include <vector>
-#include <stdint.h>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class ImageBuffer : public boost::noncopyable
-  {
-  private:
-    bool changed_;
-
-    bool forceMinimalPitch_;  // Currently unused
-    PixelFormat format_;
-    unsigned int width_;
-    unsigned int height_;
-    unsigned int pitch_;
-    void *buffer_;
-
-    void Initialize();
-    
-    void Allocate();
-
-    void Deallocate();
-
-  public:
-    ImageBuffer(PixelFormat format,
-                unsigned int width,
-                unsigned int height,
-                bool forceMinimalPitch);
-
-    ImageBuffer()
-    {
-      Initialize();
-    }
-
-    ~ImageBuffer()
-    {
-      Deallocate();
-    }
-
-    PixelFormat GetFormat() const
-    {
-      return format_;
-    }
-
-    void SetFormat(PixelFormat format);
-
-    unsigned int GetWidth() const
-    {
-      return width_;
-    }
-
-    void SetWidth(unsigned int width);
-
-    unsigned int GetHeight() const
-    {
-      return height_;
-    }
-
-    void SetHeight(unsigned int height);
-
-    unsigned int GetBytesPerPixel() const
-    {
-      return ::Orthanc::GetBytesPerPixel(format_);
-    }
-
-    ImageAccessor GetAccessor();
-
-    ImageAccessor GetConstAccessor();
-
-    bool IsMinimalPitchForced() const
-    {
-      return forceMinimalPitch_;
-    }
-
-    void AcquireOwnership(ImageBuffer& other);
-  };
-}
--- a/Resources/Orthanc/Core/Images/ImageProcessing.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,775 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "ImageProcessing.h"
-
-#include "../OrthancException.h"
-
-#include <boost/math/special_functions/round.hpp>
-
-#include <cassert>
-#include <string.h>
-#include <limits>
-#include <stdint.h>
-
-namespace Orthanc
-{
-  template <typename TargetType, typename SourceType>
-  static void ConvertInternal(ImageAccessor& target,
-                              const ImageAccessor& source)
-  {
-    const TargetType minValue = std::numeric_limits<TargetType>::min();
-    const TargetType maxValue = std::numeric_limits<TargetType>::max();
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
-      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
-
-      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
-      {
-        if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue))
-        {
-          *t = minValue;
-        }
-        else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue))
-        {
-          *t = maxValue;
-        }
-        else
-        {
-          *t = static_cast<TargetType>(*s);
-        }
-      }
-    }
-  }
-
-
-  template <typename SourceType>
-  static void ConvertGrayscaleToFloat(ImageAccessor& target,
-                                      const ImageAccessor& source)
-  {
-    assert(sizeof(float) == 4);
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      float* t = reinterpret_cast<float*>(target.GetRow(y));
-      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
-
-      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
-      {
-        *t = static_cast<float>(*s);
-      }
-    }
-  }
-
-
-  template <typename TargetType>
-  static void ConvertColorToGrayscale(ImageAccessor& target,
-                                      const ImageAccessor& source)
-  {
-    assert(source.GetFormat() == PixelFormat_RGB24);
-
-    const TargetType minValue = std::numeric_limits<TargetType>::min();
-    const TargetType maxValue = std::numeric_limits<TargetType>::max();
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
-      const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-
-      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3)
-      {
-        // Y = 0.2126 R + 0.7152 G + 0.0722 B
-        int32_t v = (2126 * static_cast<int32_t>(s[0]) +
-                     7152 * static_cast<int32_t>(s[1]) +
-                     0722 * static_cast<int32_t>(s[2])) / 1000;
-        
-        if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
-        {
-          *t = minValue;
-        }
-        else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
-        {
-          *t = maxValue;
-        }
-        else
-        {
-          *t = static_cast<TargetType>(v);
-        }
-      }
-    }
-  }
-
-
-  template <typename PixelType>
-  static void SetInternal(ImageAccessor& image,
-                          int64_t constant)
-  {
-    for (unsigned int y = 0; y < image.GetHeight(); y++)
-    {
-      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
-
-      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
-      {
-        *p = static_cast<PixelType>(constant);
-      }
-    }
-  }
-
-
-  template <typename PixelType>
-  static void GetMinMaxValueInternal(PixelType& minValue,
-                                     PixelType& maxValue,
-                                     const ImageAccessor& source)
-  {
-    // Deal with the special case of empty image
-    if (source.GetWidth() == 0 ||
-        source.GetHeight() == 0)
-    {
-      minValue = 0;
-      maxValue = 0;
-      return;
-    }
-
-    minValue = std::numeric_limits<PixelType>::max();
-    maxValue = std::numeric_limits<PixelType>::min();
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
-
-      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
-      {
-        if (*p < minValue)
-        {
-          minValue = *p;
-        }
-
-        if (*p > maxValue)
-        {
-          maxValue = *p;
-        }
-      }
-    }
-  }
-
-
-
-  template <typename PixelType>
-  static void AddConstantInternal(ImageAccessor& image,
-                                  int64_t constant)
-  {
-    if (constant == 0)
-    {
-      return;
-    }
-
-    const int64_t minValue = std::numeric_limits<PixelType>::min();
-    const int64_t maxValue = std::numeric_limits<PixelType>::max();
-
-    for (unsigned int y = 0; y < image.GetHeight(); y++)
-    {
-      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
-
-      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
-      {
-        int64_t v = static_cast<int64_t>(*p) + constant;
-
-        if (v > maxValue)
-        {
-          *p = std::numeric_limits<PixelType>::max();
-        }
-        else if (v < minValue)
-        {
-          *p = std::numeric_limits<PixelType>::min();
-        }
-        else
-        {
-          *p = static_cast<PixelType>(v);
-        }
-      }
-    }
-  }
-
-
-
-  template <typename PixelType>
-  void MultiplyConstantInternal(ImageAccessor& image,
-                                float factor)
-  {
-    if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
-    {
-      return;
-    }
-
-    const int64_t minValue = std::numeric_limits<PixelType>::min();
-    const int64_t maxValue = std::numeric_limits<PixelType>::max();
-
-    for (unsigned int y = 0; y < image.GetHeight(); y++)
-    {
-      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
-
-      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
-      {
-        int64_t v = boost::math::llround(static_cast<float>(*p) * factor);
-
-        if (v > maxValue)
-        {
-          *p = std::numeric_limits<PixelType>::max();
-        }
-        else if (v < minValue)
-        {
-          *p = std::numeric_limits<PixelType>::min();
-        }
-        else
-        {
-          *p = static_cast<PixelType>(v);
-        }
-      }
-    }
-  }
-
-
-  template <typename PixelType>
-  void ShiftScaleInternal(ImageAccessor& image,
-                          float offset,
-                          float scaling)
-  {
-    const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min());
-    const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max());
-
-    for (unsigned int y = 0; y < image.GetHeight(); y++)
-    {
-      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
-
-      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
-      {
-        float v = (static_cast<float>(*p) + offset) * scaling;
-
-        if (v > maxValue)
-        {
-          *p = std::numeric_limits<PixelType>::max();
-        }
-        else if (v < minValue)
-        {
-          *p = std::numeric_limits<PixelType>::min();
-        }
-        else
-        {
-          *p = static_cast<PixelType>(boost::math::iround(v));
-        }
-      }
-    }
-  }
-
-
-  void ImageProcessing::Copy(ImageAccessor& target,
-                             const ImageAccessor& source)
-  {
-    if (target.GetWidth() != source.GetWidth() ||
-        target.GetHeight() != source.GetHeight())
-    {
-      throw OrthancException(ErrorCode_IncompatibleImageSize);
-    }
-
-    if (target.GetFormat() != source.GetFormat())
-    {
-      throw OrthancException(ErrorCode_IncompatibleImageFormat);
-    }
-
-    unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
-
-    assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize);
-
-    for (unsigned int y = 0; y < source.GetHeight(); y++)
-    {
-      memcpy(target.GetRow(y), source.GetConstRow(y), lineSize);
-    }
-  }
-
-
-  void ImageProcessing::Convert(ImageAccessor& target,
-                                const ImageAccessor& source)
-  {
-    if (target.GetWidth() != source.GetWidth() ||
-        target.GetHeight() != source.GetHeight())
-    {
-      throw OrthancException(ErrorCode_IncompatibleImageSize);
-    }
-
-    if (source.GetFormat() == target.GetFormat())
-    {
-      Copy(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale16 &&
-        source.GetFormat() == PixelFormat_Grayscale8)
-    {
-      ConvertInternal<uint16_t, uint8_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
-        source.GetFormat() == PixelFormat_Grayscale8)
-    {
-      ConvertInternal<int16_t, uint8_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale8 &&
-        source.GetFormat() == PixelFormat_Grayscale16)
-    {
-      ConvertInternal<uint8_t, uint16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
-        source.GetFormat() == PixelFormat_Grayscale16)
-    {
-      ConvertInternal<int16_t, uint16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale8 &&
-        source.GetFormat() == PixelFormat_SignedGrayscale16)
-    {
-      ConvertInternal<uint8_t, int16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale16 &&
-        source.GetFormat() == PixelFormat_SignedGrayscale16)
-    {
-      ConvertInternal<uint16_t, int16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale8 &&
-        source.GetFormat() == PixelFormat_RGB24)
-    {
-      ConvertColorToGrayscale<uint8_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale16 &&
-        source.GetFormat() == PixelFormat_RGB24)
-    {
-      ConvertColorToGrayscale<uint16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
-        source.GetFormat() == PixelFormat_RGB24)
-    {
-      ConvertColorToGrayscale<int16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Float32 &&
-        source.GetFormat() == PixelFormat_Grayscale8)
-    {
-      ConvertGrayscaleToFloat<uint8_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Float32 &&
-        source.GetFormat() == PixelFormat_Grayscale16)
-    {
-      ConvertGrayscaleToFloat<uint16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Float32 &&
-        source.GetFormat() == PixelFormat_SignedGrayscale16)
-    {
-      ConvertGrayscaleToFloat<int16_t>(target, source);
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_Grayscale8 &&
-        source.GetFormat() == PixelFormat_RGBA32)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++, q++)
-        {
-          *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) +
-                                     7152 * static_cast<uint32_t>(p[1]) +
-                                     0722 * static_cast<uint32_t>(p[2])) / 10000);
-          p += 4;
-        }
-      }
-
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_RGB24 &&
-        source.GetFormat() == PixelFormat_RGBA32)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++)
-        {
-          q[0] = p[0];
-          q[1] = p[1];
-          q[2] = p[2];
-          p += 4;
-          q += 3;
-        }
-      }
-
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_RGB24 &&
-        source.GetFormat() == PixelFormat_BGRA32)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++)
-        {
-          q[0] = p[2];
-          q[1] = p[1];
-          q[2] = p[0];
-          p += 4;
-          q += 3;
-        }
-      }
-
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_RGBA32 &&
-        source.GetFormat() == PixelFormat_RGB24)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++)
-        {
-          q[0] = p[0];
-          q[1] = p[1];
-          q[2] = p[2];
-          q[3] = 255;   // Set the alpha channel to full opacity
-          p += 3;
-          q += 4;
-        }
-      }
-
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_RGB24 &&
-        source.GetFormat() == PixelFormat_Grayscale8)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++)
-        {
-          q[0] = *p;
-          q[1] = *p;
-          q[2] = *p;
-          p += 1;
-          q += 3;
-        }
-      }
-
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_RGBA32 &&
-        source.GetFormat() == PixelFormat_Grayscale8)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++)
-        {
-          q[0] = *p;
-          q[1] = *p;
-          q[2] = *p;
-          q[3] = 255;
-          p += 1;
-          q += 4;
-        }
-      }
-
-      return;
-    }
-
-    if (target.GetFormat() == PixelFormat_BGRA32 &&
-        source.GetFormat() == PixelFormat_RGB24)
-    {
-      for (unsigned int y = 0; y < source.GetHeight(); y++)
-      {
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
-        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
-        for (unsigned int x = 0; x < source.GetWidth(); x++)
-        {
-          q[0] = p[2];
-          q[1] = p[1];
-          q[2] = p[0];
-          q[3] = 255;
-          p += 3;
-          q += 4;
-        }
-      }
-
-      return;
-    }
-
-    throw OrthancException(ErrorCode_NotImplemented);
-  }
-
-
-
-  void ImageProcessing::Set(ImageAccessor& image,
-                            int64_t value)
-  {
-    switch (image.GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-        SetInternal<uint8_t>(image, value);
-        return;
-
-      case PixelFormat_Grayscale16:
-        SetInternal<uint16_t>(image, value);
-        return;
-
-      case PixelFormat_SignedGrayscale16:
-        SetInternal<int16_t>(image, value);
-        return;
-
-      case PixelFormat_Float32:
-        assert(sizeof(float) == 4);
-        SetInternal<float>(image, value);
-        return;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void ImageProcessing::Set(ImageAccessor& image,
-                            uint8_t red,
-                            uint8_t green,
-                            uint8_t blue,
-                            uint8_t alpha)
-  {
-    uint8_t p[4];
-    unsigned int size;
-
-    switch (image.GetFormat())
-    {
-      case PixelFormat_RGBA32:
-        p[0] = red;
-        p[1] = green;
-        p[2] = blue;
-        p[3] = alpha;
-        size = 4;
-        break;
-
-      case PixelFormat_BGRA32:
-        p[0] = blue;
-        p[1] = green;
-        p[2] = red;
-        p[3] = alpha;
-        size = 4;
-        break;
-
-      case PixelFormat_RGB24:
-        p[0] = red;
-        p[1] = green;
-        p[2] = blue;
-        size = 3;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }    
-
-    for (unsigned int y = 0; y < image.GetHeight(); y++)
-    {
-      uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
-
-      for (unsigned int x = 0; x < image.GetWidth(); x++)
-      {
-        for (unsigned int i = 0; i < size; i++)
-        {
-          q[i] = p[i];
-        }
-
-        q += size;
-      }
-    }
-  }
-
-
-  void ImageProcessing::ShiftRight(ImageAccessor& image,
-                                   unsigned int shift)
-  {
-    if (image.GetWidth() == 0 ||
-        image.GetHeight() == 0 ||
-        shift == 0)
-    {
-      // Nothing to do
-      return;
-    }
-
-    throw OrthancException(ErrorCode_NotImplemented);
-  }
-
-
-  void ImageProcessing::GetMinMaxValue(int64_t& minValue,
-                                       int64_t& maxValue,
-                                       const ImageAccessor& image)
-  {
-    switch (image.GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-      {
-        uint8_t a, b;
-        GetMinMaxValueInternal<uint8_t>(a, b, image);
-        minValue = a;
-        maxValue = b;
-        break;
-      }
-
-      case PixelFormat_Grayscale16:
-      {
-        uint16_t a, b;
-        GetMinMaxValueInternal<uint16_t>(a, b, image);
-        minValue = a;
-        maxValue = b;
-        break;
-      }
-
-      case PixelFormat_SignedGrayscale16:
-      {
-        int16_t a, b;
-        GetMinMaxValueInternal<int16_t>(a, b, image);
-        minValue = a;
-        maxValue = b;
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-
-  void ImageProcessing::AddConstant(ImageAccessor& image,
-                                    int64_t value)
-  {
-    switch (image.GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-        AddConstantInternal<uint8_t>(image, value);
-        return;
-
-      case PixelFormat_Grayscale16:
-        AddConstantInternal<uint16_t>(image, value);
-        return;
-
-      case PixelFormat_SignedGrayscale16:
-        AddConstantInternal<int16_t>(image, value);
-        return;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void ImageProcessing::MultiplyConstant(ImageAccessor& image,
-                                         float factor)
-  {
-    switch (image.GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-        MultiplyConstantInternal<uint8_t>(image, factor);
-        return;
-
-      case PixelFormat_Grayscale16:
-        MultiplyConstantInternal<uint16_t>(image, factor);
-        return;
-
-      case PixelFormat_SignedGrayscale16:
-        MultiplyConstantInternal<int16_t>(image, factor);
-        return;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void ImageProcessing::ShiftScale(ImageAccessor& image,
-                                   float offset,
-                                   float scaling)
-  {
-    switch (image.GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-        ShiftScaleInternal<uint8_t>(image, offset, scaling);
-        return;
-
-      case PixelFormat_Grayscale16:
-        ShiftScaleInternal<uint16_t>(image, offset, scaling);
-        return;
-
-      case PixelFormat_SignedGrayscale16:
-        ShiftScaleInternal<int16_t>(image, offset, scaling);
-        return;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-}
--- a/Resources/Orthanc/Core/Images/ImageProcessing.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ImageAccessor.h"
-
-#include <stdint.h>
-
-namespace Orthanc
-{
-  class ImageProcessing
-  {
-  public:
-    static void Copy(ImageAccessor& target,
-                     const ImageAccessor& source);
-
-    static void Convert(ImageAccessor& target,
-                        const ImageAccessor& source);
-
-    static void Set(ImageAccessor& image,
-                    int64_t value);
-
-    static void Set(ImageAccessor& image,
-                    uint8_t red,
-                    uint8_t green,
-                    uint8_t blue,
-                    uint8_t alpha);
-
-    static void ShiftRight(ImageAccessor& target,
-                           unsigned int shift);
-
-    static void GetMinMaxValue(int64_t& minValue,
-                               int64_t& maxValue,
-                               const ImageAccessor& image);
-
-    static void AddConstant(ImageAccessor& image,
-                            int64_t value);
-
-    static void MultiplyConstant(ImageAccessor& image,
-                                 float factor);
-
-    static void ShiftScale(ImageAccessor& image,
-                           float offset,
-                           float scaling);
-  };
-}
--- a/Resources/Orthanc/Core/Images/JpegErrorManager.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "JpegErrorManager.h"
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    void JpegErrorManager::OutputMessage(j_common_ptr cinfo)
-    {
-      char message[JMSG_LENGTH_MAX];
-      (*cinfo->err->format_message) (cinfo, message);
-
-      JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err);
-      that->message = std::string(message);
-    }
-
-
-    void JpegErrorManager::ErrorExit(j_common_ptr cinfo)
-    {
-      (*cinfo->err->output_message) (cinfo);
-
-      JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err);
-      longjmp(that->setjmp_buffer, 1);
-    }
-      
-
-    JpegErrorManager::JpegErrorManager()
-    {
-      memset(&pub, 0, sizeof(struct jpeg_error_mgr));
-      memset(&setjmp_buffer, 0, sizeof(jmp_buf));
-
-      jpeg_std_error(&pub);
-      pub.error_exit = ErrorExit;
-      pub.output_message = OutputMessage;
-    }
-  }
-}
--- a/Resources/Orthanc/Core/Images/JpegErrorManager.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include <string.h>
-#include <stdio.h>
-#include <jpeglib.h>
-#include <setjmp.h>
-#include <string>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    class JpegErrorManager 
-    {
-    private:
-      struct jpeg_error_mgr pub;  /* "public" fields */
-      jmp_buf setjmp_buffer;      /* for return to caller */
-      std::string message;
-
-      static void OutputMessage(j_common_ptr cinfo);
-
-      static void ErrorExit(j_common_ptr cinfo);
-
-    public:
-      JpegErrorManager();
-
-      struct jpeg_error_mgr* GetPublic()
-      {
-        return &pub;
-      }
-
-      jmp_buf& GetJumpBuffer()
-      {
-        return setjmp_buffer;
-      }
-
-      const std::string& GetMessage() const
-      {
-        return message;
-      }
-    };
-  }
-}
--- a/Resources/Orthanc/Core/Images/JpegReader.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "JpegReader.h"
-
-#include "JpegErrorManager.h"
-#include "../OrthancException.h"
-#include "../Logging.h"
-
-#if ORTHANC_SANDBOXED == 0
-#  include "../SystemToolbox.h"
-#endif
-
-
-namespace Orthanc
-{
-  static void Uncompress(struct jpeg_decompress_struct& cinfo,
-                         std::string& content,
-                         ImageAccessor& accessor)
-  {
-    jpeg_read_header(&cinfo, TRUE);
-    jpeg_start_decompress(&cinfo);
-
-    PixelFormat format;
-    if (cinfo.output_components == 1 &&
-        cinfo.out_color_space == JCS_GRAYSCALE)
-    {
-      format = PixelFormat_Grayscale8;
-    }
-    else if (cinfo.output_components == 3 &&
-             cinfo.out_color_space == JCS_RGB)
-    {
-      format = PixelFormat_RGB24;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    unsigned int pitch = cinfo.output_width * cinfo.output_components;
-
-    /* Make a one-row-high sample array that will go away when done with image */
-    JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, pitch, 1);
-
-    try
-    {
-      content.resize(pitch * cinfo.output_height);
-    }
-    catch (...)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    accessor.AssignWritable(format, cinfo.output_width, cinfo.output_height, pitch, 
-                            content.empty() ? NULL : &content[0]);
-
-    uint8_t* target = reinterpret_cast<uint8_t*>(&content[0]);
-    while (cinfo.output_scanline < cinfo.output_height) 
-    {
-      jpeg_read_scanlines(&cinfo, buffer, 1);
-      memcpy(target, buffer[0], pitch);
-      target += pitch;
-    }
-
-    // Everything went fine, "setjmp()" didn't get called
-
-    jpeg_finish_decompress(&cinfo);
-  }
-
-
-#if ORTHANC_SANDBOXED == 0
-  void JpegReader::ReadFromFile(const std::string& filename)
-  {
-    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
-    if (!fp)
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    struct jpeg_decompress_struct cinfo;
-    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
-
-    Internals::JpegErrorManager jerr;
-    cinfo.err = jerr.GetPublic();
-    
-    if (setjmp(jerr.GetJumpBuffer())) 
-    {
-      jpeg_destroy_decompress(&cinfo);
-      fclose(fp);
-      LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage();
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Below this line, we are under the scope of a "setjmp"
-
-    jpeg_create_decompress(&cinfo);
-    jpeg_stdio_src(&cinfo, fp);
-
-    try
-    {
-      Uncompress(cinfo, content_, *this);
-    }
-    catch (OrthancException&)
-    {
-      jpeg_destroy_decompress(&cinfo);
-      fclose(fp);
-      throw;
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    fclose(fp);
-  }
-#endif
-
-
-  void JpegReader::ReadFromMemory(const void* buffer,
-                                  size_t size)
-  {
-    struct jpeg_decompress_struct cinfo;
-    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
-
-    Internals::JpegErrorManager jerr;
-    cinfo.err = jerr.GetPublic();
-    
-    if (setjmp(jerr.GetJumpBuffer())) 
-    {
-      jpeg_destroy_decompress(&cinfo);
-      LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage();
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Below this line, we are under the scope of a "setjmp"
-    jpeg_create_decompress(&cinfo);
-    jpeg_mem_src(&cinfo, const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(buffer)), size);
-
-    try
-    {
-      Uncompress(cinfo, content_, *this);
-    }
-    catch (OrthancException&)
-    {
-      jpeg_destroy_decompress(&cinfo);
-      throw;
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-  }
-
-
-  void JpegReader::ReadFromMemory(const std::string& buffer)
-  {
-    if (buffer.empty())
-    {
-      ReadFromMemory(NULL, 0);
-    }
-    else
-    {
-      ReadFromMemory(buffer.c_str(), buffer.size());
-    }
-  }
-}
--- a/Resources/Orthanc/Core/Images/JpegReader.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ImageAccessor.h"
-
-#include <string>
-#include <boost/noncopyable.hpp>
-
-#if !defined(ORTHANC_SANDBOXED)
-#  error The macro ORTHANC_SANDBOXED must be defined
-#endif
-
-namespace Orthanc
-{
-  class JpegReader : 
-    public ImageAccessor,
-    public boost::noncopyable
-  {
-  private:
-    std::string  content_;
-
-  public:
-#if ORTHANC_SANDBOXED == 0
-    void ReadFromFile(const std::string& filename);
-#endif
-
-    void ReadFromMemory(const void* buffer,
-                        size_t size);
-
-    void ReadFromMemory(const std::string& buffer);
-  };
-}
--- a/Resources/Orthanc/Core/Images/PngReader.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "PngReader.h"
-
-#include "../OrthancException.h"
-#include "../Toolbox.h"
-
-#if ORTHANC_SANDBOXED == 0
-#  include "../SystemToolbox.h"
-#endif
-
-#include <png.h>
-#include <string.h>  // For memcpy()
-
-namespace Orthanc
-{
-#if ORTHANC_SANDBOXED == 0
-  namespace 
-  {
-    struct FileRabi
-    {
-      FILE* fp_;
-
-      FileRabi(const char* filename)
-      {
-        fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
-        if (!fp_)
-        {
-          throw OrthancException(ErrorCode_InexistentFile);
-        }
-      }
-
-      ~FileRabi()
-      {
-        if (fp_)
-        {
-          fclose(fp_);
-        }
-      }
-    };
-  }
-#endif
-
-
-  struct PngReader::PngRabi
-  {
-    png_structp png_;
-    png_infop info_;
-    png_infop endInfo_;
-
-    void Destruct()
-    {
-      if (png_)
-      {
-        png_destroy_read_struct(&png_, &info_, &endInfo_);
-
-        png_ = NULL;
-        info_ = NULL;
-        endInfo_ = NULL;
-      }
-    }
-
-    PngRabi()
-    {
-      png_ = NULL;
-      info_ = NULL;
-      endInfo_ = NULL;
-
-      png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-      if (!png_)
-      {
-        throw OrthancException(ErrorCode_NotEnoughMemory);
-      }
-
-      info_ = png_create_info_struct(png_);
-      if (!info_)
-      {
-        png_destroy_read_struct(&png_, NULL, NULL);
-        throw OrthancException(ErrorCode_NotEnoughMemory);
-      }
-
-      endInfo_ = png_create_info_struct(png_);
-      if (!info_)
-      {
-        png_destroy_read_struct(&png_, &info_, NULL);
-        throw OrthancException(ErrorCode_NotEnoughMemory);
-      }
-    }
-
-    ~PngRabi()
-    {
-      Destruct();
-    }
-
-    static void MemoryCallback(png_structp png_ptr, 
-                               png_bytep data, 
-                               png_size_t size);
-  };
-
-
-  void PngReader::CheckHeader(const void* header)
-  {
-    int is_png = !png_sig_cmp((png_bytep) header, 0, 8);
-    if (!is_png)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-  PngReader::PngReader()
-  {
-  }
-
-  void PngReader::Read(PngRabi& rabi)
-  {
-    png_set_sig_bytes(rabi.png_, 8);
-
-    png_read_info(rabi.png_, rabi.info_);
-
-    png_uint_32 width, height;
-    int bit_depth, color_type, interlace_type;
-    int compression_type, filter_method;
-    // get size and bit-depth of the PNG-image
-    png_get_IHDR(rabi.png_, rabi.info_,
-                 &width, &height,
-                 &bit_depth, &color_type, &interlace_type,
-                 &compression_type, &filter_method);
-
-    PixelFormat format;
-    unsigned int pitch;
-
-    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8)
-    {
-      format = PixelFormat_Grayscale8;
-      pitch = width;
-    }
-    else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16)
-    {
-      format = PixelFormat_Grayscale16;
-      pitch = 2 * width;
-
-      if (Toolbox::DetectEndianness() == Endianness_Little)
-      {
-        png_set_swap(rabi.png_);
-      }
-    }
-    else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8)
-    {
-      format = PixelFormat_RGB24;
-      pitch = 3 * width;
-    }
-    else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8)
-    {
-      format = PixelFormat_RGBA32;
-      pitch = 4 * width;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    data_.resize(height * pitch);
-
-    if (height == 0 || width == 0)
-    {
-      // Empty image, we are done
-      AssignEmpty(format);
-      return;
-    }
-    
-    png_read_update_info(rabi.png_, rabi.info_);
-
-    std::vector<png_bytep> rows(height);
-    for (size_t i = 0; i < height; i++)
-    {
-      rows[i] = &data_[0] + i * pitch;
-    }
-
-    png_read_image(rabi.png_, &rows[0]);
-
-    AssignWritable(format, width, height, pitch, &data_[0]);
-  }
-
-
-#if ORTHANC_SANDBOXED == 0
-  void PngReader::ReadFromFile(const std::string& filename)
-  {
-    FileRabi f(filename.c_str());
-
-    char header[8];
-    if (fread(header, 1, 8, f.fp_) != 8)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    CheckHeader(header);
-
-    PngRabi rabi;
-
-    if (setjmp(png_jmpbuf(rabi.png_)))
-    {
-      rabi.Destruct();
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    png_init_io(rabi.png_, f.fp_);
-
-    Read(rabi);
-  }
-#endif
-
-
-  namespace
-  {
-    struct MemoryBuffer
-    {
-      const uint8_t* buffer_;
-      size_t size_;
-      size_t pos_;
-      bool ok_;
-    };
-  }
-
-
-  void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, 
-                                          png_bytep outBytes, 
-                                          png_size_t byteCountToRead)
-  {
-    MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr));
-
-    if (!from->ok_)
-    {
-      return;
-    }
-
-    if (from->pos_ + byteCountToRead > from->size_)
-    {
-      from->ok_ = false;
-      return;
-    }
-
-    memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead);
-
-    from->pos_ += byteCountToRead;
-  }
-
-
-  void PngReader::ReadFromMemory(const void* buffer,
-                                 size_t size)
-  {
-    if (size < 8)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    CheckHeader(buffer);
-
-    PngRabi rabi;
-
-    if (setjmp(png_jmpbuf(rabi.png_)))
-    {
-      rabi.Destruct();
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    MemoryBuffer tmp;
-    tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8;  // We skip the header
-    tmp.size_ = size - 8;
-    tmp.pos_ = 0;
-    tmp.ok_ = true;
-
-    png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback);
-
-    Read(rabi);
-
-    if (!tmp.ok_)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-  void PngReader::ReadFromMemory(const std::string& buffer)
-  {
-    if (buffer.size() != 0)
-    {
-      ReadFromMemory(&buffer[0], buffer.size());
-    }
-    else
-    {
-      ReadFromMemory(NULL, 0);
-    }
-  }
-}
--- a/Resources/Orthanc/Core/Images/PngReader.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ImageAccessor.h"
-
-#include "../Enumerations.h"
-
-#include <vector>
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
-
-#if !defined(ORTHANC_SANDBOXED)
-#  error The macro ORTHANC_SANDBOXED must be defined
-#endif
-
-namespace Orthanc
-{
-  class PngReader : 
-    public ImageAccessor, 
-    public boost::noncopyable
-  {
-  private:
-    struct PngRabi;
-
-    std::vector<uint8_t> data_;
-
-    void CheckHeader(const void* header);
-
-    void Read(PngRabi& rabi);
-
-  public:
-    PngReader();
-
-#if ORTHANC_SANDBOXED == 0
-    void ReadFromFile(const std::string& filename);
-#endif
-
-    void ReadFromMemory(const void* buffer,
-                        size_t size);
-
-    void ReadFromMemory(const std::string& buffer);
-  };
-}
--- a/Resources/Orthanc/Core/Logging.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,543 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "Logging.h"
-
-#if ORTHANC_ENABLE_LOGGING != 1
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    void Initialize()
-    {
-    }
-
-    void Finalize()
-    {
-    }
-
-    void Reset()
-    {
-    }
-
-    void Flush()
-    {
-    }
-
-    void EnableInfoLevel(bool enabled)
-    {
-    }
-
-    void EnableTraceLevel(bool enabled)
-    {
-    }
-
-    void SetTargetFile(const std::string& path)
-    {
-    }
-
-    void SetTargetFolder(const std::string& path)
-    {
-    }
-  }
-}
-
-
-#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1
-
-/*********************************************************
- * Logger compatible with the Orthanc plugin SDK
- *********************************************************/
-
-#include <boost/lexical_cast.hpp>
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    static OrthancPluginContext* context_ = NULL;
-
-    void Initialize(OrthancPluginContext* context)
-    {
-      context_ = context;
-    }
-
-    InternalLogger::InternalLogger(const char* level,
-                                   const char* file  /* ignored */,
-                                   int line  /* ignored */) :
-      level_(level)
-    {
-    }
-
-    InternalLogger::~InternalLogger()
-    {
-      if (context_ != NULL)
-      {
-        if (level_ == "ERROR")
-        {
-          OrthancPluginLogError(context_, message_.c_str());
-        }
-        else if (level_ == "WARNING")
-        {
-          OrthancPluginLogWarning(context_, message_.c_str());
-        }
-        else if (level_ == "INFO")
-        {
-          OrthancPluginLogInfo(context_, message_.c_str());
-        }
-        else
-        {
-          std::string s = "Unknown log level (" + level_ + ") for message: " + message_;
-          OrthancPluginLogError(context_, s.c_str());
-        }
-      }
-    }
-
-    InternalLogger& InternalLogger::operator<< (const std::string& message)
-    {
-      message_ += message;
-      return *this;
-    }
-
-    InternalLogger& InternalLogger::operator<< (const char* message)
-    {
-      message_ += std::string(message);
-      return *this;
-    }
-
-    InternalLogger& InternalLogger::operator<< (int message)
-    {
-      message_ += boost::lexical_cast<std::string>(message);
-      return *this;
-    }
-  }
-}
-
-
-#else  /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && ORTHANC_ENABLE_LOGGING == 1 */
-
-/*********************************************************
- * Internal logger of Orthanc, that mimics some
- * behavior from Google Log.
- *********************************************************/
-
-#include "OrthancException.h"
-#include "Enumerations.h"
-#include "Toolbox.h"
-#include "SystemToolbox.h"
-
-#include <fstream>
-#include <boost/filesystem.hpp>
-#include <boost/thread.hpp>
-
-#if BOOST_HAS_DATE_TIME == 1
-#  include <boost/date_time/posix_time/posix_time.hpp>
-#else
-#  error Boost::date_time is required
-#endif
-
-
-namespace
-{
-  struct LoggingContext
-  {
-    bool infoEnabled_;
-    bool traceEnabled_;
-    std::string  targetFile_;
-    std::string  targetFolder_;
-
-    std::ostream* error_;
-    std::ostream* warning_;
-    std::ostream* info_;
-
-    std::auto_ptr<std::ofstream> file_;
-
-    LoggingContext() : 
-      infoEnabled_(false),
-      traceEnabled_(false),
-      error_(&std::cerr),
-      warning_(&std::cerr),
-      info_(&std::cerr)
-    {
-    }
-  };
-}
-
-
-
-static std::auto_ptr<LoggingContext> loggingContext_;
-static boost::mutex  loggingMutex_;
-
-
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    static void GetLogPath(boost::filesystem::path& log,
-                           boost::filesystem::path& link,
-                           const std::string& suffix,
-                           const std::string& directory)
-    {
-      /**
-         From Google Log documentation:
-
-         Unless otherwise specified, logs will be written to the filename
-         "<program name>.<hostname>.<user name>.log<suffix>.",
-         followed by the date, time, and pid (you can't prevent the date,
-         time, and pid from being in the filename).
-
-         In this implementation : "hostname" and "username" are not used
-      **/
-
-      boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-      boost::filesystem::path root(directory);
-      boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
-      
-      if (!boost::filesystem::exists(root) ||
-          !boost::filesystem::is_directory(root))
-      {
-        throw OrthancException(ErrorCode_CannotWriteFile);
-      }
-
-      char date[64];
-      sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d",
-              static_cast<int>(now.date().year()),
-              now.date().month().as_number(),
-              now.date().day().as_number(),
-              now.time_of_day().hours(),
-              now.time_of_day().minutes(),
-              now.time_of_day().seconds(),
-              SystemToolbox::GetProcessId());
-
-      std::string programName = exe.filename().replace_extension("").string();
-
-      log = (root / (programName + ".log" + suffix + "." + std::string(date)));
-      link = (root / (programName + ".log" + suffix));
-    }
-
-
-    static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file,
-                                 const std::string& suffix,
-                                 const std::string& directory)
-    {
-      boost::filesystem::path log, link;
-      GetLogPath(log, link, suffix, directory);
-
-#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
-      boost::filesystem::remove(link);
-      boost::filesystem::create_symlink(log.filename(), link);
-#endif
-
-      file.reset(new std::ofstream(log.string().c_str()));
-    }
-
-
-    void Initialize()
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-      loggingContext_.reset(new LoggingContext);
-    }
-
-    void Finalize()
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-      loggingContext_.reset(NULL);
-    }
-
-    void Reset()
-    {
-      // Recover the old logging context
-      std::auto_ptr<LoggingContext> old;
-
-      {
-        boost::mutex::scoped_lock lock(loggingMutex_);
-        if (loggingContext_.get() == NULL)
-        {
-          return;
-        }
-        else
-        {
-          old = loggingContext_;
-
-          // Create a new logging context, 
-          loggingContext_.reset(new LoggingContext);
-        }
-      }
-      
-      EnableInfoLevel(old->infoEnabled_);
-      EnableTraceLevel(old->traceEnabled_);
-
-      if (!old->targetFolder_.empty())
-      {
-        SetTargetFolder(old->targetFolder_);
-      }
-      else if (!old->targetFile_.empty())
-      {
-        SetTargetFile(old->targetFile_);
-      }
-    }
-
-    void EnableInfoLevel(bool enabled)
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-      assert(loggingContext_.get() != NULL);
-
-      loggingContext_->infoEnabled_ = enabled;
-      
-      if (!enabled)
-      {
-        // Also disable the "TRACE" level when info-level debugging is disabled
-        loggingContext_->traceEnabled_ = false;
-      }
-    }
-
-    void EnableTraceLevel(bool enabled)
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-      assert(loggingContext_.get() != NULL);
-
-      loggingContext_->traceEnabled_ = enabled;
-      
-      if (enabled)
-      {
-        // Also enable the "INFO" level when trace-level debugging is enabled
-        loggingContext_->infoEnabled_ = true;
-      }
-    }
-
-
-    static void CheckFile(std::auto_ptr<std::ofstream>& f)
-    {
-      if (loggingContext_->file_.get() == NULL ||
-          !loggingContext_->file_->is_open())
-      {
-        throw OrthancException(ErrorCode_CannotWriteFile);
-      }
-    }
-
-    void SetTargetFolder(const std::string& path)
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-      assert(loggingContext_.get() != NULL);
-
-      PrepareLogFolder(loggingContext_->file_, "" /* no suffix */, path);
-      CheckFile(loggingContext_->file_);
-
-      loggingContext_->targetFile_.clear();
-      loggingContext_->targetFolder_ = path;
-      loggingContext_->warning_ = loggingContext_->file_.get();
-      loggingContext_->error_ = loggingContext_->file_.get();
-      loggingContext_->info_ = loggingContext_->file_.get();
-    }
-
-
-    void SetTargetFile(const std::string& path)
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-      assert(loggingContext_.get() != NULL);
-
-      loggingContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app));
-      CheckFile(loggingContext_->file_);
-
-      loggingContext_->targetFile_ = path;
-      loggingContext_->targetFolder_.clear();
-      loggingContext_->warning_ = loggingContext_->file_.get();
-      loggingContext_->error_ = loggingContext_->file_.get();
-      loggingContext_->info_ = loggingContext_->file_.get();
-    }
-
-
-    InternalLogger::InternalLogger(const char* level,
-                                   const char* file,
-                                   int line) : 
-      lock_(loggingMutex_), 
-      stream_(&null_)  // By default, logging to "/dev/null" is simulated
-    {
-      if (loggingContext_.get() == NULL)
-      {
-        fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
-        return;
-      }
-
-      try
-      {
-        LogLevel l = StringToLogLevel(level);
-      
-        if ((l == LogLevel_Info  && !loggingContext_->infoEnabled_) ||
-            (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
-        {
-          // This logging level is disabled, directly exit and unlock
-          // the mutex to speed-up things. The stream is set to "/dev/null"
-          lock_.unlock();
-          return;
-        }
-
-        // Compute the header of the line, temporary release the lock as
-        // this is a time-consuming operation
-        lock_.unlock();
-        std::string header;
-
-        {
-          boost::filesystem::path path(file);
-          boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
-          boost::posix_time::time_duration duration = now.time_of_day();
-
-          /**
-             From Google Log documentation:
-
-             "Log lines have this form:
-
-             Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
-
-             where the fields are defined as follows:
-
-             L                A single character, representing the log level (eg 'I' for INFO)
-             mm               The month (zero padded; ie May is '05')
-             dd               The day (zero padded)
-             hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
-             threadid         The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
-             file             The file name
-             line             The line number
-             msg              The user-supplied message"
-
-             In this implementation, "threadid" is not printed.
-          **/
-
-          char date[32];
-          sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
-                  level[0],
-                  now.date().month().as_number(),
-                  now.date().day().as_number(),
-                  duration.hours(),
-                  duration.minutes(),
-                  duration.seconds(),
-                  static_cast<int>(duration.fractional_seconds()));
-
-          header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
-        }
-
-
-        // The header is computed, we now re-lock the mutex to access
-        // the stream objects. Pay attention that "loggingContext_",
-        // "infoEnabled_" or "traceEnabled_" might have changed while
-        // the mutex was unlocked.
-        lock_.lock();
-
-        if (loggingContext_.get() == NULL)
-        {
-          fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
-          return;
-        }
-
-        switch (l)
-        {
-          case LogLevel_Error:
-            stream_ = loggingContext_->error_;
-            break;
-
-          case LogLevel_Warning:
-            stream_ = loggingContext_->warning_;
-            break;
-
-          case LogLevel_Info:
-            if (loggingContext_->infoEnabled_)
-            {
-              stream_ = loggingContext_->info_;
-            }
-
-            break;
-
-          case LogLevel_Trace:
-            if (loggingContext_->traceEnabled_)
-            {
-              stream_ = loggingContext_->info_;
-            }
-
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_InternalError);
-        }
-
-        if (stream_ == &null_)
-        {
-          // The logging is disabled for this level. The stream is the
-          // "null_" member of this object, so we can release the global
-          // mutex.
-          lock_.unlock();
-        }
-
-        (*stream_) << header;
-      }
-      catch (...)
-      { 
-        // Something is going really wrong, probably running out of
-        // memory. Fallback to a degraded mode.
-        stream_ = loggingContext_->error_;
-        (*stream_) << "E???? ??:??:??.?????? ] ";
-      }
-    }
-
-
-    InternalLogger::~InternalLogger()
-    {
-      if (stream_ != &null_)
-      {
-#if defined(_WIN32)
-        *stream_ << "\r\n";
-#else
-        *stream_ << "\n";
-#endif
-
-        stream_->flush();
-      }
-    }
-      
-
-    void Flush()
-    {
-      boost::mutex::scoped_lock lock(loggingMutex_);
-
-      if (loggingContext_.get() != NULL &&
-          loggingContext_->file_.get() != NULL)
-      {
-        loggingContext_->file_->flush();
-      }
-    }
-  }
-}
-
-#endif   // ORTHANC_ENABLE_LOGGING
--- a/Resources/Orthanc/Core/Logging.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <iostream>
-
-#if !defined(ORTHANC_ENABLE_LOGGING)
-#  error The macro ORTHANC_ENABLE_LOGGING must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_LOGGING_PLUGIN)
-#  if ORTHANC_ENABLE_LOGGING == 1
-#    error The macro ORTHANC_ENABLE_LOGGING_PLUGIN must be defined
-#  else
-#    define ORTHANC_ENABLE_LOGGING_PLUGIN 0
-#  endif
-#endif
-
-#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1
-#  include <orthanc/OrthancCPlugin.h>
-#endif
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1
-    void Initialize(OrthancPluginContext* context);
-#else
-    void Initialize();
-#endif
-
-    void Finalize();
-
-    void Reset();
-
-    void Flush();
-
-    void EnableInfoLevel(bool enabled);
-
-    void EnableTraceLevel(bool enabled);
-
-    void SetTargetFile(const std::string& path);
-
-    void SetTargetFolder(const std::string& path);
-
-    struct NullStream : public std::ostream 
-    {
-      NullStream() : 
-        std::ios(0), 
-        std::ostream(0)
-      {
-      }
-      
-      std::ostream& operator<< (const std::string& message)
-      {
-        return *this;
-      }
-
-      // This overload fixes build problems with Visual Studio 2015
-      std::ostream& operator<< (const char* message)
-      {
-        return *this;
-      }
-    };
-  }
-}
-
-
-#if ORTHANC_ENABLE_LOGGING != 1
-
-#  define LOG(level)   ::Orthanc::Logging::NullStream()
-#  define VLOG(level)  ::Orthanc::Logging::NullStream()
-
-
-#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1
-
-#  include <boost/noncopyable.hpp>
-#  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
-#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    class InternalLogger : public boost::noncopyable
-    {
-    private:
-      std::string level_;
-      std::string message_;
-
-    public:
-      InternalLogger(const char* level,
-                     const char* file,
-                     int line);
-
-      ~InternalLogger();
-      
-      InternalLogger& operator<< (const std::string& message);
-
-      InternalLogger& operator<< (const char* message);
-
-      InternalLogger& operator<< (int message);
-    };
-  }
-}
-
-
-#else  /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && ORTHANC_ENABLE_LOGGING == 1 */
-
-#  include <boost/thread/mutex.hpp>
-#  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
-#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
-
-namespace Orthanc
-{
-  namespace Logging
-  {
-    class InternalLogger
-    {
-    private:
-      boost::mutex::scoped_lock lock_;
-      NullStream                null_;
-      std::ostream*             stream_;
-
-    public:
-      InternalLogger(const char* level,
-                     const char* file,
-                     int line);
-
-      ~InternalLogger();
-      
-      std::ostream& operator<< (const std::string& message)
-      {
-        return (*stream_) << message;
-      }
-
-      // This overload fixes build problems with Visual Studio 2015
-      std::ostream& operator<< (const char* message)
-      {
-        return (*stream_) << message;
-      }
-    };
-  }
-}
-
-#endif  // ORTHANC_ENABLE_LOGGING
--- a/Resources/Orthanc/Core/OrthancException.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <stdint.h>
-#include <string>
-#include "Enumerations.h"
-
-namespace Orthanc
-{
-  class OrthancException
-  {
-  protected:
-    ErrorCode  errorCode_;
-    HttpStatus httpStatus_;
-
-  public:
-    explicit OrthancException(ErrorCode errorCode) : 
-      errorCode_(errorCode),
-      httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
-    {
-    }
-
-    OrthancException(ErrorCode errorCode,
-                     HttpStatus httpStatus) :
-      errorCode_(errorCode),
-      httpStatus_(httpStatus)
-    {
-    }
-
-    ErrorCode GetErrorCode() const
-    {
-      return errorCode_;
-    }
-
-    HttpStatus GetHttpStatus() const
-    {
-      return httpStatus_;
-    }
-
-    const char* What() const
-    {
-      return EnumerationToString(errorCode_);
-    }
-  };
-}
--- a/Resources/Orthanc/Core/PrecompiledHeaders.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if defined(_WIN32) && !defined(NOMINMAX)
-#define NOMINMAX
-#endif
-
-#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
-
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/locale.hpp>
-#include <boost/regex.hpp>
-#include <boost/thread.hpp>
-#include <boost/thread/shared_mutex.hpp>
-
-#include <json/value.h>
-
-#if ORTHANC_ENABLE_PUGIXML == 1
-#include <pugixml.hpp>
-#endif
-
-#include "Enumerations.h"
-#include "Logging.h"
-#include "OrthancException.h"
-#include "Toolbox.h"
-
-#endif
--- a/Resources/Orthanc/Core/SystemToolbox.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,552 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "SystemToolbox.h"
-
-
-#if BOOST_HAS_DATE_TIME == 1
-#  include <boost/date_time/posix_time/posix_time.hpp>
-#endif
-
-
-#if defined(_WIN32)
-#  include <windows.h>
-#  include <process.h>   // For "_spawnvp()" and "_getpid()"
-#else
-#  include <unistd.h>    // For "execvp()"
-#  include <sys/wait.h>  // For "waitpid()"
-#endif
-
-
-#if defined(__APPLE__) && defined(__MACH__)
-#  include <mach-o/dyld.h> /* _NSGetExecutablePath */
-#  include <limits.h>      /* PATH_MAX */
-#endif
-
-
-#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
-#  include <limits.h>      /* PATH_MAX */
-#  include <signal.h>
-#  include <unistd.h>
-#endif
-
-
-// Inclusions for UUID
-// http://stackoverflow.com/a/1626302
-
-extern "C"
-{
-#ifdef WIN32
-#  include <rpc.h>
-#else
-#  include <uuid/uuid.h>
-#endif
-}
-
-
-#include "Logging.h"
-#include "OrthancException.h"
-#include "Toolbox.h"
-
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/fstream.hpp>
-
-
-namespace Orthanc
-{
-  static bool finish_;
-  static ServerBarrierEvent barrierEvent_;
-
-#if defined(_WIN32)
-  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
-  {
-    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
-    finish_ = true;
-    return true;
-  }
-#else
-  static void SignalHandler(int signal)
-  {
-    if (signal == SIGHUP)
-    {
-      barrierEvent_ = ServerBarrierEvent_Reload;
-    }
-
-    finish_ = true;
-  }
-#endif
-
-
-  static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
-  {
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, true);
-#else
-    signal(SIGINT, SignalHandler);
-    signal(SIGQUIT, SignalHandler);
-    signal(SIGTERM, SignalHandler);
-    signal(SIGHUP, SignalHandler);
-#endif
-  
-    // Active loop that awakens every 100ms
-    finish_ = false;
-    barrierEvent_ = ServerBarrierEvent_Stop;
-    while (!(*stopFlag || finish_))
-    {
-      SystemToolbox::USleep(100 * 1000);
-    }
-
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, false);
-#else
-    signal(SIGINT, NULL);
-    signal(SIGQUIT, NULL);
-    signal(SIGTERM, NULL);
-    signal(SIGHUP, NULL);
-#endif
-
-    return barrierEvent_;
-  }
-
-
-  ServerBarrierEvent SystemToolbox::ServerBarrier(const bool& stopFlag)
-  {
-    return ServerBarrierInternal(&stopFlag);
-  }
-
-
-  ServerBarrierEvent SystemToolbox::ServerBarrier()
-  {
-    const bool stopFlag = false;
-    return ServerBarrierInternal(&stopFlag);
-  }
-
-
-  void SystemToolbox::USleep(uint64_t microSeconds)
-  {
-#if defined(_WIN32)
-    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
-#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__native_client__)
-    usleep(microSeconds);
-#else
-#error Support your platform here
-#endif
-  }
-
-
-  static std::streamsize GetStreamSize(std::istream& f)
-  {
-    // http://www.cplusplus.com/reference/iostream/istream/tellg/
-    f.seekg(0, std::ios::end);
-    std::streamsize size = f.tellg();
-    f.seekg(0, std::ios::beg);
-
-    return size;
-  }
-
-
-  void SystemToolbox::ReadFile(std::string& content,
-                               const std::string& path) 
-  {
-    if (!IsRegularFile(path))
-    {
-      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
-      throw OrthancException(ErrorCode_RegularFileExpected);
-    }
-
-    boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ifstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    std::streamsize size = GetStreamSize(f);
-    content.resize(size);
-    if (size != 0)
-    {
-      f.read(reinterpret_cast<char*>(&content[0]), size);
-    }
-
-    f.close();
-  }
-
-
-  bool SystemToolbox::ReadHeader(std::string& header,
-                                 const std::string& path,
-                                 size_t headerSize)
-  {
-    if (!IsRegularFile(path))
-    {
-      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
-      throw OrthancException(ErrorCode_RegularFileExpected);
-    }
-
-    boost::filesystem::ifstream f;
-    f.open(path, std::ifstream::in | std::ifstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    bool full = true;
-
-    {
-      std::streamsize size = GetStreamSize(f);
-      if (size <= 0)
-      {
-        headerSize = 0;
-        full = false;
-      }
-      else if (static_cast<size_t>(size) < headerSize)
-      {
-        headerSize = size;  // Truncate to the size of the file
-        full = false;
-      }
-    }
-
-    header.resize(headerSize);
-    if (headerSize != 0)
-    {
-      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
-    }
-
-    f.close();
-
-    return full;
-  }
-
-
-  void SystemToolbox::WriteFile(const void* content,
-                                size_t size,
-                                const std::string& path)
-  {
-    boost::filesystem::ofstream f;
-    f.open(path, std::ofstream::out | std::ofstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_CannotWriteFile);
-    }
-
-    if (size != 0)
-    {
-      f.write(reinterpret_cast<const char*>(content), size);
-
-      if (!f.good())
-      {
-        f.close();
-        throw OrthancException(ErrorCode_FileStorageCannotWrite);
-      }
-    }
-
-    f.close();
-  }
-
-
-  void SystemToolbox::WriteFile(const std::string& content,
-                                const std::string& path)
-  {
-    WriteFile(content.size() > 0 ? content.c_str() : NULL,
-              content.size(), path);
-  }
-
-
-  void SystemToolbox::RemoveFile(const std::string& path)
-  {
-    if (boost::filesystem::exists(path))
-    {
-      if (IsRegularFile(path))
-      {
-        boost::filesystem::remove(path);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_RegularFileExpected);
-      }
-    }
-  }
-
-
-  uint64_t SystemToolbox::GetFileSize(const std::string& path)
-  {
-    try
-    {
-      return static_cast<uint64_t>(boost::filesystem::file_size(path));
-    }
-    catch (boost::filesystem::filesystem_error&)
-    {
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-  }
-
-
-  void SystemToolbox::MakeDirectory(const std::string& path)
-  {
-    if (boost::filesystem::exists(path))
-    {
-      if (!boost::filesystem::is_directory(path))
-      {
-        throw OrthancException(ErrorCode_DirectoryOverFile);
-      }
-    }
-    else
-    {
-      if (!boost::filesystem::create_directories(path))
-      {
-        throw OrthancException(ErrorCode_MakeDirectory);
-      }
-    }
-  }
-
-
-  bool SystemToolbox::IsExistingFile(const std::string& path)
-  {
-    return boost::filesystem::exists(path);
-  }
-
-
-#if defined(_WIN32)
-  static std::string GetPathToExecutableInternal()
-  {
-    // Yes, this is ugly, but there is no simple way to get the 
-    // required buffer size, so we use a big constant
-    std::vector<char> buffer(32768);
-    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
-    return std::string(&buffer[0]);
-  }
-
-#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
-  static std::string GetPathToExecutableInternal()
-  {
-    std::vector<char> buffer(PATH_MAX + 1);
-    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
-    if (bytes == 0)
-    {
-      throw OrthancException(ErrorCode_PathToExecutable);
-    }
-
-    return std::string(&buffer[0]);
-  }
-
-#elif defined(__APPLE__) && defined(__MACH__)
-  static std::string GetPathToExecutableInternal()
-  {
-    char pathbuf[PATH_MAX + 1];
-    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));
-
-    _NSGetExecutablePath( pathbuf, &bufsize);
-
-    return std::string(pathbuf);
-  }
-
-#else
-#error Support your platform here
-#endif
-
-
-  std::string SystemToolbox::GetPathToExecutable()
-  {
-    boost::filesystem::path p(GetPathToExecutableInternal());
-    return boost::filesystem::absolute(p).string();
-  }
-
-
-  std::string SystemToolbox::GetDirectoryOfExecutable()
-  {
-    boost::filesystem::path p(GetPathToExecutableInternal());
-    return boost::filesystem::absolute(p.parent_path()).string();
-  }
-
-
-  void SystemToolbox::ExecuteSystemCommand(const std::string& command,
-                                           const std::vector<std::string>& arguments)
-  {
-    // Convert the arguments as a C array
-    std::vector<char*>  args(arguments.size() + 2);
-
-    args.front() = const_cast<char*>(command.c_str());
-
-    for (size_t i = 0; i < arguments.size(); i++)
-    {
-      args[i + 1] = const_cast<char*>(arguments[i].c_str());
-    }
-
-    args.back() = NULL;
-
-    int status;
-
-#if defined(_WIN32)
-    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
-    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));
-
-#else
-    int pid = fork();
-
-    if (pid == -1)
-    {
-      // Error in fork()
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "Cannot fork a child process";
-#endif
-
-      throw OrthancException(ErrorCode_SystemCommand);
-    }
-    else if (pid == 0)
-    {
-      // Execute the system command in the child process
-      execvp(command.c_str(), &args[0]);
-
-      // We should never get here
-      _exit(1);
-    }
-    else
-    {
-      // Wait for the system command to exit
-      waitpid(pid, &status, 0);
-    }
-#endif
-
-    if (status != 0)
-    {
-#if ORTHANC_ENABLE_LOGGING == 1
-      LOG(ERROR) << "System command failed with status code " << status;
-#endif
-
-      throw OrthancException(ErrorCode_SystemCommand);
-    }
-  }
-
-
-  int SystemToolbox::GetProcessId()
-  {
-#if defined(_WIN32)
-    return static_cast<int>(_getpid());
-#else
-    return static_cast<int>(getpid());
-#endif
-  }
-
-
-  bool SystemToolbox::IsRegularFile(const std::string& path)
-  {
-    namespace fs = boost::filesystem;
-
-    try
-    {
-      if (fs::exists(path))
-      {
-        fs::file_status status = fs::status(path);
-        return (status.type() == boost::filesystem::regular_file ||
-                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
-      }
-    }
-    catch (fs::filesystem_error&)
-    {
-    }
-
-    return false;
-  }
-
-
-  FILE* SystemToolbox::OpenFile(const std::string& path,
-                                FileMode mode)
-  {
-#if defined(_WIN32)
-    // TODO Deal with special characters by converting to the current locale
-#endif
-
-    const char* m;
-    switch (mode)
-    {
-      case FileMode_ReadBinary:
-        m = "rb";
-        break;
-
-      case FileMode_WriteBinary:
-        m = "wb";
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    return fopen(path.c_str(), m);
-  }
-
-
-  std::string SystemToolbox::GenerateUuid()
-  {
-#ifdef WIN32
-    UUID uuid;
-    UuidCreate ( &uuid );
-
-    unsigned char * str;
-    UuidToStringA ( &uuid, &str );
-
-    std::string s( ( char* ) str );
-
-    RpcStringFreeA ( &str );
-#else
-    uuid_t uuid;
-    uuid_generate_random ( uuid );
-    char s[37];
-    uuid_unparse ( uuid, s );
-#endif
-    return s;
-  }
-
-
-#if BOOST_HAS_DATE_TIME == 1
-  std::string SystemToolbox::GetNowIsoString()
-  {
-    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-    return boost::posix_time::to_iso_string(now);
-  }
-
-  void SystemToolbox::GetNowDicom(std::string& date,
-                                  std::string& time)
-  {
-    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-    tm tm = boost::posix_time::to_tm(now);
-
-    char s[32];
-    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
-    date.assign(s);
-
-    // TODO milliseconds
-    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
-    time.assign(s);
-  }
-#endif
-}
--- a/Resources/Orthanc/Core/SystemToolbox.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU 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 namespace SystemToolbox cannot be used in sandboxed environments
-#endif
-
-#include "Enumerations.h"
-
-#include <vector>
-#include <string>
-#include <stdint.h>
-
-namespace Orthanc
-{
-  namespace SystemToolbox
-  {
-    void USleep(uint64_t microSeconds);
-
-    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
-
-    ServerBarrierEvent ServerBarrier();
-
-    void ReadFile(std::string& content,
-                  const std::string& path);
-
-    bool ReadHeader(std::string& header,
-                    const std::string& path,
-                    size_t headerSize);
-
-    void WriteFile(const void* content,
-                   size_t size,
-                   const std::string& path);
-
-    void WriteFile(const std::string& content,
-                   const std::string& path);
-
-    void RemoveFile(const std::string& path);
-
-    uint64_t GetFileSize(const std::string& path);
-
-    void MakeDirectory(const std::string& path);
-
-    bool IsExistingFile(const std::string& path);
-
-    std::string GetPathToExecutable();
-
-    std::string GetDirectoryOfExecutable();
-
-    void ExecuteSystemCommand(const std::string& command,
-                              const std::vector<std::string>& arguments);
-
-    int GetProcessId();
-
-    bool IsRegularFile(const std::string& path);
-
-    FILE* OpenFile(const std::string& path,
-                   FileMode mode);
-
-    std::string GenerateUuid();
-
-#if BOOST_HAS_DATE_TIME == 1
-    std::string GetNowIsoString();
-
-    void GetNowDicom(std::string& date,
-                     std::string& time);
-#endif
-  }
-}
--- a/Resources/Orthanc/Core/Toolbox.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1254 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "Toolbox.h"
-
-#include "OrthancException.h"
-#include "Logging.h"
-
-#include <boost/algorithm/string/replace.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/locale.hpp>
-#include <boost/uuid/sha1.hpp>
-
-#include <string>
-#include <stdint.h>
-#include <string.h>
-#include <algorithm>
-#include <ctype.h>
-
-#if BOOST_HAS_REGEX == 1
-#  include <boost/regex.hpp> 
-#endif
-
-#if BOOST_HAS_LOCALE != 1
-#  error Since version 0.7.6, Orthanc entirely relies on boost::locale
-#endif
-
-#if ORTHANC_ENABLE_MD5 == 1
-#  include "../Resources/ThirdParty/md5/md5.h"
-#endif
-
-#if ORTHANC_ENABLE_BASE64 == 1
-#  include "../Resources/ThirdParty/base64/base64.h"
-#endif
-
-
-#if defined(_MSC_VER) && (_MSC_VER < 1800)
-// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013
-extern "C"
-{
-  int64_t _strtoi64(const char *nptr, char **endptr, int base);
-  int64_t strtoll(const char *nptr, char **endptr, int base)
-  {
-    return _strtoi64(nptr, endptr, base);
-  } 
-}
-#endif
-
-
-#if defined(_WIN32)
-#  include <windows.h>   // For ::Sleep
-#endif
-
-
-#if ORTHANC_ENABLE_PUGIXML == 1
-#  include "ChunkedBuffer.h"
-#  include <pugixml.hpp>
-#endif
-
-
-namespace Orthanc
-{
-  void Toolbox::ToUpperCase(std::string& s)
-  {
-    std::transform(s.begin(), s.end(), s.begin(), toupper);
-  }
-
-
-  void Toolbox::ToLowerCase(std::string& s)
-  {
-    std::transform(s.begin(), s.end(), s.begin(), tolower);
-  }
-
-
-  void Toolbox::ToUpperCase(std::string& result,
-                            const std::string& source)
-  {
-    result = source;
-    ToUpperCase(result);
-  }
-
-  void Toolbox::ToLowerCase(std::string& result,
-                            const std::string& source)
-  {
-    result = source;
-    ToLowerCase(result);
-  }
-
-
-  void Toolbox::SplitUriComponents(UriComponents& components,
-                                   const std::string& uri)
-  {
-    static const char URI_SEPARATOR = '/';
-
-    components.clear();
-
-    if (uri.size() == 0 ||
-        uri[0] != URI_SEPARATOR)
-    {
-      throw OrthancException(ErrorCode_UriSyntax);
-    }
-
-    // Count the number of slashes in the URI to make an assumption
-    // about the number of components in the URI
-    unsigned int estimatedSize = 0;
-    for (unsigned int i = 0; i < uri.size(); i++)
-    {
-      if (uri[i] == URI_SEPARATOR)
-        estimatedSize++;
-    }
-
-    components.reserve(estimatedSize - 1);
-
-    unsigned int start = 1;
-    unsigned int end = 1;
-    while (end < uri.size())
-    {
-      // This is the loop invariant
-      assert(uri[start - 1] == '/' && (end >= start));
-
-      if (uri[end] == '/')
-      {
-        components.push_back(std::string(&uri[start], end - start));
-        end++;
-        start = end;
-      }
-      else
-      {
-        end++;
-      }
-    }
-
-    if (start < uri.size())
-    {
-      components.push_back(std::string(&uri[start], end - start));
-    }
-
-    for (size_t i = 0; i < components.size(); i++)
-    {
-      if (components[i].size() == 0)
-      {
-        // Empty component, as in: "/coucou//e"
-        throw OrthancException(ErrorCode_UriSyntax);
-      }
-    }
-  }
-
-
-  void Toolbox::TruncateUri(UriComponents& target,
-                            const UriComponents& source,
-                            size_t fromLevel)
-  {
-    target.clear();
-
-    if (source.size() > fromLevel)
-    {
-      target.resize(source.size() - fromLevel);
-
-      size_t j = 0;
-      for (size_t i = fromLevel; i < source.size(); i++, j++)
-      {
-        target[j] = source[i];
-      }
-
-      assert(j == target.size());
-    }
-  }
-  
-
-
-  bool Toolbox::IsChildUri(const UriComponents& baseUri,
-                           const UriComponents& testedUri)
-  {
-    if (testedUri.size() < baseUri.size())
-    {
-      return false;
-    }
-
-    for (size_t i = 0; i < baseUri.size(); i++)
-    {
-      if (baseUri[i] != testedUri[i])
-        return false;
-    }
-
-    return true;
-  }
-
-
-  std::string Toolbox::AutodetectMimeType(const std::string& path)
-  {
-    std::string contentType;
-    size_t lastDot = path.rfind('.');
-    size_t lastSlash = path.rfind('/');
-
-    if (lastDot == std::string::npos ||
-        (lastSlash != std::string::npos && lastDot < lastSlash))
-    {
-      // No trailing dot, unable to detect the content type
-    }
-    else
-    {
-      const char* extension = &path[lastDot + 1];
-    
-      // http://en.wikipedia.org/wiki/Mime_types
-      // Text types
-      if (!strcmp(extension, "txt"))
-        contentType = "text/plain";
-      else if (!strcmp(extension, "html"))
-        contentType = "text/html";
-      else if (!strcmp(extension, "xml"))
-        contentType = "text/xml";
-      else if (!strcmp(extension, "css"))
-        contentType = "text/css";
-
-      // Application types
-      else if (!strcmp(extension, "js"))
-        contentType = "application/javascript";
-      else if (!strcmp(extension, "json"))
-        contentType = "application/json";
-      else if (!strcmp(extension, "pdf"))
-        contentType = "application/pdf";
-
-      // Images types
-      else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg"))
-        contentType = "image/jpeg";
-      else if (!strcmp(extension, "gif"))
-        contentType = "image/gif";
-      else if (!strcmp(extension, "png"))
-        contentType = "image/png";
-    }
-
-    return contentType;
-  }
-
-
-  std::string Toolbox::FlattenUri(const UriComponents& components,
-                                  size_t fromLevel)
-  {
-    if (components.size() <= fromLevel)
-    {
-      return "/";
-    }
-    else
-    {
-      std::string r;
-
-      for (size_t i = fromLevel; i < components.size(); i++)
-      {
-        r += "/" + components[i];
-      }
-
-      return r;
-    }
-  }
-
-
-#if ORTHANC_ENABLE_MD5 == 1
-  static char GetHexadecimalCharacter(uint8_t value)
-  {
-    assert(value < 16);
-
-    if (value < 10)
-    {
-      return value + '0';
-    }
-    else
-    {
-      return (value - 10) + 'a';
-    }
-  }
-
-
-  void Toolbox::ComputeMD5(std::string& result,
-                           const std::string& data)
-  {
-    if (data.size() > 0)
-    {
-      ComputeMD5(result, &data[0], data.size());
-    }
-    else
-    {
-      ComputeMD5(result, NULL, 0);
-    }
-  }
-
-
-  void Toolbox::ComputeMD5(std::string& result,
-                           const void* data,
-                           size_t size)
-  {
-    md5_state_s state;
-    md5_init(&state);
-
-    if (size > 0)
-    {
-      md5_append(&state, 
-                 reinterpret_cast<const md5_byte_t*>(data), 
-                 static_cast<int>(size));
-    }
-
-    md5_byte_t actualHash[16];
-    md5_finish(&state, actualHash);
-
-    result.resize(32);
-    for (unsigned int i = 0; i < 16; i++)
-    {
-      result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
-      result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
-    }
-  }
-#endif
-
-
-#if ORTHANC_ENABLE_BASE64 == 1
-  void Toolbox::EncodeBase64(std::string& result, 
-                             const std::string& data)
-  {
-    result = base64_encode(data);
-  }
-
-  void Toolbox::DecodeBase64(std::string& result, 
-                             const std::string& data)
-  {
-    for (size_t i = 0; i < data.length(); i++)
-    {
-      if (!isalnum(data[i]) &&
-          data[i] != '+' &&
-          data[i] != '/' &&
-          data[i] != '=')
-      {
-        // This is not a valid character for a Base64 string
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-
-    result = base64_decode(data);
-  }
-
-
-#  if BOOST_HAS_REGEX == 1
-  bool Toolbox::DecodeDataUriScheme(std::string& mime,
-                                    std::string& content,
-                                    const std::string& source)
-  {
-    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
-                         boost::regex::icase /* case insensitive search */);
-
-    boost::cmatch what;
-    if (regex_match(source.c_str(), what, pattern))
-    {
-      mime = what[1];
-      DecodeBase64(content, what[2]);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-#  endif
-
-
-  void Toolbox::EncodeDataUriScheme(std::string& result,
-                                    const std::string& mime,
-                                    const std::string& content)
-  {
-    result = "data:" + mime + ";base64," + base64_encode(content);
-  }
-
-#endif
-
-
-  static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
-  {
-    switch (sourceEncoding)
-    {
-      case Encoding_Utf8:
-        return "UTF-8";
-
-      case Encoding_Ascii:
-        return "ASCII";
-
-      case Encoding_Latin1:
-        return "ISO-8859-1";
-        break;
-
-      case Encoding_Latin2:
-        return "ISO-8859-2";
-        break;
-
-      case Encoding_Latin3:
-        return "ISO-8859-3";
-        break;
-
-      case Encoding_Latin4:
-        return "ISO-8859-4";
-        break;
-
-      case Encoding_Latin5:
-        return "ISO-8859-9";
-        break;
-
-      case Encoding_Cyrillic:
-        return "ISO-8859-5";
-        break;
-
-      case Encoding_Windows1251:
-        return "WINDOWS-1251";
-        break;
-
-      case Encoding_Arabic:
-        return "ISO-8859-6";
-        break;
-
-      case Encoding_Greek:
-        return "ISO-8859-7";
-        break;
-
-      case Encoding_Hebrew:
-        return "ISO-8859-8";
-        break;
-        
-      case Encoding_Japanese:
-        return "SHIFT-JIS";
-        break;
-
-      case Encoding_Chinese:
-        return "GB18030";
-        break;
-
-      case Encoding_Thai:
-        return "TIS620.2533-0";
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  std::string Toolbox::ConvertToUtf8(const std::string& source,
-                                     Encoding sourceEncoding)
-  {
-    if (sourceEncoding == Encoding_Utf8)
-    {
-      // Already in UTF-8: No conversion is required
-      return source;
-    }
-
-    if (sourceEncoding == Encoding_Ascii)
-    {
-      return ConvertToAscii(source);
-    }
-
-    const char* encoding = GetBoostLocaleEncoding(sourceEncoding);
-
-    try
-    {
-      return boost::locale::conv::to_utf<char>(source, encoding);
-    }
-    catch (std::runtime_error&)
-    {
-      // Bad input string or bad encoding
-      return ConvertToAscii(source);
-    }
-  }
-
-
-  std::string Toolbox::ConvertFromUtf8(const std::string& source,
-                                       Encoding targetEncoding)
-  {
-    if (targetEncoding == Encoding_Utf8)
-    {
-      // Already in UTF-8: No conversion is required
-      return source;
-    }
-
-    if (targetEncoding == Encoding_Ascii)
-    {
-      return ConvertToAscii(source);
-    }
-
-    const char* encoding = GetBoostLocaleEncoding(targetEncoding);
-
-    try
-    {
-      return boost::locale::conv::from_utf<char>(source, encoding);
-    }
-    catch (std::runtime_error&)
-    {
-      // Bad input string or bad encoding
-      return ConvertToAscii(source);
-    }
-  }
-
-
-  bool Toolbox::IsAsciiString(const void* data,
-                              size_t size)
-  {
-    const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
-
-    for (size_t i = 0; i < size; i++, p++)
-    {
-      if (*p > 127 || (*p != 0 && iscntrl(*p)))
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  std::string Toolbox::ConvertToAscii(const std::string& source)
-  {
-    std::string result;
-
-    result.reserve(source.size() + 1);
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
-      {
-        result.push_back(source[i]);
-      }
-    }
-
-    return result;
-  }
-
-
-  void Toolbox::ComputeSHA1(std::string& result,
-                            const void* data,
-                            size_t size)
-  {
-    boost::uuids::detail::sha1 sha1;
-
-    if (size > 0)
-    {
-      sha1.process_bytes(data, size);
-    }
-
-    unsigned int digest[5];
-
-    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
-    assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 
-    
-    sha1.get_digest(digest);
-
-    result.resize(8 * 5 + 4);
-    sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
-            digest[0],
-            digest[1],
-            digest[2],
-            digest[3],
-            digest[4]);
-  }
-
-  void Toolbox::ComputeSHA1(std::string& result,
-                            const std::string& data)
-  {
-    if (data.size() > 0)
-    {
-      ComputeSHA1(result, data.c_str(), data.size());
-    }
-    else
-    {
-      ComputeSHA1(result, NULL, 0);
-    }
-  }
-
-
-  bool Toolbox::IsSHA1(const char* str,
-                       size_t size)
-  {
-    if (size == 0)
-    {
-      return false;
-    }
-
-    const char* start = str;
-    const char* end = str + size;
-
-    // Trim the beginning of the string
-    while (start < end)
-    {
-      if (*start == '\0' ||
-          isspace(*start))
-      {
-        start++;
-      }
-      else
-      {
-        break;
-      }
-    }
-
-    // Trim the trailing of the string
-    while (start < end)
-    {
-      if (*(end - 1) == '\0' ||
-          isspace(*(end - 1)))
-      {
-        end--;
-      }
-      else
-      {
-        break;
-      }
-    }
-
-    if (end - start != 44)
-    {
-      return false;
-    }
-
-    for (unsigned int i = 0; i < 44; i++)
-    {
-      if (i == 8 ||
-          i == 17 ||
-          i == 26 ||
-          i == 35)
-      {
-        if (start[i] != '-')
-          return false;
-      }
-      else
-      {
-        if (!isalnum(start[i]))
-          return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  bool Toolbox::IsSHA1(const std::string& s)
-  {
-    if (s.size() == 0)
-    {
-      return false;
-    }
-    else
-    {
-      return IsSHA1(s.c_str(), s.size());
-    }
-  }
-
-
-  std::string Toolbox::StripSpaces(const std::string& source)
-  {
-    size_t first = 0;
-
-    while (first < source.length() &&
-           isspace(source[first]))
-    {
-      first++;
-    }
-
-    if (first == source.length())
-    {
-      // String containing only spaces
-      return "";
-    }
-
-    size_t last = source.length();
-    while (last > first &&
-           isspace(source[last - 1]))
-    {
-      last--;
-    }          
-    
-    assert(first <= last);
-    return source.substr(first, last - first);
-  }
-
-
-  static char Hex2Dec(char c)
-  {
-    return ((c >= '0' && c <= '9') ? c - '0' :
-            ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10));
-  }
-
-  void Toolbox::UrlDecode(std::string& s)
-  {
-    // http://en.wikipedia.org/wiki/Percent-encoding
-    // http://www.w3schools.com/tags/ref_urlencode.asp
-    // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c
-
-    if (s.size() == 0)
-    {
-      return;
-    }
-
-    size_t source = 0;
-    size_t target = 0;
-
-    while (source < s.size())
-    {
-      if (s[source] == '%' &&
-          source + 2 < s.size() &&
-          isalnum(s[source + 1]) &&
-          isalnum(s[source + 2]))
-      {
-        s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]);
-        source += 3;
-        target += 1;
-      }
-      else
-      {
-        if (s[source] == '+')
-          s[target] = ' ';
-        else
-          s[target] = s[source];
-
-        source++;
-        target++;
-      }
-    }
-
-    s.resize(target);
-  }
-
-
-  Endianness Toolbox::DetectEndianness()
-  {
-    // http://sourceforge.net/p/predef/wiki/Endianness/
-
-    uint8_t buffer[4];
-
-    buffer[0] = 0x00;
-    buffer[1] = 0x01;
-    buffer[2] = 0x02;
-    buffer[3] = 0x03;
-
-    switch (*((uint32_t *)buffer)) 
-    {
-      case 0x00010203: 
-        return Endianness_Big;
-
-      case 0x03020100: 
-        return Endianness_Little;
-        
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-#if BOOST_HAS_REGEX == 1
-  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
-  {
-    // TODO - Speed up this with a regular expression
-
-    std::string result = source;
-
-    // Escape all special characters
-    boost::replace_all(result, "\\", "\\\\");
-    boost::replace_all(result, "^", "\\^");
-    boost::replace_all(result, ".", "\\.");
-    boost::replace_all(result, "$", "\\$");
-    boost::replace_all(result, "|", "\\|");
-    boost::replace_all(result, "(", "\\(");
-    boost::replace_all(result, ")", "\\)");
-    boost::replace_all(result, "[", "\\[");
-    boost::replace_all(result, "]", "\\]");
-    boost::replace_all(result, "+", "\\+");
-    boost::replace_all(result, "/", "\\/");
-    boost::replace_all(result, "{", "\\{");
-    boost::replace_all(result, "}", "\\}");
-
-    // Convert wildcards '*' and '?' to their regex equivalents
-    boost::replace_all(result, "?", ".");
-    boost::replace_all(result, "*", ".*");
-
-    return result;
-  }
-#endif
-
-
-
-  void Toolbox::TokenizeString(std::vector<std::string>& result,
-                               const std::string& value,
-                               char separator)
-  {
-    result.clear();
-
-    std::string currentItem;
-
-    for (size_t i = 0; i < value.size(); i++)
-    {
-      if (value[i] == separator)
-      {
-        result.push_back(currentItem);
-        currentItem.clear();
-      }
-      else
-      {
-        currentItem.push_back(value[i]);
-      }
-    }
-
-    result.push_back(currentItem);
-  }
-
-
-#if ORTHANC_ENABLE_PUGIXML == 1
-  class ChunkedBufferWriter : public pugi::xml_writer
-  {
-  private:
-    ChunkedBuffer buffer_;
-
-  public:
-    virtual void write(const void *data, size_t size)
-    {
-      if (size > 0)
-      {
-        buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
-      }
-    }
-
-    void Flatten(std::string& s)
-    {
-      buffer_.Flatten(s);
-    }
-  };
-
-
-  static void JsonToXmlInternal(pugi::xml_node& target,
-                                const Json::Value& source,
-                                const std::string& arrayElement)
-  {
-    // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030
-
-    switch (source.type())
-    {
-      case Json::nullValue:
-      {
-        target.append_child(pugi::node_pcdata).set_value("null");
-        break;
-      }
-
-      case Json::intValue:
-      {
-        std::string s = boost::lexical_cast<std::string>(source.asInt());
-        target.append_child(pugi::node_pcdata).set_value(s.c_str());
-        break;
-      }
-
-      case Json::uintValue:
-      {
-        std::string s = boost::lexical_cast<std::string>(source.asUInt());
-        target.append_child(pugi::node_pcdata).set_value(s.c_str());
-        break;
-      }
-
-      case Json::realValue:
-      {
-        std::string s = boost::lexical_cast<std::string>(source.asFloat());
-        target.append_child(pugi::node_pcdata).set_value(s.c_str());
-        break;
-      }
-
-      case Json::stringValue:
-      {
-        target.append_child(pugi::node_pcdata).set_value(source.asString().c_str());
-        break;
-      }
-
-      case Json::booleanValue:
-      {
-        target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false");
-        break;
-      }
-
-      case Json::arrayValue:
-      {
-        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
-        {
-          pugi::xml_node node = target.append_child();
-          node.set_name(arrayElement.c_str());
-          JsonToXmlInternal(node, source[i], arrayElement);
-        }
-        break;
-      }
-        
-      case Json::objectValue:
-      {
-        Json::Value::Members members = source.getMemberNames();
-
-        for (size_t i = 0; i < members.size(); i++)
-        {
-          pugi::xml_node node = target.append_child();
-          node.set_name(members[i].c_str());
-          JsonToXmlInternal(node, source[members[i]], arrayElement);          
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void Toolbox::JsonToXml(std::string& target,
-                          const Json::Value& source,
-                          const std::string& rootElement,
-                          const std::string& arrayElement)
-  {
-    pugi::xml_document doc;
-
-    pugi::xml_node n = doc.append_child(rootElement.c_str());
-    JsonToXmlInternal(n, source, arrayElement);
-
-    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
-    decl.append_attribute("version").set_value("1.0");
-    decl.append_attribute("encoding").set_value("utf-8");
-
-    ChunkedBufferWriter writer;
-    doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
-    writer.Flatten(target);
-  }
-
-#endif
-
-
-  
-  bool Toolbox::IsInteger(const std::string& str)
-  {
-    std::string s = StripSpaces(str);
-
-    if (s.size() == 0)
-    {
-      return false;
-    }
-
-    size_t pos = 0;
-    if (s[0] == '-')
-    {
-      if (s.size() == 1)
-      {
-        return false;
-      }
-
-      pos = 1;
-    }
-
-    while (pos < s.size())
-    {
-      if (!isdigit(s[pos]))
-      {
-        return false;
-      }
-
-      pos++;
-    }
-
-    return true;
-  }
-
-
-  void Toolbox::CopyJsonWithoutComments(Json::Value& target,
-                                        const Json::Value& source)
-  {
-    switch (source.type())
-    {
-      case Json::nullValue:
-        target = Json::nullValue;
-        break;
-
-      case Json::intValue:
-        target = source.asInt64();
-        break;
-
-      case Json::uintValue:
-        target = source.asUInt64();
-        break;
-
-      case Json::realValue:
-        target = source.asDouble();
-        break;
-
-      case Json::stringValue:
-        target = source.asString();
-        break;
-
-      case Json::booleanValue:
-        target = source.asBool();
-        break;
-
-      case Json::arrayValue:
-      {
-        target = Json::arrayValue;
-        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
-        {
-          Json::Value& item = target.append(Json::nullValue);
-          CopyJsonWithoutComments(item, source[i]);
-        }
-
-        break;
-      }
-
-      case Json::objectValue:
-      {
-        target = Json::objectValue;
-        Json::Value::Members members = source.getMemberNames();
-        for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
-        {
-          const std::string item = members[i];
-          CopyJsonWithoutComments(target[item], source[item]);
-        }
-
-        break;
-      }
-
-      default:
-        break;
-    }
-  }
-
-
-  bool Toolbox::StartsWith(const std::string& str,
-                           const std::string& prefix)
-  {
-    if (str.size() < prefix.size())
-    {
-      return false;
-    }
-    else
-    {
-      return str.compare(0, prefix.size(), prefix) == 0;
-    }
-  }
-  
-
-  static bool IsUnreservedCharacter(char c)
-  {
-    // This function checks whether "c" is an unserved character
-    // wrt. an URI percent-encoding
-    // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI
-
-    return ((c >= 'A' && c <= 'Z') ||
-            (c >= 'a' && c <= 'z') ||
-            (c >= '0' && c <= '9') ||
-            c == '-' ||
-            c == '_' ||
-            c == '.' ||
-            c == '~');
-  }
-
-  void Toolbox::UriEncode(std::string& target,
-                          const std::string& source)
-  {
-    // Estimate the length of the percent-encoded URI
-    size_t length = 0;
-
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (IsUnreservedCharacter(source[i]))
-      {
-        length += 1;
-      }
-      else
-      {
-        // This character must be percent-encoded
-        length += 3;
-      }
-    }
-
-    target.clear();
-    target.reserve(length);
-
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (IsUnreservedCharacter(source[i]))
-      {
-        target.push_back(source[i]);
-      }
-      else
-      {
-        // This character must be percent-encoded
-        uint8_t byte = static_cast<uint8_t>(source[i]);
-        uint8_t a = byte >> 4;
-        uint8_t b = byte & 0x0f;
-
-        target.push_back('%');
-        target.push_back(a < 10 ? a + '0' : a - 10 + 'A');
-        target.push_back(b < 10 ? b + '0' : b - 10 + 'A');
-      }
-    }
-  }
-
-
-  static bool HasField(const Json::Value& json,
-                       const std::string& key,
-                       Json::ValueType expectedType)
-  {
-    if (json.type() != Json::objectValue ||
-        !json.isMember(key))
-    {
-      return false;
-    }
-    else if (json[key].type() == expectedType)
-    {
-      return true;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-  }
-
-
-  std::string Toolbox::GetJsonStringField(const Json::Value& json,
-                                          const std::string& key,
-                                          const std::string& defaultValue)
-  {
-    if (HasField(json, key, Json::stringValue))
-    {
-      return json[key].asString();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  bool Toolbox::GetJsonBooleanField(const ::Json::Value& json,
-                                    const std::string& key,
-                                    bool defaultValue)
-  {
-    if (HasField(json, key, Json::booleanValue))
-    {
-      return json[key].asBool();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  int Toolbox::GetJsonIntegerField(const ::Json::Value& json,
-                                   const std::string& key,
-                                   int defaultValue)
-  {
-    if (HasField(json, key, Json::intValue))
-    {
-      return json[key].asInt();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  unsigned int Toolbox::GetJsonUnsignedIntegerField(const ::Json::Value& json,
-                                                    const std::string& key,
-                                                    unsigned int defaultValue)
-  {
-    int v = GetJsonIntegerField(json, key, defaultValue);
-
-    if (v < 0)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      return static_cast<unsigned int>(v);
-    }
-  }
-
-
-  bool Toolbox::IsUuid(const std::string& str)
-  {
-    if (str.size() != 36)
-    {
-      return false;
-    }
-
-    for (size_t i = 0; i < str.length(); i++)
-    {
-      if (i == 8 || i == 13 || i == 18 || i == 23)
-      {
-        if (str[i] != '-')
-          return false;
-      }
-      else
-      {
-        if (!isalnum(str[i]))
-          return false;
-      }
-    }
-
-    return true;
-  }
-
-
-  bool Toolbox::StartsWithUuid(const std::string& str)
-  {
-    if (str.size() < 36)
-    {
-      return false;
-    }
-
-    if (str.size() == 36)
-    {
-      return IsUuid(str);
-    }
-
-    assert(str.size() > 36);
-    if (!isspace(str[36]))
-    {
-      return false;
-    }
-
-    return IsUuid(str.substr(0, 36));
-  }
-}
--- a/Resources/Orthanc/Core/Toolbox.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Enumerations.h"
-
-#include <stdint.h>
-#include <vector>
-#include <string>
-#include <json/json.h>
-
-
-#if !defined(ORTHANC_ENABLE_BASE64)
-#  error The macro ORTHANC_ENABLE_BASE64 must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_MD5)
-#  error The macro ORTHANC_ENABLE_MD5 must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_PUGIXML)
-#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
-#endif
-
-#if !defined(BOOST_HAS_REGEX)
-#  error The macro BOOST_HAS_REGEX must be defined
-#endif
-
-
-/**
- * NOTE: GUID vs. UUID
- * The simple answer is: no difference, they are the same thing. Treat
- * them as a 16 byte (128 bits) value that is used as a unique
- * value. In Microsoft-speak they are called GUIDs, but call them
- * UUIDs when not using Microsoft-speak.
- * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
- **/
-
-
-
-namespace Orthanc
-{
-  typedef std::vector<std::string> UriComponents;
-
-  class NullType
-  {
-  };
-
-  namespace Toolbox
-  {
-    void ToUpperCase(std::string& s);  // Inplace version
-
-    void ToLowerCase(std::string& s);  // Inplace version
-
-    void ToUpperCase(std::string& result,
-                     const std::string& source);
-
-    void ToLowerCase(std::string& result,
-                     const std::string& source);
-
-    void SplitUriComponents(UriComponents& components,
-                            const std::string& uri);
-  
-    void TruncateUri(UriComponents& target,
-                     const UriComponents& source,
-                     size_t fromLevel);
-  
-    bool IsChildUri(const UriComponents& baseUri,
-                    const UriComponents& testedUri);
-
-    std::string AutodetectMimeType(const std::string& path);
-
-    std::string FlattenUri(const UriComponents& components,
-                           size_t fromLevel = 0);
-
-#if ORTHANC_ENABLE_MD5 == 1
-    void ComputeMD5(std::string& result,
-                    const std::string& data);
-
-    void ComputeMD5(std::string& result,
-                    const void* data,
-                    size_t size);
-#endif
-
-    void ComputeSHA1(std::string& result,
-                     const std::string& data);
-
-    void ComputeSHA1(std::string& result,
-                     const void* data,
-                     size_t size);
-
-    bool IsSHA1(const char* str,
-                size_t size);
-
-    bool IsSHA1(const std::string& s);
-
-#if ORTHANC_ENABLE_BASE64 == 1
-    void DecodeBase64(std::string& result, 
-                      const std::string& data);
-
-    void EncodeBase64(std::string& result, 
-                      const std::string& data);
-
-#  if BOOST_HAS_REGEX == 1
-    bool DecodeDataUriScheme(std::string& mime,
-                             std::string& content,
-                             const std::string& source);
-#  endif
-
-    void EncodeDataUriScheme(std::string& result,
-                             const std::string& mime,
-                             const std::string& content);
-#endif
-
-    std::string ConvertToUtf8(const std::string& source,
-                              Encoding sourceEncoding);
-
-    std::string ConvertFromUtf8(const std::string& source,
-                                Encoding targetEncoding);
-
-    bool IsAsciiString(const void* data,
-                       size_t size);
-
-    std::string ConvertToAscii(const std::string& source);
-
-    std::string StripSpaces(const std::string& source);
-
-    // In-place percent-decoding for URL
-    void UrlDecode(std::string& s);
-
-    Endianness DetectEndianness();
-
-#if BOOST_HAS_REGEX == 1
-    std::string WildcardToRegularExpression(const std::string& s);
-#endif
-
-    void TokenizeString(std::vector<std::string>& result,
-                        const std::string& source,
-                        char separator);
-
-#if ORTHANC_ENABLE_PUGIXML == 1
-    void JsonToXml(std::string& target,
-                   const Json::Value& source,
-                   const std::string& rootElement = "root",
-                   const std::string& arrayElement = "item");
-#endif
-
-    bool IsInteger(const std::string& str);
-
-    void CopyJsonWithoutComments(Json::Value& target,
-                                 const Json::Value& source);
-
-    bool StartsWith(const std::string& str,
-                    const std::string& prefix);
-
-    void UriEncode(std::string& target,
-                   const std::string& source);
-
-    std::string GetJsonStringField(const ::Json::Value& json,
-                                   const std::string& key,
-                                   const std::string& defaultValue);
-
-    bool GetJsonBooleanField(const ::Json::Value& json,
-                             const std::string& key,
-                             bool defaultValue);
-
-    int GetJsonIntegerField(const ::Json::Value& json,
-                            const std::string& key,
-                            int defaultValue);
-
-    unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json,
-                                             const std::string& key,
-                                             unsigned int defaultValue);
-
-    bool IsUuid(const std::string& str);
-
-    bool StartsWithUuid(const std::string& str);
-  }
-}
--- a/Resources/Orthanc/Core/WebServiceParameters.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,273 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeaders.h"
-#include "WebServiceParameters.h"
-
-#include "../Core/Logging.h"
-#include "../Core/OrthancException.h"
-
-#if ORTHANC_SANDBOXED == 0
-#  include "../Core/SystemToolbox.h"
-#endif
-
-#include <cassert>
-
-namespace Orthanc
-{
-  WebServiceParameters::WebServiceParameters() : 
-    advancedFormat_(false),
-    url_("http://127.0.0.1:8042/"),
-    pkcs11Enabled_(false)
-  {
-  }
-
-
-  void WebServiceParameters::ClearClientCertificate()
-  {
-    certificateFile_.clear();
-    certificateKeyFile_.clear();
-    certificateKeyPassword_.clear();
-  }
-
-
-#if ORTHANC_SANDBOXED == 0
-  void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
-                                                  const std::string& certificateKeyFile,
-                                                  const std::string& certificateKeyPassword)
-  {
-    if (certificateFile.empty())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (!SystemToolbox::IsRegularFile(certificateFile))
-    {
-      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    if (!certificateKeyFile.empty() && 
-        !SystemToolbox::IsRegularFile(certificateKeyFile))
-    {
-      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
-      throw OrthancException(ErrorCode_InexistentFile);
-    }
-
-    advancedFormat_ = true;
-    certificateFile_ = certificateFile;
-    certificateKeyFile_ = certificateKeyFile;
-    certificateKeyPassword_ = certificateKeyPassword;
-  }
-#endif
-
-
-  static void AddTrailingSlash(std::string& url)
-  {
-    if (url.size() != 0 && 
-        url[url.size() - 1] != '/')
-    {
-      url += '/';
-    }
-  }
-
-
-  void WebServiceParameters::FromJsonArray(const Json::Value& peer)
-  {
-    assert(peer.isArray());
-
-    advancedFormat_ = false;
-    pkcs11Enabled_ = false;
-
-    if (peer.size() != 1 && 
-        peer.size() != 3)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    std::string url = peer.get(0u, "").asString();
-    if (url.empty())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    AddTrailingSlash(url);
-    SetUrl(url);
-
-    if (peer.size() == 1)
-    {
-      SetUsername("");
-      SetPassword("");
-    }
-    else if (peer.size() == 3)
-    {
-      SetUsername(peer.get(1u, "").asString());
-      SetPassword(peer.get(2u, "").asString());
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  static std::string GetStringMember(const Json::Value& peer,
-                                     const std::string& key,
-                                     const std::string& defaultValue)
-  {
-    if (!peer.isMember(key))
-    {
-      return defaultValue;
-    }
-    else if (peer[key].type() != Json::stringValue)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      return peer[key].asString();
-    }
-  }
-
-
-  void WebServiceParameters::FromJsonObject(const Json::Value& peer)
-  {
-    assert(peer.isObject());
-    advancedFormat_ = true;
-
-    std::string url = GetStringMember(peer, "Url", "");
-    if (url.empty())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    AddTrailingSlash(url);
-    SetUrl(url);
-
-    SetUsername(GetStringMember(peer, "Username", ""));
-    SetPassword(GetStringMember(peer, "Password", ""));
-
-#if ORTHANC_SANDBOXED == 0
-    if (peer.isMember("CertificateFile"))
-    {
-      SetClientCertificate(GetStringMember(peer, "CertificateFile", ""),
-                           GetStringMember(peer, "CertificateKeyFile", ""),
-                           GetStringMember(peer, "CertificateKeyPassword", ""));
-    }
-#endif
-
-    if (peer.isMember("Pkcs11"))
-    {
-      if (peer["Pkcs11"].type() == Json::booleanValue)
-      {
-        pkcs11Enabled_ = peer["Pkcs11"].asBool();
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-  }
-
-
-  void WebServiceParameters::FromJson(const Json::Value& peer)
-  {
-    try
-    {
-      if (peer.isArray())
-      {
-        FromJsonArray(peer);
-      }
-      else if (peer.isObject())
-      {
-        FromJsonObject(peer);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-    catch (OrthancException&)
-    {
-      throw;
-    }
-    catch (...)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  void WebServiceParameters::ToJson(Json::Value& value) const
-  {
-    if (advancedFormat_)
-    {
-      value = Json::objectValue;
-      value["Url"] = url_;
-
-      if (!username_.empty() ||
-          !password_.empty())
-      {
-        value["Username"] = username_;
-        value["Password"] = password_;
-      }
-
-      if (!certificateFile_.empty())
-      {
-        value["CertificateFile"] = certificateFile_;
-      }
-
-      if (!certificateKeyFile_.empty())
-      {
-        value["CertificateKeyFile"] = certificateKeyFile_;
-      }
-
-      if (!certificateKeyPassword_.empty())
-      {
-        value["CertificateKeyPassword"] = certificateKeyPassword_;
-      }
-    }
-    else
-    {
-      value = Json::arrayValue;
-      value.append(url_);
-
-      if (!username_.empty() ||
-          !password_.empty())
-      {
-        value.append(username_);
-        value.append(password_);
-      }
-    }
-  }
-}
--- a/Resources/Orthanc/Core/WebServiceParameters.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU 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
-
-#include <string>
-#include <json/json.h>
-
-namespace Orthanc
-{
-  class WebServiceParameters
-  {
-  private:
-    bool        advancedFormat_;
-    std::string url_;
-    std::string username_;
-    std::string password_;
-    std::string certificateFile_;
-    std::string certificateKeyFile_;
-    std::string certificateKeyPassword_;
-    bool        pkcs11Enabled_;
-
-    void FromJsonArray(const Json::Value& peer);
-
-    void FromJsonObject(const Json::Value& peer);
-
-  public:
-    WebServiceParameters();
-
-    const std::string& GetUrl() const
-    {
-      return url_;
-    }
-
-    void SetUrl(const std::string& url)
-    {
-      url_ = url;
-    }
-
-    const std::string& GetUsername() const
-    {
-      return username_;
-    }
-
-    void SetUsername(const std::string& username)
-    {
-      username_ = username;
-    }
-    
-    const std::string& GetPassword() const
-    {
-      return password_;
-    }
-
-    void SetPassword(const std::string& password)
-    {
-      password_ = password;
-    }
-
-    void ClearClientCertificate();
-
-#if ORTHANC_SANDBOXED == 0
-    void SetClientCertificate(const std::string& certificateFile,
-                              const std::string& certificateKeyFile,
-                              const std::string& certificateKeyPassword);
-#endif
-
-    const std::string& GetCertificateFile() const
-    {
-      return certificateFile_;
-    }
-
-    const std::string& GetCertificateKeyFile() const
-    {
-      return certificateKeyFile_;
-    }
-
-    const std::string& GetCertificateKeyPassword() const
-    {
-      return certificateKeyPassword_;
-    }
-
-    void SetPkcs11Enabled(bool pkcs11Enabled)
-    {
-      pkcs11Enabled_ = pkcs11Enabled;
-    }
-
-    bool IsPkcs11Enabled() const
-    {
-      return pkcs11Enabled_;
-    }
-
-    void FromJson(const Json::Value& peer);
-
-    void ToJson(Json::Value& value) const;
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomDatasetReader.h"
-
-#include "OrthancPluginException.h"
-
-#include <boost/lexical_cast.hpp>
-
-namespace OrthancPlugins
-{
-  // This function is copied-pasted from "../../../Core/Toolbox.cpp",
-  // in order to avoid the dependency of plugins against the Orthanc core
-  static std::string StripSpaces(const std::string& source)
-  {
-    size_t first = 0;
-
-    while (first < source.length() &&
-           isspace(source[first]))
-    {
-      first++;
-    }
-
-    if (first == source.length())
-    {
-      // String containing only spaces
-      return "";
-    }
-
-    size_t last = source.length();
-    while (last > first &&
-           isspace(source[last - 1]))
-    {
-      last--;
-    }          
-    
-    assert(first <= last);
-    return source.substr(first, last - first);
-  }
-
-
-  DicomDatasetReader::DicomDatasetReader(const IDicomDataset& dataset) :
-    dataset_(dataset)
-  {
-  }
-  
-
-  std::string DicomDatasetReader::GetStringValue(const DicomPath& path,
-                                                 const std::string& defaultValue) const
-  {
-    std::string s;
-    if (dataset_.GetStringValue(s, path))
-    {
-      return s;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  std::string DicomDatasetReader::GetMandatoryStringValue(const DicomPath& path) const
-  {
-    std::string s;
-    if (dataset_.GetStringValue(s, path))
-    {
-      return s;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InexistentTag);
-    }
-  }
-
-
-  template <typename T>
-  static bool GetValueInternal(T& target,
-                               const IDicomDataset& dataset,
-                               const DicomPath& path)
-  {
-    try
-    {
-      std::string s;
-
-      if (dataset.GetStringValue(s, path))
-      {
-        target = boost::lexical_cast<T>(StripSpaces(s));
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);        
-    }
-  }
-
-
-  bool DicomDatasetReader::GetIntegerValue(int& target,
-                                           const DicomPath& path) const
-  {
-    return GetValueInternal<int>(target, dataset_, path);
-  }
-
-
-  bool DicomDatasetReader::GetUnsignedIntegerValue(unsigned int& target,
-                                                   const DicomPath& path) const
-  {
-    int value;
-
-    if (!GetIntegerValue(value, path))
-    {
-      return false;
-    }
-    else if (value >= 0)
-    {
-      target = static_cast<unsigned int>(value);
-      return true;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  bool DicomDatasetReader::GetFloatValue(float& target,
-                                         const DicomPath& path) const
-  {
-    return GetValueInternal<float>(target, dataset_, path);
-  }
-
-
-  bool DicomDatasetReader::GetDoubleValue(double& target,
-                                          const DicomPath& path) const
-  {
-    return GetValueInternal<double>(target, dataset_, path);
-  }
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IDicomDataset.h"
-
-#include <memory>
-#include <vector>
-
-namespace OrthancPlugins
-{
-  class DicomDatasetReader : public boost::noncopyable
-  {
-  private:
-    const IDicomDataset&  dataset_;
-
-  public:
-    DicomDatasetReader(const IDicomDataset& dataset);
-
-    const IDicomDataset& GetDataset() const
-    {
-      return dataset_;
-    }
-
-    std::string GetStringValue(const DicomPath& path,
-                               const std::string& defaultValue) const;
-
-    std::string GetMandatoryStringValue(const DicomPath& path) const;
-
-    bool GetIntegerValue(int& target,
-                         const DicomPath& path) const;
-
-    bool GetUnsignedIntegerValue(unsigned int& target,
-                                 const DicomPath& path) const;
-
-    bool GetFloatValue(float& target,
-                       const DicomPath& path) const;
-
-    bool GetDoubleValue(double& target,
-                        const DicomPath& path) const;
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomPath.h"
-
-#include "OrthancPluginException.h"
-
-namespace OrthancPlugins
-{
-  const DicomPath::Prefix& DicomPath::GetPrefixItem(size_t depth) const
-  {
-    if (depth >= prefix_.size())
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-    else
-    {
-      return prefix_[depth];
-    }
-  }
-
-
-  DicomPath::DicomPath(const DicomTag& sequence,
-                       size_t index,
-                       const DicomTag& tag) :
-    finalTag_(tag)
-  {
-    AddToPrefix(sequence, index);
-  }
-
-
-  DicomPath::DicomPath(const DicomTag& sequence1,
-                       size_t index1,
-                       const DicomTag& sequence2,
-                       size_t index2,
-                       const DicomTag& tag) :
-    finalTag_(tag)
-  {
-    AddToPrefix(sequence1, index1);
-    AddToPrefix(sequence2, index2);
-  }
-
-
-  DicomPath::DicomPath(const DicomTag& sequence1,
-                       size_t index1,
-                       const DicomTag& sequence2,
-                       size_t index2,
-                       const DicomTag& sequence3,
-                       size_t index3,
-                       const DicomTag& tag) :
-    finalTag_(tag)
-  {
-    AddToPrefix(sequence1, index1);
-    AddToPrefix(sequence2, index2);
-    AddToPrefix(sequence3, index3);
-  }
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/DicomPath.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomTag.h"
-
-#include <vector>
-#include <stddef.h>
-
-namespace OrthancPlugins
-{
-  class DicomPath
-  {
-  private:
-    typedef std::pair<DicomTag, size_t>  Prefix;
-
-    std::vector<Prefix>  prefix_;
-    DicomTag             finalTag_;
-
-    const Prefix& GetPrefixItem(size_t depth) const;
-
-  public:
-    DicomPath(const DicomTag& finalTag) :
-    finalTag_(finalTag)
-    {
-    }
-
-    DicomPath(const DicomTag& sequence,
-              size_t index,
-              const DicomTag& tag);
-
-    DicomPath(const DicomTag& sequence1,
-              size_t index1,
-              const DicomTag& sequence2,
-              size_t index2,
-              const DicomTag& tag);
-
-    DicomPath(const DicomTag& sequence1,
-              size_t index1,
-              const DicomTag& sequence2,
-              size_t index2,
-              const DicomTag& sequence3,
-              size_t index3,
-              const DicomTag& tag);
-
-    void AddToPrefix(const DicomTag& tag,
-                     size_t position)
-    {
-      prefix_.push_back(std::make_pair(tag, position));
-    }
-
-    size_t GetPrefixLength() const
-    {
-      return prefix_.size();
-    }
-    
-    DicomTag GetPrefixTag(size_t depth) const
-    {
-      return GetPrefixItem(depth).first;
-    }
-
-    size_t GetPrefixIndex(size_t depth) const
-    {
-      return GetPrefixItem(depth).second;
-    }
-    
-    const DicomTag& GetFinalTag() const
-    {
-      return finalTag_;
-    }
-
-    void SetFinalTag(const DicomTag& tag)
-    {
-      finalTag_ = tag;
-    }
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/DicomTag.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomTag.h"
-
-#include "OrthancPluginException.h"
-
-namespace OrthancPlugins
-{
-  const char* DicomTag::GetName() const
-  {
-    if (*this == DICOM_TAG_BITS_STORED)
-    {
-      return "BitsStored";
-    }
-    else if (*this == DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX)
-    {
-      return "ColumnPositionInTotalImagePixelMatrix";
-    }
-    else if (*this == DICOM_TAG_COLUMNS)
-    {
-      return "Columns";
-    }
-    else if (*this == DICOM_TAG_MODALITY)
-    {
-      return "Modality";
-    }
-    else if (*this == DICOM_TAG_NUMBER_OF_FRAMES)
-    {
-      return "NumberOfFrames";
-    }
-    else if (*this == DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE)
-    {
-      return "PerFrameFunctionalGroupsSequence";
-    }
-    else if (*this == DICOM_TAG_PHOTOMETRIC_INTERPRETATION)
-    {
-      return "PhotometricInterpretation";
-    }
-    else if (*this == DICOM_TAG_PIXEL_REPRESENTATION)
-    {
-      return "PixelRepresentation";
-    }
-    else if (*this == DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE)
-    {
-      return "PlanePositionSlideSequence";
-    }
-    else if (*this == DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX)
-    {
-      return "RowPositionInTotalImagePixelMatrix";
-    }
-    else if (*this == DICOM_TAG_ROWS)
-    {
-      return "Rows";
-    }
-    else if (*this == DICOM_TAG_SOP_CLASS_UID)
-    {
-      return "SOPClassUID";
-    }
-    else if (*this == DICOM_TAG_SAMPLES_PER_PIXEL)
-    {
-      return "SamplesPerPixel";
-    }
-    else if (*this == DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS)
-    {
-      return "TotalPixelMatrixColumns";
-    }
-    else if (*this == DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS)
-    {
-      return "TotalPixelMatrixRows";
-    }
-    else if (*this == DICOM_TAG_TRANSFER_SYNTAX_UID)
-    {
-      return "TransferSyntaxUID";
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotImplemented);
-    }
-  }
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/DicomTag.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <stdint.h>
-
-namespace OrthancPlugins
-{
-  class DicomTag
-  {
-  private:
-    uint16_t  group_;
-    uint16_t  element_;
-
-    DicomTag();  // Forbidden
-
-  public:
-    DicomTag(uint16_t group,
-             uint16_t element) :
-      group_(group),
-      element_(element)
-    {
-    }
-
-    uint16_t GetGroup() const
-    {
-      return group_;
-    }
-
-    uint16_t GetElement() const
-    {
-      return element_;
-    }
-
-    const char* GetName() const;
-
-    bool operator== (const DicomTag& other) const
-    {
-      return group_ == other.group_ && element_ == other.element_;
-    }
-
-    bool operator!= (const DicomTag& other) const
-    {
-      return !(*this == other);
-    }
-  };
-
-
-  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
-  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
-  static const DicomTag DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021e);
-  static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
-  static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
-  static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060);
-  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
-  static const DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE(0x5200, 0x9230);
-  static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
-  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
-  static const DicomTag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030);
-  static const DicomTag DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE(0x0048, 0x021a);
-  static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052);
-  static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053);
-  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
-  static const DicomTag DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021f);
-  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
-  static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050);
-  static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
-  static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS(0x0048, 0x0006);
-  static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS(0x0048, 0x0007);
-  static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010);
-  static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
-  static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "FullOrthancDataset.h"
-
-#include "OrthancPluginException.h"
-
-#include <stdio.h>
-#include <cassert>
-
-namespace OrthancPlugins
-{
-  static const Json::Value* AccessTag(const Json::Value& dataset,
-                                      const DicomTag& tag) 
-  {
-    if (dataset.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    char name[16];
-    sprintf(name, "%04x,%04x", tag.GetGroup(), tag.GetElement());
-
-    if (!dataset.isMember(name))
-    {
-      return NULL;
-    }
-
-    const Json::Value& value = dataset[name];
-    if (value.type() != Json::objectValue ||
-        !value.isMember("Name") ||
-        !value.isMember("Type") ||
-        !value.isMember("Value") ||
-        value["Name"].type() != Json::stringValue ||
-        value["Type"].type() != Json::stringValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    return &value;
-  }
-
-
-  static const Json::Value& GetSequenceContent(const Json::Value& sequence)
-  {
-    assert(sequence.type() == Json::objectValue);
-    assert(sequence.isMember("Type"));
-    assert(sequence.isMember("Value"));
-
-    const Json::Value& value = sequence["Value"];
-      
-    if (sequence["Type"].asString() != "Sequence" ||
-        value.type() != Json::arrayValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-    else
-    {
-      return value;
-    }
-  }
-
-
-  static bool GetStringInternal(std::string& result,
-                                const Json::Value& tag)
-  {
-    assert(tag.type() == Json::objectValue);
-    assert(tag.isMember("Type"));
-    assert(tag.isMember("Value"));
-
-    const Json::Value& value = tag["Value"];
-      
-    if (tag["Type"].asString() != "String" ||
-        value.type() != Json::stringValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-    else
-    {
-      result = value.asString();
-      return true;
-    }
-  }
-
-
-  const Json::Value* FullOrthancDataset::LookupPath(const DicomPath& path) const
-  {
-    const Json::Value* content = &root_;
-                                  
-    for (unsigned int depth = 0; depth < path.GetPrefixLength(); depth++)
-    {
-      const Json::Value* sequence = AccessTag(*content, path.GetPrefixTag(depth));
-      if (sequence == NULL)
-      {
-        return NULL;
-      }
-
-      const Json::Value& nextContent = GetSequenceContent(*sequence);
-
-      size_t index = path.GetPrefixIndex(depth);
-      if (index >= nextContent.size())
-      {
-        return NULL;
-      }
-      else
-      {
-        content = &nextContent[static_cast<Json::Value::ArrayIndex>(index)];
-      }
-    }
-
-    return AccessTag(*content, path.GetFinalTag());
-  }
-
-
-  void FullOrthancDataset::CheckRoot() const
-  {
-    if (root_.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  FullOrthancDataset::FullOrthancDataset(IOrthancConnection& orthanc,
-                                         const std::string& uri)
-  {
-    IOrthancConnection::RestApiGet(root_, orthanc, uri);
-    CheckRoot();
-  }
-
-
-  FullOrthancDataset::FullOrthancDataset(const std::string& content)
-  {
-    IOrthancConnection::ParseJson(root_, content);
-    CheckRoot();
-  }
-
-
-  bool FullOrthancDataset::GetStringValue(std::string& result,
-                                          const DicomPath& path) const
-  {
-    const Json::Value* value = LookupPath(path);
-
-    if (value == NULL)
-    {
-      return false;
-    }
-    else
-    {
-      return GetStringInternal(result, *value);
-    }
-  }
-
-
-  bool FullOrthancDataset::GetSequenceSize(size_t& size,
-                                           const DicomPath& path) const
-  {
-    const Json::Value* sequence = LookupPath(path);
-
-    if (sequence == NULL)
-    {
-      return false;
-    }
-    else
-    {
-      size = GetSequenceContent(*sequence).size();
-      return true;
-    }
-  }
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IOrthancConnection.h"
-#include "IDicomDataset.h"
-
-#include <json/value.h>
-
-namespace OrthancPlugins
-{
-  class FullOrthancDataset : public IDicomDataset
-  {
-  private:
-    Json::Value   root_;
-
-    const Json::Value* LookupPath(const DicomPath& path) const;
-
-    void CheckRoot() const;
-
-  public:
-    FullOrthancDataset(IOrthancConnection& orthanc,
-                       const std::string& uri);
-
-    FullOrthancDataset(const std::string& content);
-
-    virtual bool GetStringValue(std::string& result,
-                                const DicomPath& path) const;
-
-    virtual bool GetSequenceSize(size_t& size,
-                                 const DicomPath& path) const;
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomPath.h"
-
-#include <boost/noncopyable.hpp>
-#include <string>
-
-namespace OrthancPlugins
-{
-  class IDicomDataset : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomDataset()
-    {
-    }
-
-    virtual bool GetStringValue(std::string& result,
-                                const DicomPath& path) const = 0;
-
-    virtual bool GetSequenceSize(size_t& size,
-                                 const DicomPath& path) const = 0;
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "IOrthancConnection.h"
-
-#include "OrthancPluginException.h"
-
-#include <json/reader.h>
-
-namespace OrthancPlugins
-{
-  void IOrthancConnection::ParseJson(Json::Value& result,
-                                     const std::string& content)
-  {
-    Json::Reader reader;
-    
-    if (!reader.parse(content, result))
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  void IOrthancConnection::RestApiGet(Json::Value& result,
-                                      IOrthancConnection& orthanc,
-                                      const std::string& uri)
-  {
-    std::string content;
-    orthanc.RestApiGet(content, uri);
-    ParseJson(result, content);
-  }
-
-
-  void IOrthancConnection::RestApiPost(Json::Value& result,
-                                       IOrthancConnection& orthanc,
-                                       const std::string& uri,
-                                       const std::string& body)
-  {
-    std::string content;
-    orthanc.RestApiPost(content, uri, body);
-    ParseJson(result, content);
-  }
-
-
-  void IOrthancConnection::RestApiPut(Json::Value& result,
-                                      IOrthancConnection& orthanc,
-                                      const std::string& uri,
-                                      const std::string& body)
-  {
-    std::string content;
-    orthanc.RestApiPut(content, uri, body);
-    ParseJson(result, content);
-  }
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomPath.h"
-
-#include <boost/noncopyable.hpp>
-#include <string>
-#include <json/value.h>
-
-namespace OrthancPlugins
-{
-  class IOrthancConnection : public boost::noncopyable
-  {
-  public:
-    virtual ~IOrthancConnection()
-    {
-    }
-
-    virtual void RestApiGet(std::string& result,
-                            const std::string& uri) = 0;
-
-    virtual void RestApiPost(std::string& result,
-                             const std::string& uri,
-                             const std::string& body) = 0;
-
-    virtual void RestApiPut(std::string& result,
-                            const std::string& uri,
-                            const std::string& body) = 0;
-
-    virtual void RestApiDelete(const std::string& uri) = 0;
-
-    static void ParseJson(Json::Value& result,
-                          const std::string& content);
-
-    static void RestApiGet(Json::Value& result,
-                           IOrthancConnection& orthanc,
-                           const std::string& uri);
-
-    static void RestApiPost(Json::Value& result,
-                            IOrthancConnection& orthanc,
-                            const std::string& uri,
-                            const std::string& body);
-
-    static void RestApiPut(Json::Value& result,
-                           IOrthancConnection& orthanc,
-                           const std::string& uri,
-                           const std::string& body);
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrthancHttpConnection.h"
-
-namespace OrthancPlugins
-{
-  void OrthancHttpConnection::Setup()
-  {
-    url_ = client_.GetUrl();
-
-    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
-    client_.SetRedirectionFollowed(false);  
-  }
-
-
-  OrthancHttpConnection::OrthancHttpConnection() :
-    client_(Orthanc::WebServiceParameters(), "")
-  {
-    Setup();
-  }
-
-
-  OrthancHttpConnection::OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters) :
-    client_(parameters, "")
-  {
-    Setup();
-  }
-
-
-  void OrthancHttpConnection::RestApiGet(std::string& result,
-                                         const std::string& uri)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    client_.SetMethod(Orthanc::HttpMethod_Get);
-    client_.SetUrl(url_ + uri);
-    client_.ApplyAndThrowException(result);
-  }
-
-
-  void OrthancHttpConnection::RestApiPost(std::string& result,
-                                          const std::string& uri,
-                                          const std::string& body)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    client_.SetMethod(Orthanc::HttpMethod_Post);
-    client_.SetUrl(url_ + uri);
-    client_.SetBody(body);
-    client_.ApplyAndThrowException(result);
-  }
-
-
-  void OrthancHttpConnection::RestApiPut(std::string& result,
-                                         const std::string& uri,
-                                         const std::string& body)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    client_.SetMethod(Orthanc::HttpMethod_Put);
-    client_.SetUrl(url_ + uri);
-    client_.SetBody(body);
-    client_.ApplyAndThrowException(result);
-  }
-
-
-  void OrthancHttpConnection::RestApiDelete(const std::string& uri)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    std::string result;
-
-    client_.SetMethod(Orthanc::HttpMethod_Delete);
-    client_.SetUrl(url_ + uri);
-    client_.ApplyAndThrowException(result);
-  }
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IOrthancConnection.h"
-
-#if HAS_ORTHANC_EXCEPTION != 1
-#  error The macro HAS_ORTHANC_EXCEPTION must be set to 1 if using this header
-#endif
-
-#include "../../../Core/HttpClient.h"
-
-#include <boost/thread/mutex.hpp>
-
-namespace OrthancPlugins
-{
-  // This class is thread-safe
-  class OrthancHttpConnection : public IOrthancConnection
-  {
-  private:
-    boost::mutex         mutex_;
-    Orthanc::HttpClient  client_;
-    std::string          url_;
-
-    void Setup();
-
-  public:
-    OrthancHttpConnection();
-
-    OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters);
-
-    virtual void RestApiGet(std::string& result,
-                            const std::string& uri);
-
-    virtual void RestApiPost(std::string& result,
-                             const std::string& uri,
-                             const std::string& body);
-
-    virtual void RestApiPut(std::string& result,
-                            const std::string& uri,
-                            const std::string& body);
-
-    virtual void RestApiDelete(const std::string& uri);
-  };
-}
--- a/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if !defined(HAS_ORTHANC_EXCEPTION)
-#  error The macro HAS_ORTHANC_EXCEPTION must be defined
-#endif
-
-
-#if HAS_ORTHANC_EXCEPTION == 1
-#  include "../../../Core/OrthancException.h"
-#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
-#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
-#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
-#else
-#  include <orthanc/OrthancCPlugin.h>
-#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
-#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
-#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
-#endif
-
-
-#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
-  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
-
-
-#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
-  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
-                                                  
-
-#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
-  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
-  {                                                                 \
-    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
-  }
-
-
-namespace OrthancPlugins
-{
-#if HAS_ORTHANC_EXCEPTION == 0
-  class PluginException
-  {
-  private:
-    OrthancPluginErrorCode  code_;
-
-  public:
-    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
-    {
-    }
-
-    OrthancPluginErrorCode GetErrorCode() const
-    {
-      return code_;
-    }
-
-    const char* What(OrthancPluginContext* context) const
-    {
-      const char* description = OrthancPluginGetErrorDescription(context, code_);
-      if (description)
-      {
-        return description;
-      }
-      else
-      {
-        return "No description available";
-      }
-    }
-  };
-#endif
-}
--- a/Resources/Orthanc/README.txt	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-This folder contains an excerpt of the source code of Orthanc. It is
-automatically generated using the "../../Resources/SyncOrthancFolder.py"
-script.
--- a/Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED")
-set(AUTOGENERATED_SOURCES)
-
-file(MAKE_DIRECTORY ${AUTOGENERATED_DIR})
-include_directories(${AUTOGENERATED_DIR})
-
-macro(EmbedResources)
-  # Convert a semicolon separated list to a whitespace separated string
-  set(SCRIPT_OPTIONS)
-  set(SCRIPT_ARGUMENTS)
-  set(DEPENDENCIES)
-  set(IS_PATH_NAME false)
-
-  # Loop over the arguments of the function
-  foreach(arg ${ARGN})
-    # Extract the first character of the argument
-    string(SUBSTRING "${arg}" 0 1 FIRST_CHAR)
-    if (${FIRST_CHAR} STREQUAL "-")
-      # If the argument starts with a dash "-", this is an option to
-      # EmbedResources.py
-      list(APPEND SCRIPT_OPTIONS ${arg})
-    else()
-      if (${IS_PATH_NAME})
-        list(APPEND SCRIPT_ARGUMENTS "${arg}")
-        list(APPEND DEPENDENCIES "${arg}")
-        set(IS_PATH_NAME false)
-      else()
-        list(APPEND SCRIPT_ARGUMENTS "${arg}")
-        set(IS_PATH_NAME true)
-      endif()
-    endif()
-  endforeach()
-
-  set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources")
-  add_custom_command(
-    OUTPUT
-    "${TARGET_BASE}.h"
-    "${TARGET_BASE}.cpp"
-    COMMAND 
-    ${PYTHON_EXECUTABLE}
-    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
-    ${SCRIPT_OPTIONS}
-    "${AUTOGENERATED_DIR}/EmbeddedResources"
-    ${SCRIPT_ARGUMENTS}
-    DEPENDS
-    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
-    ${DEPENDENCIES}
-    )
-
-  list(APPEND AUTOGENERATED_SOURCES
-    "${AUTOGENERATED_DIR}/EmbeddedResources.cpp"
-    ) 
-endmacro()
--- a/Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
-  set(BOOST_STATIC 1)
-else()
-  include(FindBoost)
-
-  set(BOOST_STATIC 0)
-  #set(Boost_DEBUG 1)
-  #set(Boost_USE_STATIC_LIBS ON)
-
-  find_package(Boost
-    COMPONENTS filesystem thread system date_time regex locale ${ORTHANC_BOOST_COMPONENTS})
-
-  if (NOT Boost_FOUND)
-    message(FATAL_ERROR "Unable to locate Boost on this system")
-  endif()
-
-  # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem
-  # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm
-  if (${Boost_VERSION} LESS 104400)
-    add_definitions(
-      -DBOOST_HAS_FILESYSTEM_V3=0
-      )
-  else()
-    add_definitions(
-      -DBOOST_HAS_FILESYSTEM_V3=1
-      -DBOOST_FILESYSTEM_VERSION=3
-      )
-  endif()
-
-  #if (${Boost_VERSION} LESS 104800)
-  # boost::locale is only available from 1.48.00
-  #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version")
-  #  set(BOOST_STATIC 1)
-  #endif()
-
-  include_directories(${Boost_INCLUDE_DIRS})
-  link_libraries(${Boost_LIBRARIES})
-endif()
-
-
-if (BOOST_STATIC)
-  # Parameters for Boost 1.60.0
-  set(BOOST_NAME boost_1_60_0)
-  set(BOOST_BCP_SUFFIX bcpdigest-1.0.1)
-  set(BOOST_MD5 "a789f8ec2056ad1c2d5f0cb64687cc7b")
-  set(BOOST_URL "http://www.orthanc-server.com/downloads/third-party/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
-  set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") 
-  set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
-
-  DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}")
-
-  set(BOOST_SOURCES)
-
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/atomic/src/lockpool.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp
-      )
-    add_definitions(
-      -DBOOST_LOCALE_WITH_ICONV=1
-      -DBOOST_LOCALE_NO_WINAPI_BACKEND=1
-      -DBOOST_LOCALE_NO_STD_BACKEND=1
-      )
-
-    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
-      add_definitions(-DBOOST_HAS_SCHED_YIELD=1)
-    endif()
-
-  elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
-      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp
-      )
-
-    # Starting with release 0.8.2, Orthanc statically links against
-    # libiconv, even on Windows. Indeed, the "WCONV" library of
-    # Windows XP seems not to support properly several codepages
-    # (notably "Latin3", "Hebrew", and "Arabic").
-
-    if (USE_BOOST_ICONV)
-      include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake)
-    else()
-      add_definitions(-DBOOST_LOCALE_WITH_WCONV=1)
-    endif()
-
-    add_definitions(
-      -DBOOST_LOCALE_NO_POSIX_BACKEND=1
-      -DBOOST_LOCALE_NO_STD_BACKEND=1
-      )
-
-  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
-    add_definitions(
-      -DBOOST_LOCALE_NO_POSIX_BACKEND=1
-      -DBOOST_LOCALE_NO_STD_BACKEND=1
-      )
-
-  else()
-    message(FATAL_ERROR "Support your platform here")
-  endif()
-
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
-      )
-  endif()
-
-  aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES)
-
-  list(APPEND BOOST_SOURCES
-    ${BOOST_REGEX_SOURCES}
-    ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
-    ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
-    )
-
-  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
-    list(APPEND BOOST_SOURCES
-      ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
-      )
-  endif()
-
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
-    # boost::filesystem is not available on PNaCl
-    add_definitions(
-      -DBOOST_HAS_FILESYSTEM_V3=0
-      -D__INTEGRITY=1
-      )
-  else()
-    add_definitions(
-      -DBOOST_HAS_FILESYSTEM_V3=1
-      )
-    list(APPEND BOOST_SOURCES
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
-      )
-  endif()
-
-  if (USE_BOOST_LOCALE_BACKENDS)
-    if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
-      list(APPEND BOOST_SOURCES
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/converter.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/numeric.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp
-        )
-    elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-      list(APPEND BOOST_SOURCES
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/lcid.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/numeric.cpp
-        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp
-        )
-    else()
-      message(FATAL_ERROR "Support your platform here")
-    endif()
-
-    list(APPEND BOOST_SOURCES
-      ${BOOST_REGEX_SOURCES}
-      ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
-      ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
-
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
-      ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
-
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/generator.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/date_time.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/formatting.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/ids.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/localization_backend.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/message.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/mo_lambda.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/codecvt_converter.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/default_locale.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/gregorian.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/info.cpp
-      ${BOOST_SOURCES_DIR}/libs/locale/src/util/locale_data.cpp
-      )        
-  endif()
-
-  add_definitions(
-    # Static build of Boost
-    -DBOOST_ALL_NO_LIB 
-    -DBOOST_ALL_NOLIB 
-    -DBOOST_DATE_TIME_NO_LIB 
-    -DBOOST_THREAD_BUILD_LIB
-    -DBOOST_PROGRAM_OPTIONS_NO_LIB
-    -DBOOST_REGEX_NO_LIB
-    -DBOOST_SYSTEM_NO_LIB
-    -DBOOST_LOCALE_NO_LIB
-    -DBOOST_HAS_LOCALE=1
-    )
-
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    add_definitions(-isystem ${BOOST_SOURCES_DIR})
-  endif()
-
-  include_directories(
-    ${BOOST_SOURCES_DIR}
-    )
-
-  source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
-
-else()
-  add_definitions(
-    -DBOOST_HAS_LOCALE=1
-    )
-endif()
-
-
-add_definitions(
-  -DBOOST_HAS_DATE_TIME=1
-  -DBOOST_HAS_REGEX=1
-  )
--- a/Resources/Orthanc/Resources/CMake/Compiler.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-# This file sets all the compiler-related flags
-
-if (CMAKE_CROSSCOMPILING)
-  # Cross-compilation necessarily implies standalone and static build
-  SET(STATIC_BUILD ON)
-  SET(STANDALONE_BUILD ON)
-endif()
-
-if (CMAKE_COMPILER_IS_GNUCXX)
-  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration")  
-  # --std=c99 makes libcurl not to compile
-  # -pedantic gives a lot of warnings on OpenSSL 
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros")
-
-  if (CMAKE_CROSSCOMPILING)
-    # http://stackoverflow.com/a/3543845/881731
-    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>")
-  endif()
-
-elseif (MSVC)
-  # Use static runtime under Visual Studio
-  # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
-  # http://stackoverflow.com/a/6510446
-  foreach(flag_var
-    CMAKE_C_FLAGS_DEBUG
-    CMAKE_CXX_FLAGS_DEBUG
-    CMAKE_C_FLAGS_RELEASE 
-    CMAKE_CXX_FLAGS_RELEASE
-    CMAKE_C_FLAGS_MINSIZEREL 
-    CMAKE_CXX_FLAGS_MINSIZEREL 
-    CMAKE_C_FLAGS_RELWITHDEBINFO 
-    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
-    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
-  endforeach(flag_var)
-
-  # Add /Zm256 compiler option to Visual Studio to fix PCH errors
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256")
-
-  add_definitions(
-    -D_CRT_SECURE_NO_WARNINGS=1
-    -D_CRT_SECURE_NO_DEPRECATE=1
-    )
-
-  if (MSVC_VERSION LESS 1600)
-    # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >=
-    # 1600), Microsoft ships a standard-compliant <stdint.h>
-    # header. For earlier versions of Visual Studio, give access to a
-    # compatibility header.
-    # http://stackoverflow.com/a/70630/881731
-    # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links
-    include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
-  endif()
-
-  link_libraries(netapi32)
-endif()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
-
-  if (NOT DEFINED ENABLE_PLUGINS_VERSION_SCRIPT OR 
-      ENABLE_PLUGINS_VERSION_SCRIPT)
-    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
-  endif()
-
-  # Remove the "-rdynamic" option
-  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
-  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
-  link_libraries(uuid pthread rt)
-
-  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
-    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed")
-    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed")
-    add_definitions(
-      -D_LARGEFILE64_SOURCE=1 
-      -D_FILE_OFFSET_BITS=64
-      )
-    link_libraries(dl)
-  endif()
-
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-  if (NOT HAVE_UUID_H)
-    message(FATAL_ERROR "Please install the uuid-dev package")
-  endif()
-
-elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  if (MSVC)
-    message("MSVC compiler version = " ${MSVC_VERSION} "\n")
-    # Starting Visual Studio 2013 (version 1800), it is not possible
-    # to target Windows XP anymore
-    if (MSVC_VERSION LESS 1800)
-      add_definitions(
-        -DWINVER=0x0501
-        -D_WIN32_WINNT=0x0501
-        )
-    endif()
-  else()
-    add_definitions(
-      -DWINVER=0x0501
-      -D_WIN32_WINNT=0x0501
-      )
-  endif()
-
-  add_definitions(
-    -D_CRT_SECURE_NO_WARNINGS=1
-    )
-  link_libraries(rpcrt4 ws2_32)
-
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    # Some additional C/C++ compiler flags for MinGW
-    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
-
-    # This is a patch for MinGW64
-    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
-    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
-
-    CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
-    if (HAVE_WIN_PTHREAD)
-      # This line is necessary to compile with recent versions of MinGW,
-      # otherwise "libwinpthread-1.dll" is not statically linked.
-      SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
-      add_definitions(-DHAVE_WIN_PTHREAD=1)
-    else()
-      add_definitions(-DHAVE_WIN_PTHREAD=0)
-    endif()
-  endif()
-
-elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list")
-
-  add_definitions(
-    -D_XOPEN_SOURCE=1
-    )
-  link_libraries(iconv)
-
-  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
-  if (NOT HAVE_UUID_H)
-    message(FATAL_ERROR "Please install the uuid-dev package")
-  endif()
-
-endif()
-
-
-if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}")
-endif()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-  # In FreeBSD, the "/usr/local/" folder contains the ports and need to be imported
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
-  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib")
-endif()
-
-
-if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
-  if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
-    message(WARNING "Enabling profiling on a non-debug build will not produce full information")
-  endif()
-
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
-    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pg")
-    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
-  else()
-    message(FATAL_ERROR "Don't know how to enable profiling on your configuration")
-  endif()
-endif()
-
-
-if (STATIC_BUILD)
-  add_definitions(-DORTHANC_STATIC=1)
-else()
-  add_definitions(-DORTHANC_STATIC=0)
-endif()
--- a/Resources/Orthanc/Resources/CMake/DownloadPackage.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-macro(GetUrlFilename TargetVariable Url)
-  string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}")
-endmacro()
-
-
-macro(GetUrlExtension TargetVariable Url)
-  #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}")
-  string(REGEX REPLACE "^.*\\." "" TMP "${Url}")
-  string(TOLOWER "${TMP}" "${TargetVariable}")
-endmacro()
-
-
-
-##
-## Setup the patch command-line tool
-##
-
-if (NOT ORTHANC_DISABLE_PATCH)
-  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-    set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
-    if (NOT EXISTS ${PATCH_EXECUTABLE})
-      message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
-    endif()
-
-  else ()
-    find_program(PATCH_EXECUTABLE patch)
-    if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
-      message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
-    endif()
-  endif()
-endif()
-
-
-
-##
-## Check the existence of the required decompression tools
-##
-
-if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-  find_program(ZIP_EXECUTABLE 7z 
-    PATHS 
-    "$ENV{ProgramFiles}/7-Zip"
-    "$ENV{ProgramW6432}/7-Zip"
-    )
-
-  if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
-  endif()
-
-else()
-  find_program(UNZIP_EXECUTABLE unzip)
-  if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the 'unzip' package")
-  endif()
-
-  find_program(TAR_EXECUTABLE tar)
-  if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the 'tar' package")
-  endif()
-endif()
-
-
-macro(DownloadPackage MD5 Url TargetDirectory)
-  if (NOT IS_DIRECTORY "${TargetDirectory}")
-    GetUrlFilename(TMP_FILENAME "${Url}")
-
-    set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
-    if (NOT EXISTS "${TMP_PATH}")
-      message("Downloading ${Url}")
-
-      # This fixes issue 6: "I think cmake shouldn't download the
-      # packages which are not in the system, it should stop and let
-      # user know."
-      # https://code.google.com/p/orthanc/issues/detail?id=6
-      if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
-	message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
-      endif()
-
-      file(DOWNLOAD "${Url}" "${TMP_PATH}" 
-        SHOW_PROGRESS EXPECTED_MD5 "${MD5}"
-        TIMEOUT 60 INACTIVITY_TIMEOUT 60)
-    else()
-      message("Using local copy of ${Url}")
-    endif()
-
-    GetUrlExtension(TMP_EXTENSION "${Url}")
-    #message(${TMP_EXTENSION})
-    message("Uncompressing ${TMP_FILENAME}")
-
-    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-      # How to silently extract files using 7-zip
-      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
-
-      if (("${TMP_EXTENSION}" STREQUAL "gz") OR 
-          ("${TMP_EXTENSION}" STREQUAL "tgz") OR
-          ("${TMP_EXTENSION}" STREQUAL "xz"))
-        execute_process(
-          COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          OUTPUT_QUIET
-          )
-
-        if (Failure)
-          message(FATAL_ERROR "Error while running the uncompression tool")
-        endif()
-
-        if ("${TMP_EXTENSION}" STREQUAL "tgz")
-          string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
-        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
-          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
-        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
-          string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}")
-        endif()
-
-        execute_process(
-          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          OUTPUT_QUIET
-          )
-      elseif ("${TMP_EXTENSION}" STREQUAL "zip")
-        execute_process(
-          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          OUTPUT_QUIET
-          )
-      else()
-        message(FATAL_ERROR "Support your platform here")
-      endif()
-
-    else()
-      if ("${TMP_EXTENSION}" STREQUAL "zip")
-        execute_process(
-          COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-        )
-      elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
-        #message("tar xvfz ${TMP_PATH}")
-        execute_process(
-          COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          )
-      elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
-        execute_process(
-          COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          )
-      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
-        execute_process(
-          COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}"
-          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-          RESULT_VARIABLE Failure
-          )
-      else()
-        message(FATAL_ERROR "Unknown package format.")
-      endif()
-    endif()
-   
-    if (Failure)
-      message(FATAL_ERROR "Error while running the uncompression tool")
-    endif()
-
-    if (NOT IS_DIRECTORY "${TargetDirectory}")
-      message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.")
-    endif()
-  endif()
-endmacro()
--- a/Resources/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-if (USE_GTEST_DEBIAN_SOURCE_PACKAGE)
-  find_path(GTEST_DEBIAN_SOURCES_DIR
-    NAMES src/gtest-all.cc
-    PATHS
-    /usr/src/gtest
-    /usr/src/googletest/googletest
-    PATH_SUFFIXES src
-    )
-
-  find_path(GTEST_DEBIAN_INCLUDE_DIR
-    NAMES gtest.h
-    PATHS
-    /usr/include/gtest
-    )
-
-  message("Path to the Debian Google Test sources: ${GTEST_DEBIAN_SOURCES_DIR}")
-  message("Path to the Debian Google Test includes: ${GTEST_DEBIAN_INCLUDE_DIR}")
-
-  set(GTEST_SOURCES ${GTEST_DEBIAN_SOURCES_DIR}/src/gtest-all.cc)
-  include_directories(${GTEST_DEBIAN_SOURCES_DIR})
-
-  if (NOT EXISTS ${GTEST_SOURCES} OR
-      NOT EXISTS ${GTEST_DEBIAN_INCLUDE_DIR}/gtest.h)
-    message(FATAL_ERROR "Please install the libgtest-dev package")
-  endif()
-
-elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
-  set(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0)
-  set(GTEST_URL "http://www.orthanc-server.com/downloads/third-party/gtest-1.7.0.zip")
-  set(GTEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7")
-
-  DownloadPackage(${GTEST_MD5} ${GTEST_URL} "${GTEST_SOURCES_DIR}")
-
-  include_directories(
-    ${GTEST_SOURCES_DIR}/include
-    ${GTEST_SOURCES_DIR}
-    )
-
-  set(GTEST_SOURCES
-    ${GTEST_SOURCES_DIR}/src/gtest-all.cc
-    )
-
-  # https://code.google.com/p/googletest/issues/detail?id=412
-  if (MSVC) # VS2012 does not support tuples correctly yet
-    add_definitions(/D _VARIADIC_MAX=10)
-  endif()
-
-  source_group(ThirdParty\\GoogleTest REGULAR_EXPRESSION ${GTEST_SOURCES_DIR}/.*)
-
-else()
-  include(FindGTest)
-  if (NOT GTEST_FOUND)
-    message(FATAL_ERROR "Unable to find GoogleTest")
-  endif()
-
-  include_directories(${GTEST_INCLUDE_DIRS})
-  link_libraries(${GTEST_LIBRARIES})
-endif()
--- a/Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
-  set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5)
-  set(JSONCPP_URL "http://www.orthanc-server.com/downloads/third-party/jsoncpp-0.10.5.tar.gz")
-  set(JSONCPP_MD5 "db146bac5a126ded9bd728ab7b61ed6b")
-
-  DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}")
-
-  set(JSONCPP_SOURCES
-    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
-    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
-    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
-    )
-
-  include_directories(
-    ${JSONCPP_SOURCES_DIR}/include
-    )
-
-  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
-
-else()
-  find_path(JSONCPP_INCLUDE_DIR json/reader.h
-    /usr/include/jsoncpp
-    /usr/local/include/jsoncpp
-    )
-
-  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
-  include_directories(${JSONCPP_INCLUDE_DIR})
-  link_libraries(jsoncpp)
-
-  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
-  if (NOT HAVE_JSONCPP_H)
-    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
-  endif()
-
-  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
-  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
-    file(STRINGS
-      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
-      JSONCPP_VERSION_MAJOR1 REGEX
-      ".*define JSONCPP_VERSION_MAJOR.*")
-
-    if (NOT JSONCPP_VERSION_MAJOR1)
-      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
-    endif()
-    
-    string(REGEX REPLACE
-      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
-      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
-    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
-
-    if (CMAKE_COMPILER_IS_GNUCXX AND 
-        JSONCPP_VERSION_MAJOR GREATER 0)
-      message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
-      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
-    endif()
-  else()
-    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
-  endif()
-
-endif()
--- a/Resources/Orthanc/Resources/CMake/LibCurlConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_CURL)
-  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.50.3)
-  SET(CURL_URL "http://www.orthanc-server.com/downloads/third-party/curl-7.50.3.tar.gz")
-  SET(CURL_MD5 "870e16fd88a88b52e26a4f04dfc161db")
-
-  DownloadPackage(${CURL_MD5} ${CURL_URL} "${CURL_SOURCES_DIR}")
-
-  include_directories(
-    ${CURL_SOURCES_DIR}/include
-    )
-
-  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES)
-  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vauth CURL_SOURCES)
-  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES)
-  source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*)
-
-  add_definitions(
-    -DBUILDING_LIBCURL=1
-    -DCURL_STATICLIB=1
-    -DCURL_DISABLE_LDAPS=1
-    -DCURL_DISABLE_LDAP=1
-    -DCURL_DISABLE_DICT=1
-    -DCURL_DISABLE_FILE=1
-    -DCURL_DISABLE_FTP=1
-    -DCURL_DISABLE_GOPHER=1
-    -DCURL_DISABLE_LDAP=1
-    -DCURL_DISABLE_LDAPS=1
-    -DCURL_DISABLE_POP3=1
-    #-DCURL_DISABLE_PROXY=1
-    -DCURL_DISABLE_RTSP=1
-    -DCURL_DISABLE_TELNET=1
-    -DCURL_DISABLE_TFTP=1
-    )
-
-  if (ENABLE_SSL)
-    add_definitions(
-      #-DHAVE_LIBSSL=1
-      -DUSE_OPENSSL=1
-      -DHAVE_OPENSSL_ENGINE_H=1
-      -DUSE_SSLEAY=1
-      )
-  endif()
-
-  if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/curl_config.h")
-    file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "")
-
-    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h "#include \"../vauth.h\"\n")
-    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/digest.h "#include \"../digest.h\"\n")
-    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/ntlm.h "#include \"../ntlm.h\"\n")
-    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vtls/vtls.h "#include \"../../vtls/vtls.h\"\n")
-
-    file(GLOB CURL_LIBS_HEADERS ${CURL_SOURCES_DIR}/lib/*.h)
-    foreach (header IN LISTS CURL_LIBS_HEADERS)
-      get_filename_component(filename ${header} NAME)
-      file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/${filename} "#include \"../${filename}\"\n")
-      file(WRITE ${CURL_SOURCES_DIR}/lib/vtls/${filename} "#include \"../${filename}\"\n")
-    endforeach()
-  endif()
-
-  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
-    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
-      SET(TMP_OS "x86_64")
-    else()
-      SET(TMP_OS "x86")
-    endif()
-
-    set_property(
-      SOURCE ${CURL_SOURCES}
-      PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;HAVE_LONGLONG;OS=\"${TMP_OS}\""
-      )
-
-    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
-      add_definitions(
-        -DRECV_TYPE_ARG1=int
-        -DRECV_TYPE_ARG2=void*
-        -DRECV_TYPE_ARG3=size_t
-        -DRECV_TYPE_ARG4=int
-        -DRECV_TYPE_RETV=ssize_t
-        -DSEND_TYPE_ARG1=int
-        -DSEND_TYPE_ARG2=void*
-        -DSEND_QUAL_ARG2=const
-        -DSEND_TYPE_ARG3=size_t
-        -DSEND_TYPE_ARG4=int
-        -DSEND_TYPE_RETV=ssize_t
-        -DSIZEOF_SHORT=2
-        -DSIZEOF_INT=4
-        -DSIZEOF_SIZE_T=8
-        )
-    elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
-      add_definitions(
-        -DRECV_TYPE_ARG1=int
-        -DRECV_TYPE_ARG2=void*
-        -DRECV_TYPE_ARG3=size_t
-        -DRECV_TYPE_ARG4=int
-        -DRECV_TYPE_RETV=int
-        -DSEND_TYPE_ARG1=int
-        -DSEND_TYPE_ARG2=void*
-        -DSEND_QUAL_ARG2=const
-        -DSEND_TYPE_ARG3=size_t
-        -DSEND_TYPE_ARG4=int
-        -DSEND_TYPE_RETV=int
-        -DSIZEOF_SHORT=2
-        -DSIZEOF_INT=4
-        -DSIZEOF_SIZE_T=4
-        )
-    else()
-      message(FATAL_ERROR "Support your platform here")
-    endif()
-  endif()
-
-else()
-  include(FindCURL)
-  include_directories(${CURL_INCLUDE_DIRS})
-  link_libraries(${CURL_LIBRARIES})
-
-  if (NOT ${CURL_FOUND})
-    message(FATAL_ERROR "Unable to find LibCurl")
-  endif()
-endif()
--- a/Resources/Orthanc/Resources/CMake/LibIconvConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-set(LIBICONV_SOURCES_DIR ${CMAKE_BINARY_DIR}/libiconv-1.14)
-set(LIBICONV_URL "http://www.orthanc-server.com/downloads/third-party/libiconv-1.14.tar.gz")
-set(LIBICONV_MD5 "e34509b1623cec449dfeb73d7ce9c6c6")
-
-DownloadPackage(${LIBICONV_MD5} ${LIBICONV_URL} "${LIBICONV_SOURCES_DIR}")
-
-# https://groups.google.com/d/msg/android-ndk/AS1nkxnk6m4/EQm09hD1tigJ
-add_definitions(
-  -DBOOST_LOCALE_WITH_ICONV=1
-  -DBUILDING_LIBICONV=1
-  -DIN_LIBRARY=1
-  -DLIBDIR=""
-  -DICONV_CONST=
-  )
-
-configure_file(
-  ${LIBICONV_SOURCES_DIR}/srclib/localcharset.h
-  ${LIBICONV_SOURCES_DIR}/include
-  COPYONLY)
-
-set(HAVE_VISIBILITY 0)
-set(ICONV_CONST ${ICONV_CONST})
-set(USE_MBSTATE_T 1)
-set(BROKEN_WCHAR_H 0)
-set(EILSEQ)
-set(HAVE_WCHAR_T 1)   
-configure_file(
-  ${LIBICONV_SOURCES_DIR}/include/iconv.h.build.in
-  ${LIBICONV_SOURCES_DIR}/include/iconv.h
-  )
-unset(HAVE_VISIBILITY)
-unset(ICONV_CONST)
-unset(USE_MBSTATE_T)
-unset(BROKEN_WCHAR_H)
-unset(EILSEQ)
-unset(HAVE_WCHAR_T)   
-
-# Create an empty "config.h" for libiconv
-file(WRITE ${LIBICONV_SOURCES_DIR}/include/config.h "")
-
-include_directories(
-  ${LIBICONV_SOURCES_DIR}/include
-  )
-
-list(APPEND BOOST_SOURCES
-  ${LIBICONV_SOURCES_DIR}/lib/iconv.c  
-  ${LIBICONV_SOURCES_DIR}/lib/relocatable.c
-  ${LIBICONV_SOURCES_DIR}/libcharset/lib/localcharset.c  
-  ${LIBICONV_SOURCES_DIR}/libcharset/lib/relocatable.c
-  )
-
-source_group(ThirdParty\\libiconv REGULAR_EXPRESSION ${LIBICONV_SOURCES_DIR}/.*)
--- a/Resources/Orthanc/Resources/CMake/LibJpegConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG)
-  set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a)
-  DownloadPackage(
-    "3353992aecaee1805ef4109aadd433e7"
-    "http://www.orthanc-server.com/downloads/third-party/jpegsrc.v9a.tar.gz"
-    "${LIBJPEG_SOURCES_DIR}")
-
-  include_directories(
-    ${LIBJPEG_SOURCES_DIR}
-    )
-
-  list(APPEND LIBJPEG_SOURCES 
-    ${LIBJPEG_SOURCES_DIR}/jaricom.c
-    ${LIBJPEG_SOURCES_DIR}/jcapimin.c
-    ${LIBJPEG_SOURCES_DIR}/jcapistd.c
-    ${LIBJPEG_SOURCES_DIR}/jcarith.c
-    ${LIBJPEG_SOURCES_DIR}/jccoefct.c
-    ${LIBJPEG_SOURCES_DIR}/jccolor.c
-    ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c
-    ${LIBJPEG_SOURCES_DIR}/jchuff.c
-    ${LIBJPEG_SOURCES_DIR}/jcinit.c
-    ${LIBJPEG_SOURCES_DIR}/jcmarker.c
-    ${LIBJPEG_SOURCES_DIR}/jcmaster.c
-    ${LIBJPEG_SOURCES_DIR}/jcomapi.c
-    ${LIBJPEG_SOURCES_DIR}/jcparam.c
-    ${LIBJPEG_SOURCES_DIR}/jcprepct.c
-    ${LIBJPEG_SOURCES_DIR}/jcsample.c
-    ${LIBJPEG_SOURCES_DIR}/jctrans.c
-    ${LIBJPEG_SOURCES_DIR}/jdapimin.c
-    ${LIBJPEG_SOURCES_DIR}/jdapistd.c
-    ${LIBJPEG_SOURCES_DIR}/jdarith.c
-    ${LIBJPEG_SOURCES_DIR}/jdatadst.c
-    ${LIBJPEG_SOURCES_DIR}/jdatasrc.c
-    ${LIBJPEG_SOURCES_DIR}/jdcoefct.c
-    ${LIBJPEG_SOURCES_DIR}/jdcolor.c
-    ${LIBJPEG_SOURCES_DIR}/jddctmgr.c
-    ${LIBJPEG_SOURCES_DIR}/jdhuff.c
-    ${LIBJPEG_SOURCES_DIR}/jdinput.c
-    ${LIBJPEG_SOURCES_DIR}/jcmainct.c
-    ${LIBJPEG_SOURCES_DIR}/jdmainct.c
-    ${LIBJPEG_SOURCES_DIR}/jdmarker.c
-    ${LIBJPEG_SOURCES_DIR}/jdmaster.c
-    ${LIBJPEG_SOURCES_DIR}/jdmerge.c
-    ${LIBJPEG_SOURCES_DIR}/jdpostct.c
-    ${LIBJPEG_SOURCES_DIR}/jdsample.c
-    ${LIBJPEG_SOURCES_DIR}/jdtrans.c
-    ${LIBJPEG_SOURCES_DIR}/jerror.c
-    ${LIBJPEG_SOURCES_DIR}/jfdctflt.c
-    ${LIBJPEG_SOURCES_DIR}/jfdctfst.c
-    ${LIBJPEG_SOURCES_DIR}/jfdctint.c
-    ${LIBJPEG_SOURCES_DIR}/jidctflt.c
-    ${LIBJPEG_SOURCES_DIR}/jidctfst.c
-    ${LIBJPEG_SOURCES_DIR}/jidctint.c
-    #${LIBJPEG_SOURCES_DIR}/jmemansi.c
-    #${LIBJPEG_SOURCES_DIR}/jmemdos.c
-    #${LIBJPEG_SOURCES_DIR}/jmemmac.c
-    ${LIBJPEG_SOURCES_DIR}/jmemmgr.c
-    #${LIBJPEG_SOURCES_DIR}/jmemname.c
-    ${LIBJPEG_SOURCES_DIR}/jmemnobs.c
-    ${LIBJPEG_SOURCES_DIR}/jquant1.c
-    ${LIBJPEG_SOURCES_DIR}/jquant2.c
-    ${LIBJPEG_SOURCES_DIR}/jutils.c
-
-    # ${LIBJPEG_SOURCES_DIR}/rdbmp.c
-    # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c
-    # ${LIBJPEG_SOURCES_DIR}/rdgif.c
-    # ${LIBJPEG_SOURCES_DIR}/rdppm.c
-    # ${LIBJPEG_SOURCES_DIR}/rdrle.c
-    # ${LIBJPEG_SOURCES_DIR}/rdswitch.c
-    # ${LIBJPEG_SOURCES_DIR}/rdtarga.c
-    # ${LIBJPEG_SOURCES_DIR}/transupp.c
-    # ${LIBJPEG_SOURCES_DIR}/wrbmp.c
-    # ${LIBJPEG_SOURCES_DIR}/wrgif.c
-    # ${LIBJPEG_SOURCES_DIR}/wrppm.c
-    # ${LIBJPEG_SOURCES_DIR}/wrrle.c
-    # ${LIBJPEG_SOURCES_DIR}/wrtarga.c
-    )
-
-  configure_file(
-    ${LIBJPEG_SOURCES_DIR}/jconfig.txt
-    ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY
-    )
-
-  source_group(ThirdParty\\libjpeg REGULAR_EXPRESSION ${LIBJPEG_SOURCES_DIR}/.*)
-
-else()
-  include(FindJPEG)
-
-  if (NOT ${JPEG_FOUND})
-    message(FATAL_ERROR "Unable to find libjpeg")
-  endif()
-
-  include_directories(${JPEG_INCLUDE_DIR})
-  link_libraries(${JPEG_LIBRARIES})
-endif()
--- a/Resources/Orthanc/Resources/CMake/LibPngConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG)
-  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12)
-  SET(LIBPNG_URL "http://www.orthanc-server.com/downloads/third-party/libpng-1.5.12.tar.gz")
-  SET(LIBPNG_MD5 "8ea7f60347a306c5faf70b977fa80e28")
-
-  DownloadPackage(${LIBPNG_MD5} ${LIBPNG_URL} "${LIBPNG_SOURCES_DIR}")
-
-  include_directories(
-    ${LIBPNG_SOURCES_DIR}
-    )
-
-  configure_file(
-    ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt
-    ${LIBPNG_SOURCES_DIR}/pnglibconf.h
-    )
-
-  set(LIBPNG_SOURCES
-    #${LIBPNG_SOURCES_DIR}/example.c
-    ${LIBPNG_SOURCES_DIR}/png.c
-    ${LIBPNG_SOURCES_DIR}/pngerror.c
-    ${LIBPNG_SOURCES_DIR}/pngget.c
-    ${LIBPNG_SOURCES_DIR}/pngmem.c
-    ${LIBPNG_SOURCES_DIR}/pngpread.c
-    ${LIBPNG_SOURCES_DIR}/pngread.c
-    ${LIBPNG_SOURCES_DIR}/pngrio.c
-    ${LIBPNG_SOURCES_DIR}/pngrtran.c
-    ${LIBPNG_SOURCES_DIR}/pngrutil.c
-    ${LIBPNG_SOURCES_DIR}/pngset.c
-    #${LIBPNG_SOURCES_DIR}/pngtest.c
-    ${LIBPNG_SOURCES_DIR}/pngtrans.c
-    ${LIBPNG_SOURCES_DIR}/pngwio.c
-    ${LIBPNG_SOURCES_DIR}/pngwrite.c
-    ${LIBPNG_SOURCES_DIR}/pngwtran.c
-    ${LIBPNG_SOURCES_DIR}/pngwutil.c
-    )
-
-  #set_property(
-  #  SOURCE ${LIBPNG_SOURCES}
-  #  PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H)
-
-  add_definitions(
-    -DPNG_NO_CONSOLE_IO=1
-    -DPNG_NO_STDIO=1
-    # The following declaration avoids "__declspec(dllexport)" in
-    # libpng to prevent publicly exposing its symbols by the DLLs
-    -DPNG_IMPEXP=
-    )
-
-  source_group(ThirdParty\\libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*)
-
-else()
-  include(FindPNG)
-
-  if (NOT ${PNG_FOUND})
-    message(FATAL_ERROR "Unable to find libpng")
-  endif()
-
-  include_directories(${PNG_INCLUDE_DIRS})
-  link_libraries(${PNG_LIBRARIES})
-  add_definitions(${PNG_DEFINITIONS})
-endif()
--- a/Resources/Orthanc/Resources/CMake/OpenSslConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,230 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL)
-  # WARNING - We had to repack the upstream ".tar.gz" file to a ZIP
-  # file, as the upstream distribution ships symbolic links that are
-  # not always properly handled when uncompressing on Windows.
-
-  SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2d)
-  SET(OPENSSL_URL "http://www.orthanc-server.com/downloads/third-party/openssl-1.0.2d.zip")
-  SET(OPENSSL_MD5 "4b2ac15fc6db17f3dadc54482d3eee85")
-
-  if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
-    set(FirstRun OFF)
-  else()
-    set(FirstRun ON)
-  endif()
-
-  DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}")
-
-  add_definitions(
-    -DOPENSSL_THREADS
-    -DOPENSSL_IA32_SSE2
-    -DOPENSSL_NO_ASM
-    -DOPENSSL_NO_DYNAMIC_ENGINE
-    -DNO_WINDOWS_BRAINDEATH
-
-    -DOPENSSL_NO_BF 
-    -DOPENSSL_NO_CAMELLIA
-    -DOPENSSL_NO_CAST 
-    -DOPENSSL_NO_EC_NISTP_64_GCC_128
-    -DOPENSSL_NO_GMP
-    -DOPENSSL_NO_GOST
-    -DOPENSSL_NO_HW
-    -DOPENSSL_NO_JPAKE
-    -DOPENSSL_NO_IDEA
-    -DOPENSSL_NO_KRB5 
-    -DOPENSSL_NO_MD2 
-    -DOPENSSL_NO_MDC2 
-    -DOPENSSL_NO_MD4
-    -DOPENSSL_NO_RC2 
-    -DOPENSSL_NO_RC4 
-    -DOPENSSL_NO_RC5 
-    -DOPENSSL_NO_RFC3779
-    -DOPENSSL_NO_SCTP
-    -DOPENSSL_NO_STORE
-    -DOPENSSL_NO_SEED
-    -DOPENSSL_NO_WHIRLPOOL
-    -DOPENSSL_NO_RIPEMD
-    )
-
-  include_directories(
-    ${OPENSSL_SOURCES_DIR}
-    ${OPENSSL_SOURCES_DIR}/crypto
-    ${OPENSSL_SOURCES_DIR}/crypto/asn1
-    ${OPENSSL_SOURCES_DIR}/crypto/modes
-    ${OPENSSL_SOURCES_DIR}/crypto/evp
-    ${OPENSSL_SOURCES_DIR}/include
-    )
-
-  set(OPENSSL_SOURCES_SUBDIRS
-    ${OPENSSL_SOURCES_DIR}/crypto
-    ${OPENSSL_SOURCES_DIR}/crypto/aes
-    ${OPENSSL_SOURCES_DIR}/crypto/asn1
-    ${OPENSSL_SOURCES_DIR}/crypto/bio
-    ${OPENSSL_SOURCES_DIR}/crypto/bn
-    ${OPENSSL_SOURCES_DIR}/crypto/buffer
-    ${OPENSSL_SOURCES_DIR}/crypto/cmac
-    ${OPENSSL_SOURCES_DIR}/crypto/cms
-    ${OPENSSL_SOURCES_DIR}/crypto/comp
-    ${OPENSSL_SOURCES_DIR}/crypto/conf
-    ${OPENSSL_SOURCES_DIR}/crypto/des
-    ${OPENSSL_SOURCES_DIR}/crypto/dh
-    ${OPENSSL_SOURCES_DIR}/crypto/dsa
-    ${OPENSSL_SOURCES_DIR}/crypto/dso
-    ${OPENSSL_SOURCES_DIR}/crypto/engine
-    ${OPENSSL_SOURCES_DIR}/crypto/err
-    ${OPENSSL_SOURCES_DIR}/crypto/evp
-    ${OPENSSL_SOURCES_DIR}/crypto/hmac
-    ${OPENSSL_SOURCES_DIR}/crypto/lhash
-    ${OPENSSL_SOURCES_DIR}/crypto/md5
-    ${OPENSSL_SOURCES_DIR}/crypto/modes
-    ${OPENSSL_SOURCES_DIR}/crypto/objects
-    ${OPENSSL_SOURCES_DIR}/crypto/ocsp
-    ${OPENSSL_SOURCES_DIR}/crypto/pem
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs12
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7
-    ${OPENSSL_SOURCES_DIR}/crypto/pqueue
-    ${OPENSSL_SOURCES_DIR}/crypto/rand
-    ${OPENSSL_SOURCES_DIR}/crypto/rsa
-    ${OPENSSL_SOURCES_DIR}/crypto/sha
-    ${OPENSSL_SOURCES_DIR}/crypto/srp
-    ${OPENSSL_SOURCES_DIR}/crypto/stack
-    ${OPENSSL_SOURCES_DIR}/crypto/ts
-    ${OPENSSL_SOURCES_DIR}/crypto/txt_db
-    ${OPENSSL_SOURCES_DIR}/crypto/ui
-    ${OPENSSL_SOURCES_DIR}/crypto/x509
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3
-    ${OPENSSL_SOURCES_DIR}/ssl
-    )
-
-  if (ENABLE_PKCS11)
-    list(APPEND OPENSSL_SOURCES_SUBDIRS
-      # EC, ECDH and ECDSA are necessary for PKCS11
-      ${OPENSSL_SOURCES_DIR}/crypto/ec
-      ${OPENSSL_SOURCES_DIR}/crypto/ecdh
-      ${OPENSSL_SOURCES_DIR}/crypto/ecdsa
-      )
-  else()
-    add_definitions(
-      -DOPENSSL_NO_EC
-      -DOPENSSL_NO_ECDH
-      -DOPENSSL_NO_ECDSA
-      )
-  endif()
-
-  foreach(d ${OPENSSL_SOURCES_SUBDIRS})
-    AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES)
-  endforeach()
-
-  list(REMOVE_ITEM OPENSSL_SOURCES
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c
-    ${OPENSSL_SOURCES_DIR}/crypto/armcap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c
-    ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c
-    ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/des.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c
-    ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c
-    ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c
-    ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c
-    ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c
-
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c
-    ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c
-
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_extra_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/verify_extra_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509/verify_extra_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3prin.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3nametest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/constant_time_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c
-
-    ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c
-    )
-
-
-  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
-    set_source_files_properties(
-      ${OPENSSL_SOURCES}
-      PROPERTIES COMPILE_DEFINITIONS
-      "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN")
-
-  elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
-    execute_process(
-      COMMAND ${PATCH_EXECUTABLE} -N ui_openssl.c -i ${ORTHANC_ROOT}/Resources/Patches/openssl-lsb.diff
-      WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui
-      RESULT_VARIABLE Failure
-      )
-
-    if (Failure AND FirstRun)
-      message(FATAL_ERROR "Error while patching a file")
-    endif()
-  endif()
-
-  source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
-
-else()
-  include(FindOpenSSL)
-
-  if (NOT ${OPENSSL_FOUND})
-    message(FATAL_ERROR "Unable to find OpenSSL")
-  endif()
-
-  include_directories(${OPENSSL_INCLUDE_DIR})
-  link_libraries(${OPENSSL_LIBRARIES})
-endif()
--- a/Resources/Orthanc/Resources/CMake/ZlibConfiguration.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
-  SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
-  SET(ZLIB_URL "http://www.orthanc-server.com/downloads/third-party/zlib-1.2.7.tar.gz")
-  SET(ZLIB_MD5 "60df6a37c56e7c1366cca812414f7b85")
-
-  DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}")
-
-  include_directories(
-    ${ZLIB_SOURCES_DIR}
-    )
-
-  list(APPEND ZLIB_SOURCES 
-    ${ZLIB_SOURCES_DIR}/adler32.c
-    ${ZLIB_SOURCES_DIR}/compress.c
-    ${ZLIB_SOURCES_DIR}/crc32.c 
-    ${ZLIB_SOURCES_DIR}/deflate.c 
-    ${ZLIB_SOURCES_DIR}/gzclose.c 
-    ${ZLIB_SOURCES_DIR}/gzlib.c 
-    ${ZLIB_SOURCES_DIR}/gzread.c 
-    ${ZLIB_SOURCES_DIR}/gzwrite.c 
-    ${ZLIB_SOURCES_DIR}/infback.c 
-    ${ZLIB_SOURCES_DIR}/inffast.c 
-    ${ZLIB_SOURCES_DIR}/inflate.c 
-    ${ZLIB_SOURCES_DIR}/inftrees.c 
-    ${ZLIB_SOURCES_DIR}/trees.c 
-    ${ZLIB_SOURCES_DIR}/uncompr.c 
-    ${ZLIB_SOURCES_DIR}/zutil.c
-    )
-
-  source_group(ThirdParty\\zlib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
-
-else()
-  include(FindZLIB)
-  include_directories(${ZLIB_INCLUDE_DIRS})
-  link_libraries(${ZLIB_LIBRARIES})
-endif()
--- a/Resources/Orthanc/Resources/EmbedResources.py	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,431 +0,0 @@
-#!/usr/bin/python
-
-# Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
-# Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017 Osimis, Belgium
-#
-# This program is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# In addition, as a special exception, the copyright holders of this
-# program give permission to link the code of its release with the
-# OpenSSL project's "OpenSSL" library (or with modified versions of it
-# that use the same license as the "OpenSSL" library), and distribute
-# the linked executables. You must obey the GNU General Public License
-# in all respects for all of the code used other than "OpenSSL". If you
-# modify file(s) with this exception, you may extend this exception to
-# your version of the file(s), but you are not obligated to do so. If
-# you do not wish to do so, delete this exception statement from your
-# version. If you delete this exception statement from all source files
-# in the program, then also delete it here.
-# 
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-import sys
-import os
-import os.path
-import pprint
-import re
-
-UPCASE_CHECK = True
-USE_SYSTEM_EXCEPTION = False
-EXCEPTION_CLASS = 'OrthancException'
-OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)'
-INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)'
-NAMESPACE = 'Orthanc'
-
-ARGS = []
-for i in range(len(sys.argv)):
-    if not sys.argv[i].startswith('--'):
-        ARGS.append(sys.argv[i])
-    elif sys.argv[i].lower() == '--no-upcase-check':
-        UPCASE_CHECK = False
-    elif sys.argv[i].lower() == '--system-exception':
-        USE_SYSTEM_EXCEPTION = True
-        EXCEPTION_CLASS = '::std::runtime_error'
-        OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS
-        INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS
-    elif sys.argv[i].startswith('--namespace='):
-        NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ]
-
-if len(ARGS) < 2 or len(ARGS) % 2 != 0:
-    print ('Usage:')
-    print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0])
-    exit(-1)
-
-TARGET_BASE_FILENAME = ARGS[1]
-SOURCES = ARGS[2:]
-
-try:
-    # Make sure the destination directory exists
-    os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..')))
-except:
-    pass
-
-
-#####################################################################
-## Read each resource file
-#####################################################################
-
-def CheckNoUpcase(s):
-    global UPCASE_CHECK
-    if (UPCASE_CHECK and
-        re.search('[A-Z]', s) != None):
-        raise Exception("Path in a directory with an upcase letter: %s" % s)
-
-resources = {}
-
-counter = 0
-i = 0
-while i < len(SOURCES):
-    resourceName = SOURCES[i].upper()
-    pathName = SOURCES[i + 1]
-
-    if not os.path.exists(pathName):
-        raise Exception("Non existing path: %s" % pathName)
-
-    if resourceName in resources:
-        raise Exception("Twice the same resource: " + resourceName)
-    
-    if os.path.isdir(pathName):
-        # The resource is a directory: Recursively explore its files
-        content = {}
-        for root, dirs, files in os.walk(pathName):
-            base = os.path.relpath(root, pathName)
-
-            # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
-            # Ignore folders whose name starts with a dot (".")
-            if base.find('/.') != -1:
-                print('Ignoring folder: %s' % root)
-                continue
-
-            for f in files:
-                if f.find('~') == -1:  # Ignore Emacs backup files
-                    if base == '.':
-                        r = f
-                    else:
-                        r = os.path.join(base, f)
-
-                    CheckNoUpcase(r)
-                    r = '/' + r.replace('\\', '/')
-                    if r in content:
-                        raise Exception("Twice the same filename (check case): " + r)
-
-                    content[r] = {
-                        'Filename' : os.path.join(root, f),
-                        'Index' : counter
-                        }
-                    counter += 1
-
-        resources[resourceName] = {
-            'Type' : 'Directory',
-            'Files' : content
-            }
-
-    elif os.path.isfile(pathName):
-        resources[resourceName] = {
-            'Type' : 'File',
-            'Index' : counter,
-            'Filename' : pathName
-            }
-        counter += 1
-
-    else:
-        raise Exception("Not a regular file, nor a directory: " + pathName)
-
-    i += 2
-
-#pprint.pprint(resources)
-
-
-#####################################################################
-## Write .h header
-#####################################################################
-
-header = open(TARGET_BASE_FILENAME + '.h', 'w')
-
-header.write("""
-#pragma once
-
-#include <string>
-#include <list>
-
-#if defined(_MSC_VER)
-#  pragma warning(disable: 4065)  // "Switch statement contains 'default' but no 'case' labels"
-#endif
-
-namespace %s
-{
-  namespace EmbeddedResources
-  {
-    enum FileResourceId
-    {
-""" % NAMESPACE)
-
-isFirst = True
-for name in resources:
-    if resources[name]['Type'] == 'File':
-        if isFirst:
-            isFirst = False
-        else:    
-            header.write(',\n')
-        header.write('      %s' % name)
-
-header.write("""
-    };
-
-    enum DirectoryResourceId
-    {
-""")
-
-isFirst = True
-for name in resources:
-    if resources[name]['Type'] == 'Directory':
-        if isFirst:
-            isFirst = False
-        else:    
-            header.write(',\n')
-        header.write('      %s' % name)
-
-header.write("""
-    };
-
-    const void* GetFileResourceBuffer(FileResourceId id);
-    size_t GetFileResourceSize(FileResourceId id);
-    void GetFileResource(std::string& result, FileResourceId id);
-
-    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path);
-    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path);
-    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path);
-
-    void ListResources(std::list<std::string>& result, DirectoryResourceId id);
-  }
-}
-""")
-header.close()
-
-
-
-#####################################################################
-## Write the resource content in the .cpp source
-#####################################################################
-
-PYTHON_MAJOR_VERSION = sys.version_info[0]
-
-def WriteResource(cpp, item):
-    cpp.write('    static const uint8_t resource%dBuffer[] = {' % item['Index'])
-
-    f = open(item['Filename'], "rb")
-    content = f.read()
-    f.close()
-
-    # http://stackoverflow.com/a/1035360
-    pos = 0
-    for b in content:
-        if PYTHON_MAJOR_VERSION == 2:
-            c = ord(b[0])
-        else:
-            c = b
-
-        if pos > 0:
-            cpp.write(', ')
-
-        if (pos % 16) == 0:
-            cpp.write('\n    ')
-
-        if c < 0:
-            raise Exception("Internal error")
-
-        cpp.write("0x%02x" % c)
-        pos += 1
-
-    # Zero-size array are disallowed, so we put one single void character in it.
-    if pos == 0:
-        cpp.write('  0')
-
-    cpp.write('  };\n')
-    cpp.write('    static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
-
-
-cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w')
-
-cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME))
-
-if USE_SYSTEM_EXCEPTION:
-    cpp.write('#include <stdexcept>')
-else:
-    cpp.write('#include "%s/Core/OrthancException.h"' % os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-
-cpp.write("""
-#include <stdint.h>
-#include <string.h>
-
-namespace %s
-{
-  namespace EmbeddedResources
-  {
-""" % NAMESPACE)
-
-
-for name in resources:
-    if resources[name]['Type'] == 'File':
-        WriteResource(cpp, resources[name])
-    else:
-        for f in resources[name]['Files']:
-            WriteResource(cpp, resources[name]['Files'][f])
-
-
-
-#####################################################################
-## Write the accessors to the file resources in .cpp
-#####################################################################
-
-cpp.write("""
-    const void* GetFileResourceBuffer(FileResourceId id)
-    {
-      switch (id)
-      {
-""")
-for name in resources:
-    if resources[name]['Type'] == 'File':
-        cpp.write('      case %s:\n' % name)
-        cpp.write('        return resource%dBuffer;\n' % resources[name]['Index'])
-
-cpp.write("""
-      default:
-        throw %s;
-      }
-    }
-
-    size_t GetFileResourceSize(FileResourceId id)
-    {
-      switch (id)
-      {
-""" % OUT_OF_RANGE_EXCEPTION)
-
-for name in resources:
-    if resources[name]['Type'] == 'File':
-        cpp.write('      case %s:\n' % name)
-        cpp.write('        return resource%dSize;\n' % resources[name]['Index'])
-
-cpp.write("""
-      default:
-        throw %s;
-      }
-    }
-""" % OUT_OF_RANGE_EXCEPTION)
-
-
-
-#####################################################################
-## Write the accessors to the directory resources in .cpp
-#####################################################################
-
-cpp.write("""
-    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path)
-    {
-      switch (id)
-      {
-""")
-
-for name in resources:
-    if resources[name]['Type'] == 'Directory':
-        cpp.write('      case %s:\n' % name)
-        isFirst = True
-        for path in resources[name]['Files']:
-            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
-            cpp.write('          return resource%dBuffer;\n' % resources[name]['Files'][path]['Index'])
-        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
-
-cpp.write("""      default:
-        throw %s;
-      }
-    }
-
-    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path)
-    {
-      switch (id)
-      {
-""" % OUT_OF_RANGE_EXCEPTION)
-
-for name in resources:
-    if resources[name]['Type'] == 'Directory':
-        cpp.write('      case %s:\n' % name)
-        isFirst = True
-        for path in resources[name]['Files']:
-            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
-            cpp.write('          return resource%dSize;\n' % resources[name]['Files'][path]['Index'])
-        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
-
-cpp.write("""      default:
-        throw %s;
-      }
-    }
-""" % OUT_OF_RANGE_EXCEPTION)
-
-
-
-
-#####################################################################
-## List the resources in a directory
-#####################################################################
-
-cpp.write("""
-    void ListResources(std::list<std::string>& result, DirectoryResourceId id)
-    {
-      result.clear();
-
-      switch (id)
-      {
-""")
-
-for name in resources:
-    if resources[name]['Type'] == 'Directory':
-        cpp.write('      case %s:\n' % name)
-        for path in sorted(resources[name]['Files']):
-            cpp.write('        result.push_back("%s");\n' % path)
-        cpp.write('        break;\n\n')
-
-cpp.write("""      default:
-        throw %s;
-      }
-    }
-""" % OUT_OF_RANGE_EXCEPTION)
-
-
-
-
-#####################################################################
-## Write the convenience wrappers in .cpp
-#####################################################################
-
-cpp.write("""
-    void GetFileResource(std::string& result, FileResourceId id)
-    {
-      size_t size = GetFileResourceSize(id);
-      result.resize(size);
-      if (size > 0)
-        memcpy(&result[0], GetFileResourceBuffer(id), size);
-    }
-
-    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path)
-    {
-      size_t size = GetDirectoryResourceSize(id, path);
-      result.resize(size);
-      if (size > 0)
-        memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size);
-    }
-  }
-}
-""")
-cpp.close()
--- a/Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# the name of the target operating system
-set(CMAKE_SYSTEM_NAME Windows)
-
-# which compilers to use for C and C++
-set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
-set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
-set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
-
-# here is the target environment located
-set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
-
-# adjust the default behaviour of the FIND_XXX() commands:
-# search headers and libraries in the target environment, search 
-# programs in the host environment
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# the name of the target operating system
-set(CMAKE_SYSTEM_NAME Windows)
-
-# which compilers to use for C and C++
-set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
-set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
-set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
-
-# here is the target environment located
-set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
-
-# adjust the default behaviour of the FIND_XXX() commands:
-# search headers and libraries in the target environment, search 
-# programs in the host environment
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Resources/Orthanc/Resources/MinGWToolchain.cmake	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# the name of the target operating system
-set(CMAKE_SYSTEM_NAME Windows)
-
-# which compilers to use for C and C++
-set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
-set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
-set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
-
-# here is the target environment located
-set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
-
-# adjust the default behaviour of the FIND_XXX() commands:
-# search headers and libraries in the target environment, search 
-# programs in the host environment
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,259 +0,0 @@
-// ISO C9x  compliant stdint.h for Microsoft Visual Studio
-// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
-// 
-//  Copyright (c) 2006-2013 Alexander Chemeris
-// 
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-// 
-//   1. Redistributions of source code must retain the above copyright notice,
-//      this list of conditions and the following disclaimer.
-// 
-//   2. Redistributions in binary form must reproduce the above copyright
-//      notice, this list of conditions and the following disclaimer in the
-//      documentation and/or other materials provided with the distribution.
-// 
-//   3. Neither the name of the product nor the names of its contributors may
-//      be used to endorse or promote products derived from this software
-//      without specific prior written permission.
-// 
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// 
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef _MSC_VER // [
-#error "Use this header only with Microsoft Visual C++ compilers!"
-#endif // _MSC_VER ]
-
-#ifndef _MSC_STDINT_H_ // [
-#define _MSC_STDINT_H_
-
-#if _MSC_VER > 1000
-#pragma once
-#endif
-
-#if _MSC_VER >= 1600 // [
-#include <stdint.h>
-#else // ] _MSC_VER >= 1600 [
-
-#include <limits.h>
-
-// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
-// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
-// or compiler give many errors like this:
-//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
-#ifdef __cplusplus
-extern "C" {
-#endif
-#  include <wchar.h>
-#ifdef __cplusplus
-}
-#endif
-
-// Define _W64 macros to mark types changing their size, like intptr_t.
-#ifndef _W64
-#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
-#     define _W64 __w64
-#  else
-#     define _W64
-#  endif
-#endif
-
-
-// 7.18.1 Integer types
-
-// 7.18.1.1 Exact-width integer types
-
-// Visual Studio 6 and Embedded Visual C++ 4 doesn't
-// realize that, e.g. char has the same size as __int8
-// so we give up on __intX for them.
-#if (_MSC_VER < 1300)
-   typedef signed char       int8_t;
-   typedef signed short      int16_t;
-   typedef signed int        int32_t;
-   typedef unsigned char     uint8_t;
-   typedef unsigned short    uint16_t;
-   typedef unsigned int      uint32_t;
-#else
-   typedef signed __int8     int8_t;
-   typedef signed __int16    int16_t;
-   typedef signed __int32    int32_t;
-   typedef unsigned __int8   uint8_t;
-   typedef unsigned __int16  uint16_t;
-   typedef unsigned __int32  uint32_t;
-#endif
-typedef signed __int64       int64_t;
-typedef unsigned __int64     uint64_t;
-
-
-// 7.18.1.2 Minimum-width integer types
-typedef int8_t    int_least8_t;
-typedef int16_t   int_least16_t;
-typedef int32_t   int_least32_t;
-typedef int64_t   int_least64_t;
-typedef uint8_t   uint_least8_t;
-typedef uint16_t  uint_least16_t;
-typedef uint32_t  uint_least32_t;
-typedef uint64_t  uint_least64_t;
-
-// 7.18.1.3 Fastest minimum-width integer types
-typedef int8_t    int_fast8_t;
-typedef int16_t   int_fast16_t;
-typedef int32_t   int_fast32_t;
-typedef int64_t   int_fast64_t;
-typedef uint8_t   uint_fast8_t;
-typedef uint16_t  uint_fast16_t;
-typedef uint32_t  uint_fast32_t;
-typedef uint64_t  uint_fast64_t;
-
-// 7.18.1.4 Integer types capable of holding object pointers
-#ifdef _WIN64 // [
-   typedef signed __int64    intptr_t;
-   typedef unsigned __int64  uintptr_t;
-#else // _WIN64 ][
-   typedef _W64 signed int   intptr_t;
-   typedef _W64 unsigned int uintptr_t;
-#endif // _WIN64 ]
-
-// 7.18.1.5 Greatest-width integer types
-typedef int64_t   intmax_t;
-typedef uint64_t  uintmax_t;
-
-
-// 7.18.2 Limits of specified-width integer types
-
-#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
-
-// 7.18.2.1 Limits of exact-width integer types
-#define INT8_MIN     ((int8_t)_I8_MIN)
-#define INT8_MAX     _I8_MAX
-#define INT16_MIN    ((int16_t)_I16_MIN)
-#define INT16_MAX    _I16_MAX
-#define INT32_MIN    ((int32_t)_I32_MIN)
-#define INT32_MAX    _I32_MAX
-#define INT64_MIN    ((int64_t)_I64_MIN)
-#define INT64_MAX    _I64_MAX
-#define UINT8_MAX    _UI8_MAX
-#define UINT16_MAX   _UI16_MAX
-#define UINT32_MAX   _UI32_MAX
-#define UINT64_MAX   _UI64_MAX
-
-// 7.18.2.2 Limits of minimum-width integer types
-#define INT_LEAST8_MIN    INT8_MIN
-#define INT_LEAST8_MAX    INT8_MAX
-#define INT_LEAST16_MIN   INT16_MIN
-#define INT_LEAST16_MAX   INT16_MAX
-#define INT_LEAST32_MIN   INT32_MIN
-#define INT_LEAST32_MAX   INT32_MAX
-#define INT_LEAST64_MIN   INT64_MIN
-#define INT_LEAST64_MAX   INT64_MAX
-#define UINT_LEAST8_MAX   UINT8_MAX
-#define UINT_LEAST16_MAX  UINT16_MAX
-#define UINT_LEAST32_MAX  UINT32_MAX
-#define UINT_LEAST64_MAX  UINT64_MAX
-
-// 7.18.2.3 Limits of fastest minimum-width integer types
-#define INT_FAST8_MIN    INT8_MIN
-#define INT_FAST8_MAX    INT8_MAX
-#define INT_FAST16_MIN   INT16_MIN
-#define INT_FAST16_MAX   INT16_MAX
-#define INT_FAST32_MIN   INT32_MIN
-#define INT_FAST32_MAX   INT32_MAX
-#define INT_FAST64_MIN   INT64_MIN
-#define INT_FAST64_MAX   INT64_MAX
-#define UINT_FAST8_MAX   UINT8_MAX
-#define UINT_FAST16_MAX  UINT16_MAX
-#define UINT_FAST32_MAX  UINT32_MAX
-#define UINT_FAST64_MAX  UINT64_MAX
-
-// 7.18.2.4 Limits of integer types capable of holding object pointers
-#ifdef _WIN64 // [
-#  define INTPTR_MIN   INT64_MIN
-#  define INTPTR_MAX   INT64_MAX
-#  define UINTPTR_MAX  UINT64_MAX
-#else // _WIN64 ][
-#  define INTPTR_MIN   INT32_MIN
-#  define INTPTR_MAX   INT32_MAX
-#  define UINTPTR_MAX  UINT32_MAX
-#endif // _WIN64 ]
-
-// 7.18.2.5 Limits of greatest-width integer types
-#define INTMAX_MIN   INT64_MIN
-#define INTMAX_MAX   INT64_MAX
-#define UINTMAX_MAX  UINT64_MAX
-
-// 7.18.3 Limits of other integer types
-
-#ifdef _WIN64 // [
-#  define PTRDIFF_MIN  _I64_MIN
-#  define PTRDIFF_MAX  _I64_MAX
-#else  // _WIN64 ][
-#  define PTRDIFF_MIN  _I32_MIN
-#  define PTRDIFF_MAX  _I32_MAX
-#endif  // _WIN64 ]
-
-#define SIG_ATOMIC_MIN  INT_MIN
-#define SIG_ATOMIC_MAX  INT_MAX
-
-#ifndef SIZE_MAX // [
-#  ifdef _WIN64 // [
-#     define SIZE_MAX  _UI64_MAX
-#  else // _WIN64 ][
-#     define SIZE_MAX  _UI32_MAX
-#  endif // _WIN64 ]
-#endif // SIZE_MAX ]
-
-// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
-#ifndef WCHAR_MIN // [
-#  define WCHAR_MIN  0
-#endif  // WCHAR_MIN ]
-#ifndef WCHAR_MAX // [
-#  define WCHAR_MAX  _UI16_MAX
-#endif  // WCHAR_MAX ]
-
-#define WINT_MIN  0
-#define WINT_MAX  _UI16_MAX
-
-#endif // __STDC_LIMIT_MACROS ]
-
-
-// 7.18.4 Limits of other integer types
-
-#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
-
-// 7.18.4.1 Macros for minimum-width integer constants
-
-#define INT8_C(val)  val##i8
-#define INT16_C(val) val##i16
-#define INT32_C(val) val##i32
-#define INT64_C(val) val##i64
-
-#define UINT8_C(val)  val##ui8
-#define UINT16_C(val) val##ui16
-#define UINT32_C(val) val##ui32
-#define UINT64_C(val) val##ui64
-
-// 7.18.4.2 Macros for greatest-width integer constants
-// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>.
-// Check out Issue 9 for the details.
-#ifndef INTMAX_C //   [
-#  define INTMAX_C   INT64_C
-#endif // INTMAX_C    ]
-#ifndef UINTMAX_C //  [
-#  define UINTMAX_C  UINT64_C
-#endif // UINTMAX_C   ]
-
-#endif // __STDC_CONSTANT_MACROS ]
-
-#endif // _MSC_VER >= 1600 ]
-
-#endif // _MSC_STDINT_H_ ]
--- a/Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/* 
-   base64.cpp and base64.h
-
-   Copyright (C) 2004-2008 René Nyffenegger
-
-   This source code is provided 'as-is', without any express or implied
-   warranty. In no event will the author be held liable for any damages
-   arising from the use of this software.
-
-   Permission is granted to anyone to use this software for any purpose,
-   including commercial applications, and to alter it and redistribute it
-   freely, subject to the following restrictions:
-
-   1. The origin of this source code must not be misrepresented; you must not
-      claim that you wrote the original source code. If you use this source code
-      in a product, an acknowledgment in the product documentation would be
-      appreciated but is not required.
-
-   2. Altered source versions must be plainly marked as such, and must not be
-      misrepresented as being the original source code.
-
-   3. This notice may not be removed or altered from any source distribution.
-
-   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
-
-*/
-
-#include "base64.h"
-#include <string.h>
-
-static const std::string base64_chars = 
-             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-             "abcdefghijklmnopqrstuvwxyz"
-             "0123456789+/";
-
-
-static inline bool is_base64(unsigned char c) {
-  return (isalnum(c) || (c == '+') || (c == '/'));
-}
-
-std::string base64_encode(const std::string& stringToEncode) 
-{
-  const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>
-    (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL);
-  unsigned int in_len = stringToEncode.size();
-  
-  std::string ret;
-  int i = 0;
-  int j = 0;
-  unsigned char char_array_3[3];
-  unsigned char char_array_4[4];
-
-  while (in_len--) {
-    char_array_3[i++] = *(bytes_to_encode++);
-    if (i == 3) {
-      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
-      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
-      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
-      char_array_4[3] = char_array_3[2] & 0x3f;
-
-      for(i = 0; (i <4) ; i++)
-        ret += base64_chars[char_array_4[i]];
-      i = 0;
-    }
-  }
-
-  if (i)
-  {
-    for(j = i; j < 3; j++)
-      char_array_3[j] = '\0';
-
-    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
-    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
-    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
-    char_array_4[3] = char_array_3[2] & 0x3f;
-
-    for (j = 0; (j < i + 1); j++)
-      ret += base64_chars[char_array_4[j]];
-
-    while((i++ < 3))
-      ret += '=';
-
-  }
-
-  return ret;
-}
-
-
-std::string base64_decode(const std::string& encoded_string) {
-  int in_len = encoded_string.size();
-  int i = 0;
-  int j = 0;
-  int in_ = 0;
-  unsigned char char_array_4[4], char_array_3[3];
-  std::string ret;
-
-  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
-    char_array_4[i++] = encoded_string[in_]; in_++;
-    if (i ==4) {
-      for (i = 0; i <4; i++)
-        char_array_4[i] = base64_chars.find(char_array_4[i]);
-
-      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
-      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
-      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
-
-      for (i = 0; (i < 3); i++)
-        ret += char_array_3[i];
-      i = 0;
-    }
-  }
-
-  if (i) {
-    for (j = i; j <4; j++)
-      char_array_4[j] = 0;
-
-    for (j = 0; j <4; j++)
-      char_array_4[j] = base64_chars.find(char_array_4[j]);
-
-    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
-    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
-    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
-
-    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
-  }
-
-  return ret;
-}
--- a/Resources/Orthanc/Resources/ThirdParty/base64/base64.h	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-#include <string>
-
-std::string base64_encode(const std::string& stringToEncode);
-std::string base64_decode(const std::string& s);
Binary file Resources/Orthanc/Resources/ThirdParty/patch/msys-1.0.dll has changed
Binary file Resources/Orthanc/Resources/ThirdParty/patch/patch.exe has changed
--- a/Resources/Orthanc/Resources/ThirdParty/patch/patch.exe.manifest	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
-  <assemblyIdentity version="7.95.0.0"
-     processorArchitecture="X86"
-     name="patch.exe"
-     type="win32"/>
-
-  <!-- Identify the application security requirements. -->
-  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
-    <security>
-      <requestedPrivileges>
-        <requestedExecutionLevel
-          level="asInvoker"
-          uiAccess="false"/>
-        </requestedPrivileges>
-       </security>
-  </trustInfo>
-</assembly>
-
--- a/Resources/Orthanc/Resources/WindowsResources.py	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-#!/usr/bin/python
-
-# Orthanc - A Lightweight, RESTful DICOM Store
-# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
-# Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017 Osimis, Belgium
-#
-# This program is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# In addition, as a special exception, the copyright holders of this
-# program give permission to link the code of its release with the
-# OpenSSL project's "OpenSSL" library (or with modified versions of it
-# that use the same license as the "OpenSSL" library), and distribute
-# the linked executables. You must obey the GNU General Public License
-# in all respects for all of the code used other than "OpenSSL". If you
-# modify file(s) with this exception, you may extend this exception to
-# your version of the file(s), but you are not obligated to do so. If
-# you do not wish to do so, delete this exception statement from your
-# version. If you delete this exception statement from all source files
-# in the program, then also delete it here.
-# 
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-import os
-import sys
-import datetime
-
-if len(sys.argv) != 5:
-    sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0])
-    sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0])
-    sys.exit(-1)
-
-SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc')
-
-VERSION = sys.argv[1]
-PRODUCT = sys.argv[2]
-FILENAME = sys.argv[3]
-DESCRIPTION = sys.argv[4]
-
-if VERSION == 'mainline':
-    VERSION = '999.999.999'
-    RELEASE = 'This is a mainline build, not an official release'
-else:
-    RELEASE = 'Release %s' % VERSION
-
-v = VERSION.split('.')
-if len(v) != 2 and len(v) != 3:
-    sys.stderr.write('Bad version number: %s\n' % VERSION)
-    sys.exit(-1)
-
-if len(v) == 2:
-    v.append('0')
-
-extension = os.path.splitext(FILENAME)[1]
-if extension.lower() == '.dll':
-    BLOCK = '040904E4'
-    TYPE = 'VFT_DLL'
-elif extension.lower() == '.exe':
-    #BLOCK = '040904B0'   # LANG_ENGLISH/SUBLANG_ENGLISH_US,
-    BLOCK = '040904E4'   # Lang=US English, CharSet=Windows Multilingual
-    TYPE = 'VFT_APP'
-else:
-    sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension)
-    sys.exit(-1)
-
-
-with open(SOURCE, 'r') as source:
-    content = source.read()
-    content = content.replace('${VERSION_MAJOR}', v[0])
-    content = content.replace('${VERSION_MINOR}', v[1])
-    content = content.replace('${VERSION_PATCH}', v[2])
-    content = content.replace('${RELEASE}', RELEASE)
-    content = content.replace('${DESCRIPTION}', DESCRIPTION)
-    content = content.replace('${PRODUCT}', PRODUCT)   
-    content = content.replace('${FILENAME}', FILENAME)   
-    content = content.replace('${YEAR}', str(datetime.datetime.now().year))
-    content = content.replace('${BLOCK}', BLOCK)
-    content = content.replace('${TYPE}', TYPE)
-
-    sys.stdout.write(content)
--- a/Resources/Orthanc/Resources/WindowsResources.rc	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#include <winver.h>
-
-VS_VERSION_INFO VERSIONINFO
-   FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH}
-   PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0
-   FILEOS VOS_NT_WINDOWS32
-   FILETYPE ${TYPE}
-   BEGIN
-      BLOCK "StringFileInfo"
-      BEGIN
-         BLOCK "${BLOCK}"
-         BEGIN
-            VALUE "Comments", "${RELEASE}"
-            VALUE "CompanyName", "University Hospital of Liege, Belgium"
-            VALUE "FileDescription", "${DESCRIPTION}"
-            VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
-            VALUE "InternalName", "${PRODUCT}"
-            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium"
-            VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
-            VALUE "OriginalFilename", "${FILENAME}"
-            VALUE "ProductName", "${PRODUCT}"
-            VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}"
-         END
-      END
-
-      BLOCK "VarFileInfo"
-      BEGIN
-        VALUE "Translation", 0x409, 1252  // U.S. English
-      END
-   END
--- a/Resources/OrthancStone.doxygen	Tue Jan 02 09:51:36 2018 +0100
+++ b/Resources/OrthancStone.doxygen	Tue Mar 20 20:03:02 2018 +0100
@@ -655,7 +655,7 @@
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = @ORTHANC_STONE_DIR@/Framework
+INPUT                  = @ORTHANC_STONE_DIR@/Framework @ORTHANC_STONE_DIR@/Platforms
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
--- a/Resources/SyncOrthancFolder.py	Tue Jan 02 09:51:36 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-#!/usr/bin/python
-
-#
-# This maintenance script updates the content of the "Orthanc" folder
-# to match the latest version of the Orthanc source code.
-#
-
-import sys
-import multiprocessing
-import os
-import stat
-import urllib2
-
-TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
-REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
-
-FILES = [
-    'Core/ChunkedBuffer.cpp',
-    'Core/ChunkedBuffer.h',
-    'Core/Compression/DeflateBaseCompressor.cpp',
-    'Core/Compression/DeflateBaseCompressor.h',
-    'Core/Compression/GzipCompressor.cpp',
-    'Core/Compression/GzipCompressor.h',
-    'Core/Compression/IBufferCompressor.h',
-    'Core/Enumerations.cpp',
-    'Core/Enumerations.h',
-    'Core/HttpClient.cpp',
-    'Core/HttpClient.h',
-    'Core/Images/Image.cpp',
-    'Core/Images/Image.h',
-    'Core/Images/ImageAccessor.cpp',
-    'Core/Images/ImageAccessor.h',
-    'Core/Images/ImageBuffer.cpp',
-    'Core/Images/ImageBuffer.h',
-    'Core/Images/ImageProcessing.cpp',
-    'Core/Images/ImageProcessing.h',
-    'Core/Images/JpegErrorManager.cpp',
-    'Core/Images/JpegErrorManager.h',
-    'Core/Images/JpegReader.cpp',
-    'Core/Images/JpegReader.h',
-    'Core/Images/PngReader.cpp',
-    'Core/Images/PngReader.h',
-    'Core/Logging.cpp',
-    'Core/Logging.h',
-    'Core/OrthancException.h',
-    'Core/PrecompiledHeaders.h',
-    'Core/SystemToolbox.cpp',
-    'Core/SystemToolbox.h',
-    'Core/Toolbox.cpp',
-    'Core/Toolbox.h',
-    'Core/WebServiceParameters.cpp',
-    'Core/WebServiceParameters.h',
-    'Plugins/Samples/Common/DicomDatasetReader.cpp',
-    'Plugins/Samples/Common/DicomDatasetReader.h',
-    'Plugins/Samples/Common/DicomPath.cpp',
-    'Plugins/Samples/Common/DicomPath.h',
-    'Plugins/Samples/Common/DicomTag.cpp',
-    'Plugins/Samples/Common/DicomTag.h',
-    'Plugins/Samples/Common/FullOrthancDataset.cpp',
-    'Plugins/Samples/Common/FullOrthancDataset.h',
-    'Plugins/Samples/Common/IDicomDataset.h',
-    'Plugins/Samples/Common/IOrthancConnection.cpp',
-    'Plugins/Samples/Common/IOrthancConnection.h',
-    'Plugins/Samples/Common/OrthancHttpConnection.cpp',
-    'Plugins/Samples/Common/OrthancHttpConnection.h',
-    'Plugins/Samples/Common/OrthancPluginException.h',
-    'Resources/CMake/AutoGeneratedCode.cmake',
-    'Resources/CMake/BoostConfiguration.cmake',
-    'Resources/CMake/Compiler.cmake',
-    'Resources/CMake/DownloadPackage.cmake',
-    'Resources/CMake/GoogleTestConfiguration.cmake',
-    'Resources/CMake/JsonCppConfiguration.cmake',
-    'Resources/CMake/LibCurlConfiguration.cmake',
-    'Resources/CMake/LibIconvConfiguration.cmake',
-    'Resources/CMake/LibJpegConfiguration.cmake',
-    'Resources/CMake/LibPngConfiguration.cmake',
-    'Resources/CMake/OpenSslConfiguration.cmake',
-    'Resources/CMake/ZlibConfiguration.cmake',
-    'Resources/EmbedResources.py',
-    'Resources/MinGW-W64-Toolchain32.cmake',
-    'Resources/MinGW-W64-Toolchain64.cmake',
-    'Resources/MinGWToolchain.cmake',
-    'Resources/ThirdParty/VisualStudio/stdint.h',
-    'Resources/ThirdParty/base64/base64.cpp',
-    'Resources/ThirdParty/base64/base64.h',
-    'Resources/ThirdParty/patch/msys-1.0.dll',
-    'Resources/ThirdParty/patch/patch.exe',
-    'Resources/ThirdParty/patch/patch.exe.manifest',
-    'Resources/WindowsResources.py',
-    'Resources/WindowsResources.rc',
-]
-
-EXE = [
-    'Resources/WindowsResources.py',
-]
-
-
-def Download(x):
-    branch = x[0]
-    source = x[1]
-    target = os.path.join(TARGET, x[2])
-    print target
-
-    try:
-        os.makedirs(os.path.dirname(target))
-    except:
-        pass
-
-    url = '%s/%s/%s' % (REPOSITORY, branch, source)
-
-    with open(target, 'w') as f:
-        f.write(urllib2.urlopen(url).read())
-
-
-commands = []
-
-for f in FILES:
-    commands.append([ 'default', f, f ])
-
-
-if sys.platform == 'win32':
-    # Sequential execution
-    for c in commands:
-        Download(c)
-else:
-    # Concurrent downloads
-    pool = multiprocessing.Pool(10)
-    pool.map(Download, commands)
-
-
-for exe in EXE:
-    path = os.path.join(TARGET, exe)
-    st = os.stat(path)
-    os.chmod(path, st.st_mode | stat.S_IEXEC)
-
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Jan 02 09:51:36 2018 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Mar 20 20:03:02 2018 +0100
@@ -19,9 +19,711 @@
  **/
 
 
+#include "../Framework/dev.h"
 #include "gtest/gtest.h"
 
-#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Framework/Layers/FrameRenderer.h"
+#include "../Framework/Layers/LayerSourceBase.h"
+#include "../Framework/Toolbox/DownloadStack.h"
+#include "../Framework/Toolbox/FiniteProjectiveCamera.h"
+#include "../Framework/Toolbox/OrthancSlicesLoader.h"
+#include "../Framework/Volumes/ImageBuffer3D.h"
+#include "../Framework/Volumes/SlicedVolumeBase.h"
+#include "../Platforms/Generic/OracleWebService.h"
+
+#include <Core/HttpClient.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
+#include <Core/MultiThreading/SharedMessageQueue.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/thread/thread.hpp> 
+#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_Full);
+      }
+    }
+
+    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;
+  
+  // https://en.wikipedia.org/wiki/Bilinear_interpolation#Application_in_image_processing
+  ASSERT_FLOAT_EQ(146.1f, ComputeBilinearInterpolationUnitSquare(0.5f, 0.2f, 91, 210, 162, 95));
+
+  ASSERT_FLOAT_EQ(91,  ComputeBilinearInterpolationUnitSquare(0, 0, 91, 210, 162, 95));
+  ASSERT_FLOAT_EQ(210, ComputeBilinearInterpolationUnitSquare(1, 0, 91, 210, 162, 95));
+  ASSERT_FLOAT_EQ(162, ComputeBilinearInterpolationUnitSquare(0, 1, 91, 210, 162, 95));
+  ASSERT_FLOAT_EQ(95,  ComputeBilinearInterpolationUnitSquare(1, 1, 91, 210, 162, 95));
+
+  ASSERT_FLOAT_EQ(123.35f, ComputeTrilinearInterpolationUnitSquare
+                  (0.5f, 0.2f, 0.7f,
+                   91, 210, 162, 95,
+                   51, 190, 80, 92));
+
+  ASSERT_FLOAT_EQ(ComputeBilinearInterpolationUnitSquare(0.5f, 0.2f, 91, 210, 162, 95),
+                  ComputeTrilinearInterpolationUnitSquare(0.5f, 0.2f, 0,
+                                                          91, 210, 162, 95,
+                                                          51, 190, 80, 92));
+
+  ASSERT_FLOAT_EQ(ComputeBilinearInterpolationUnitSquare(0.5f, 0.2f, 51, 190, 80, 92),
+                  ComputeTrilinearInterpolationUnitSquare(0.5f, 0.2f, 1,
+                                                          91, 210, 162, 95,
+                                                          51, 190, 80, 92));
+}
+
+
+static bool CompareMatrix(const OrthancStone::Matrix& a,
+                          const OrthancStone::Matrix& b,
+                          double threshold = 0.00000001)
+{
+  if (a.size1() != b.size1() ||
+      a.size2() != b.size2())
+  {
+    return false;
+  }
+
+  for (size_t i = 0; i < a.size1(); i++)
+  {
+    for (size_t j = 0; j < a.size2(); j++)
+    {
+      if (fabs(a(i, j) - b(i, j)) > threshold)
+      {
+        LOG(ERROR) << "Too large difference in component ("
+                   << i << "," << j << "): " << a(i,j) << " != " << b(i,j);
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+
+static bool CompareVector(const OrthancStone::Vector& a,
+                          const OrthancStone::Vector& b,
+                          double threshold = 0.00000001)
+{
+  if (a.size() != b.size())
+  {
+    return false;
+  }
+
+  for (size_t i = 0; i < a.size(); i++)
+  {
+    if (fabs(a(i) - b(i)) > threshold)
+    {
+      LOG(ERROR) << "Too large difference in component "
+                 << i << ": " << a(i) << " != " << b(i);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+
+
+TEST(FiniteProjectiveCamera, Decomposition1)
+{
+  // Example 6.2 of "Multiple View Geometry in Computer Vision - 2nd
+  // edition" (page 163)
+  const double p[12] = {
+    3.53553e+2,  3.39645e+2,  2.77744e+2, -1.44946e+6,
+    -1.03528e+2, 2.33212e+1,  4.59607e+2, -6.32525e+5,
+    7.07107e-1,  -3.53553e-1, 6.12372e-1, -9.18559e+2
+  };
+
+  OrthancStone::FiniteProjectiveCamera camera(p);
+  ASSERT_EQ(3u, camera.GetMatrix().size1());
+  ASSERT_EQ(4u, camera.GetMatrix().size2());
+  ASSERT_EQ(3u, camera.GetIntrinsicParameters().size1());
+  ASSERT_EQ(3u, camera.GetIntrinsicParameters().size2());
+  ASSERT_EQ(3u, camera.GetRotation().size1());
+  ASSERT_EQ(3u, camera.GetRotation().size2());
+  ASSERT_EQ(3u, camera.GetCenter().size());
+
+  ASSERT_NEAR(1000.0, camera.GetCenter()[0], 0.01);
+  ASSERT_NEAR(2000.0, camera.GetCenter()[1], 0.01);
+  ASSERT_NEAR(1500.0, camera.GetCenter()[2], 0.01);
+
+  ASSERT_NEAR(468.2, camera.GetIntrinsicParameters() (0, 0), 0.1);
+  ASSERT_NEAR(91.2,  camera.GetIntrinsicParameters() (0, 1), 0.1);
+  ASSERT_NEAR(300.0, camera.GetIntrinsicParameters() (0, 2), 0.1);
+  ASSERT_NEAR(427.2, camera.GetIntrinsicParameters() (1, 1), 0.1);
+  ASSERT_NEAR(200.0, camera.GetIntrinsicParameters() (1, 2), 0.1);
+  ASSERT_NEAR(1.0,   camera.GetIntrinsicParameters() (2, 2), 0.1);
+
+  ASSERT_NEAR(0, camera.GetIntrinsicParameters() (1, 0), 0.0000001);
+  ASSERT_NEAR(0, camera.GetIntrinsicParameters() (2, 0), 0.0000001);
+  ASSERT_NEAR(0, camera.GetIntrinsicParameters() (2, 1), 0.0000001);
+
+  ASSERT_NEAR(0.41380,  camera.GetRotation() (0, 0), 0.00001);
+  ASSERT_NEAR(0.90915,  camera.GetRotation() (0, 1), 0.00001);
+  ASSERT_NEAR(0.04708,  camera.GetRotation() (0, 2), 0.00001);
+  ASSERT_NEAR(-0.57338, camera.GetRotation() (1, 0), 0.00001);
+  ASSERT_NEAR(0.22011,  camera.GetRotation() (1, 1), 0.00001);
+  ASSERT_NEAR(0.78917,  camera.GetRotation() (1, 2), 0.00001);
+  ASSERT_NEAR(0.70711,  camera.GetRotation() (2, 0), 0.00001);
+  ASSERT_NEAR(-0.35355, camera.GetRotation() (2, 1), 0.00001);
+  ASSERT_NEAR(0.61237,  camera.GetRotation() (2, 2), 0.00001);
+
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(camera.GetRotation()));
+
+  OrthancStone::FiniteProjectiveCamera camera2(camera.GetIntrinsicParameters(),
+                                               camera.GetRotation(),
+                                               camera.GetCenter());
+
+  ASSERT_TRUE(CompareMatrix(camera.GetMatrix(), camera2.GetMatrix()));
+  ASSERT_TRUE(CompareMatrix(camera.GetIntrinsicParameters(), camera2.GetIntrinsicParameters()));
+  ASSERT_TRUE(CompareMatrix(camera.GetRotation(), camera2.GetRotation()));
+  ASSERT_TRUE(CompareVector(camera.GetCenter(), camera2.GetCenter()));
+}
+
+
+TEST(FiniteProjectiveCamera, Decomposition2)
+{
+  const double p[] = { 1188.111986, 580.205341, -808.445330, 128000.000000, -366.466264, 1446.510501, 418.499736, 128000.000000, -0.487118, 0.291726, -0.823172, 500.000000 };
+  const double k[] = { -1528.494743, 0.000000, 256.000000, 0.000000, 1528.494743, 256.000000, 0.000000, 0.000000, 1.000000 };
+  const double r[] = { -0.858893, -0.330733, 0.391047, -0.158171, 0.897503, 0.411668, -0.487118, 0.291726, -0.823172 };
+  const double c[] = { 243.558936, -145.863085, 411.585964 };
+
+  OrthancStone::FiniteProjectiveCamera camera(p);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(camera.GetRotation()));
+
+  OrthancStone::FiniteProjectiveCamera camera2(k, r, c);
+  ASSERT_TRUE(CompareMatrix(camera.GetMatrix(), camera2.GetMatrix(), 1));
+  ASSERT_TRUE(CompareMatrix(camera.GetIntrinsicParameters(), camera2.GetIntrinsicParameters(), 0.001));
+  ASSERT_TRUE(CompareMatrix(camera.GetRotation(), camera2.GetRotation(), 0.000001));
+  ASSERT_TRUE(CompareVector(camera.GetCenter(), camera2.GetCenter(), 0.0001));
+}
+
+
+TEST(FiniteProjectiveCamera, Decomposition3)
+{
+  const double p[] = { 10, 0, 0, 0,
+                       0, 20, 0, 0,
+                       0, 0, 30, 0 };
+
+  OrthancStone::FiniteProjectiveCamera camera(p);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(camera.GetRotation()));
+  ASSERT_DOUBLE_EQ(10, camera.GetIntrinsicParameters() (0, 0));
+  ASSERT_DOUBLE_EQ(20, camera.GetIntrinsicParameters() (1, 1));
+  ASSERT_DOUBLE_EQ(30, camera.GetIntrinsicParameters() (2, 2));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (0, 0));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (1, 1));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (2, 2));
+  ASSERT_DOUBLE_EQ(0, camera.GetCenter() (0));
+  ASSERT_DOUBLE_EQ(0, camera.GetCenter() (1));
+  ASSERT_DOUBLE_EQ(0, camera.GetCenter() (2));
+}
+
+
+TEST(FiniteProjectiveCamera, Decomposition4)
+{
+  const double p[] = { 1, 0, 0, 10,
+                       0, 1, 0, 20,
+                       0, 0, 1, 30 };
+
+  OrthancStone::FiniteProjectiveCamera camera(p);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(camera.GetRotation()));
+  ASSERT_DOUBLE_EQ(1, camera.GetIntrinsicParameters() (0, 0));
+  ASSERT_DOUBLE_EQ(1, camera.GetIntrinsicParameters() (1, 1));
+  ASSERT_DOUBLE_EQ(1, camera.GetIntrinsicParameters() (2, 2));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (0, 0));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (1, 1));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (2, 2));
+  ASSERT_DOUBLE_EQ(-10, camera.GetCenter() (0));
+  ASSERT_DOUBLE_EQ(-20, camera.GetCenter() (1));
+  ASSERT_DOUBLE_EQ(-30, camera.GetCenter() (2));
+}
+
+
+TEST(FiniteProjectiveCamera, Decomposition5)
+{
+  const double p[] = { 0, 0, 10, 0,
+                       0, 20, 0, 0,
+                       30, 0, 0, 0 };
+
+  OrthancStone::FiniteProjectiveCamera camera(p);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(camera.GetRotation()));
+  ASSERT_DOUBLE_EQ(-10, camera.GetIntrinsicParameters() (0, 0));
+  ASSERT_DOUBLE_EQ(20, camera.GetIntrinsicParameters() (1, 1));
+  ASSERT_DOUBLE_EQ(30, camera.GetIntrinsicParameters() (2, 2));
+  ASSERT_DOUBLE_EQ(-1, camera.GetRotation() (0, 2));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (1, 1));
+  ASSERT_DOUBLE_EQ(1, camera.GetRotation() (2, 0));
+  ASSERT_DOUBLE_EQ(0, camera.GetCenter() (0));
+  ASSERT_DOUBLE_EQ(0, camera.GetCenter() (1));
+  ASSERT_DOUBLE_EQ(0, camera.GetCenter() (2));
+
+  OrthancStone::FiniteProjectiveCamera camera2(camera.GetIntrinsicParameters(),
+                                               camera.GetRotation(),
+                                               camera.GetCenter());
+  ASSERT_TRUE(CompareMatrix(camera.GetMatrix(), camera2.GetMatrix()));
+  ASSERT_TRUE(CompareMatrix(camera.GetIntrinsicParameters(), camera2.GetIntrinsicParameters()));
+  ASSERT_TRUE(CompareMatrix(camera.GetRotation(), camera2.GetRotation()));
+  ASSERT_TRUE(CompareVector(camera.GetCenter(), camera2.GetCenter()));
+}
+
+
+static double GetCosAngle(const OrthancStone::Vector& a,
+                          const OrthancStone::Vector& b)
+{
+  // Returns the cosine of the angle between two vectors
+  // https://en.wikipedia.org/wiki/Dot_product#Geometric_definition
+  return boost::numeric::ublas::inner_prod(a, b) / 
+    (boost::numeric::ublas::norm_2(a) * boost::numeric::ublas::norm_2(b));
+}
+
+
+TEST(FiniteProjectiveCamera, Ray)
+{
+  const double pp[] = { -1499.650894, 2954.618773, -259.737419, 637891.819097,
+                        -2951.517707, -1501.019129, -285.785281, 637891.819097,
+                        0.008528, 0.003067, -0.999959, 2491.764918 };
+
+  const OrthancStone::FiniteProjectiveCamera camera(pp);
+
+  ASSERT_NEAR(-21.2492, camera.GetCenter() (0), 0.0001);
+  ASSERT_NEAR(-7.64234, camera.GetCenter() (1), 0.00001);
+  ASSERT_NEAR(2491.66, camera.GetCenter() (2), 0.01);
+
+  // Image plane that led to these parameters, with principal point at
+  // (256,256). The image has dimensions 512x512.
+  OrthancStone::Vector o =
+    OrthancStone::LinearAlgebra::CreateVector(7.009620, 2.521030, -821.942000);
+  OrthancStone::Vector ax =
+    OrthancStone::LinearAlgebra::CreateVector(-0.453219, 0.891399, -0.001131);
+  OrthancStone::Vector ay =
+    OrthancStone::LinearAlgebra::CreateVector(-0.891359, -0.453210, -0.008992);
+
+  OrthancStone::CoordinateSystem3D imagePlane(o, ax, ay);
+
+  // Back-projection of the principal point
+  {
+    OrthancStone::Vector ray = camera.GetRayDirection(256, 256);
+
+    // The principal axis vector is orthogonal to the image plane
+    // (i.e. parallel to the plane normal), in the opposite direction
+    // ("-1" corresponds to "cos(pi)").
+    ASSERT_NEAR(-1, GetCosAngle(ray, imagePlane.GetNormal()), 0.0000001);
+
+    // Forward projection of principal axis, resulting in the principal point
+    double x, y;
+    camera.ApplyFinite(x, y, camera.GetCenter() - ray);
+
+    ASSERT_NEAR(256, x, 0.00001);
+    ASSERT_NEAR(256, y, 0.00001);
+  }
+
+  // Back-projection of the 4 corners of the image
+  std::vector<double> cx, cy;
+  cx.push_back(0);
+  cy.push_back(0);
+  cx.push_back(512);
+  cy.push_back(0);
+  cx.push_back(512);
+  cy.push_back(512);
+  cx.push_back(0);
+  cy.push_back(512);
+
+  bool first = true;
+  double angle;
+
+  for (size_t i = 0; i < cx.size(); i++)
+  {
+    OrthancStone::Vector ray = camera.GetRayDirection(cx[i], cy[i]);
+
+    // Check that the angle wrt. principal axis is the same for all
+    // the 4 corners
+    double a = GetCosAngle(ray, imagePlane.GetNormal());
+    if (first)
+    {
+      first = false;
+      angle = a;
+    }
+    else
+    {
+      ASSERT_NEAR(angle, a, 0.000001);
+    }
+
+    // Forward projection of the ray, going back to the original point
+    double x, y;
+    camera.ApplyFinite(x, y, camera.GetCenter() - ray);
+    
+    ASSERT_NEAR(cx[i], x, 0.00001);
+    ASSERT_NEAR(cy[i], y, 0.00001);
+
+    // Alternative construction, by computing the intersection of the
+    // ray with the image plane
+    OrthancStone::Vector p;
+    ASSERT_TRUE(imagePlane.IntersectLine(p, camera.GetCenter(), -ray));
+    imagePlane.ProjectPoint(x, y, p);
+    ASSERT_NEAR(cx[i], x + 256, 0.01);
+    ASSERT_NEAR(cy[i], y + 256, 0.01);
+  }
+}
+
+
+TEST(Matrix, Inverse1)
+{
+  OrthancStone::Matrix a, b;
+
+  a.resize(0, 0);
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+  ASSERT_EQ(0u, b.size1());
+  ASSERT_EQ(0u, b.size2());
+
+  a.resize(2, 3);
+  ASSERT_THROW(OrthancStone::LinearAlgebra::InvertMatrix(b, a), Orthanc::OrthancException);
+
+  a.resize(1, 1);
+  a(0, 0) = 45.0;
+
+  ASSERT_DOUBLE_EQ(45, OrthancStone::LinearAlgebra::ComputeDeterminant(a));
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+  ASSERT_EQ(1u, b.size1());
+  ASSERT_EQ(1u, b.size2());
+  ASSERT_DOUBLE_EQ(1.0 / 45.0, b(0, 0));
+
+  a(0, 0) = 0;
+  ASSERT_DOUBLE_EQ(0, OrthancStone::LinearAlgebra::ComputeDeterminant(a));
+  ASSERT_THROW(OrthancStone::LinearAlgebra::InvertMatrix(b, a), Orthanc::OrthancException);
+}
+
+
+TEST(Matrix, Inverse2)
+{
+  OrthancStone::Matrix a, b;
+  a.resize(2, 2);
+  a(0, 0) = 4;
+  a(0, 1) = 3;
+  a(1, 0) = 3;
+  a(1, 1) = 2;
+
+  ASSERT_DOUBLE_EQ(-1, OrthancStone::LinearAlgebra::ComputeDeterminant(a));
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+  ASSERT_EQ(2u, b.size1());
+  ASSERT_EQ(2u, b.size2());
+
+  ASSERT_DOUBLE_EQ(-2, b(0, 0));
+  ASSERT_DOUBLE_EQ(3, b(0, 1));
+  ASSERT_DOUBLE_EQ(3, b(1, 0));
+  ASSERT_DOUBLE_EQ(-4, b(1, 1));
+
+  a(0, 0) = 1;
+  a(0, 1) = 2;
+  a(1, 0) = 3;
+  a(1, 1) = 4;
+
+  ASSERT_DOUBLE_EQ(-2, OrthancStone::LinearAlgebra::ComputeDeterminant(a));
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+
+  ASSERT_DOUBLE_EQ(-2, b(0, 0));
+  ASSERT_DOUBLE_EQ(1, b(0, 1));
+  ASSERT_DOUBLE_EQ(1.5, b(1, 0));
+  ASSERT_DOUBLE_EQ(-0.5, b(1, 1));
+}
+
+
+TEST(Matrix, Inverse3)
+{
+  OrthancStone::Matrix a, b;
+  a.resize(3, 3);
+  a(0, 0) = 7;
+  a(0, 1) = 2;
+  a(0, 2) = 1;
+  a(1, 0) = 0;
+  a(1, 1) = 3;
+  a(1, 2) = -1;
+  a(2, 0) = -3;
+  a(2, 1) = 4;
+  a(2, 2) = -2;
+
+  ASSERT_DOUBLE_EQ(1, OrthancStone::LinearAlgebra::ComputeDeterminant(a));
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+  ASSERT_EQ(3u, b.size1());
+  ASSERT_EQ(3u, b.size2());
+
+  ASSERT_DOUBLE_EQ(-2, b(0, 0));
+  ASSERT_DOUBLE_EQ(8, b(0, 1));
+  ASSERT_DOUBLE_EQ(-5, b(0, 2));
+  ASSERT_DOUBLE_EQ(3, b(1, 0));
+  ASSERT_DOUBLE_EQ(-11, b(1, 1));
+  ASSERT_DOUBLE_EQ(7, b(1, 2));
+  ASSERT_DOUBLE_EQ(9, b(2, 0));
+  ASSERT_DOUBLE_EQ(-34, b(2, 1));
+  ASSERT_DOUBLE_EQ(21, b(2, 2));
+
+
+  a(0, 0) = 1;
+  a(0, 1) = 2;
+  a(0, 2) = 2;
+  a(1, 0) = 1;
+  a(1, 1) = 0;
+  a(1, 2) = 1;
+  a(2, 0) = 1;
+  a(2, 1) = 2;
+  a(2, 2) = 1;
+
+  ASSERT_DOUBLE_EQ(2, OrthancStone::LinearAlgebra::ComputeDeterminant(a));
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+  ASSERT_EQ(3u, b.size1());
+  ASSERT_EQ(3u, b.size2());
+
+  ASSERT_DOUBLE_EQ(-1, b(0, 0));
+  ASSERT_DOUBLE_EQ(1, b(0, 1));
+  ASSERT_DOUBLE_EQ(1, b(0, 2));
+  ASSERT_DOUBLE_EQ(0, b(1, 0));
+  ASSERT_DOUBLE_EQ(-0.5, b(1, 1));
+  ASSERT_DOUBLE_EQ(0.5, b(1, 2));
+  ASSERT_DOUBLE_EQ(1, b(2, 0));
+  ASSERT_DOUBLE_EQ(0, b(2, 1));
+  ASSERT_DOUBLE_EQ(-1, b(2, 2));
+}
+
+
+TEST(Matrix, Inverse4)
+{
+  OrthancStone::Matrix a, b;
+  a.resize(4, 4);
+  a(0, 0) = 2;
+  a(0, 1) = 1;
+  a(0, 2) = 2;
+  a(0, 3) = -3;
+  a(1, 0) = -2;
+  a(1, 1) = 2;
+  a(1, 2) = -1;
+  a(1, 3) = -1;
+  a(2, 0) = 2;
+  a(2, 1) = 2;
+  a(2, 2) = -3;
+  a(2, 3) = -1;
+  a(3, 0) = 3;
+  a(3, 1) = -2;
+  a(3, 2) = -3;
+  a(3, 3) = -1;
+
+  OrthancStone::LinearAlgebra::InvertMatrix(b, a);
+  ASSERT_EQ(4u, b.size1());
+  ASSERT_EQ(4u, b.size2());
+
+  b *= 134.0;  // This is the determinant
+
+  ASSERT_DOUBLE_EQ(8, b(0, 0));
+  ASSERT_DOUBLE_EQ(-44, b(0, 1));
+  ASSERT_DOUBLE_EQ(30, b(0, 2));
+  ASSERT_DOUBLE_EQ(-10, b(0, 3));
+  ASSERT_DOUBLE_EQ(2, b(1, 0));
+  ASSERT_DOUBLE_EQ(-11, b(1, 1));
+  ASSERT_DOUBLE_EQ(41, b(1, 2));
+  ASSERT_DOUBLE_EQ(-36, b(1, 3));
+  ASSERT_DOUBLE_EQ(16, b(2, 0));
+  ASSERT_DOUBLE_EQ(-21, b(2, 1));
+  ASSERT_DOUBLE_EQ(-7, b(2, 2));
+  ASSERT_DOUBLE_EQ(-20, b(2, 3));
+  ASSERT_DOUBLE_EQ(-28, b(3, 0));
+  ASSERT_DOUBLE_EQ(-47, b(3, 1));
+  ASSERT_DOUBLE_EQ(29, b(3, 2));
+  ASSERT_DOUBLE_EQ(-32, b(3, 3));
+}
+
+
+TEST(FiniteProjectiveCamera, Calibration)
+{
+  unsigned int volumeWidth = 512;
+  unsigned int volumeHeight = 512;
+  unsigned int volumeDepth = 110;
+
+  OrthancStone::Vector camera = OrthancStone::LinearAlgebra::CreateVector
+    (-1000, -5000, -static_cast<double>(volumeDepth) * 32);
+
+  OrthancStone::Vector principalPoint = OrthancStone::LinearAlgebra::CreateVector
+    (volumeWidth/2, volumeHeight/2, volumeDepth * 2);
+
+  OrthancStone::FiniteProjectiveCamera c(camera, principalPoint, 0, 512, 512, 1, 1);
+
+  double swapv[9] = { 1, 0, 0,
+                      0, -1, 512,
+                      0, 0, 1 };
+  OrthancStone::Matrix swap;
+  OrthancStone::LinearAlgebra::FillMatrix(swap, 3, 3, swapv);
+
+  OrthancStone::Matrix p = OrthancStone::LinearAlgebra::Product(swap, c.GetMatrix());
+  p /= p(2,3);
+
+  ASSERT_NEAR( 1.04437,     p(0,0), 0.00001);
+  ASSERT_NEAR(-0.0703111,   p(0,1), 0.00000001);
+  ASSERT_NEAR(-0.179283,    p(0,2), 0.000001);
+  ASSERT_NEAR( 61.7431,     p(0,3), 0.0001);
+  ASSERT_NEAR( 0.11127,     p(1,0), 0.000001);
+  ASSERT_NEAR(-0.595541,    p(1,1), 0.000001);
+  ASSERT_NEAR( 0.872211,    p(1,2), 0.000001);
+  ASSERT_NEAR( 203.748,     p(1,3), 0.001);
+  ASSERT_NEAR( 3.08593e-05, p(2,0), 0.0000000001);
+  ASSERT_NEAR( 0.000129138, p(2,1), 0.000000001);
+  ASSERT_NEAR( 9.18901e-05, p(2,2), 0.0000000001);
+  ASSERT_NEAR( 1,           p(2,3), 0.0000001);
+}
+
+
+static bool IsEqualVector(OrthancStone::Vector a,
+                          OrthancStone::Vector b)
+{
+  if (a.size() == 3 &&
+      b.size() == 3)
+  {
+    OrthancStone::LinearAlgebra::NormalizeVector(a);
+    OrthancStone::LinearAlgebra::NormalizeVector(b);
+    return OrthancStone::LinearAlgebra::IsCloseToZero(boost::numeric::ublas::norm_2(a - b));
+  }
+  else
+  {
+    return false;
+  } 
+}
+
+
+TEST(GeometryToolbox, AlignVectorsWithRotation)
+{
+  OrthancStone::Vector a, b;
+  OrthancStone::Matrix r;
+
+  OrthancStone::LinearAlgebra::AssignVector(a, -200, 200, -846.63);
+  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+
+  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
+  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+
+  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, b, a);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
+  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, b), a));
+
+  OrthancStone::LinearAlgebra::AssignVector(a, 1, 0, 0);
+  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
+  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+
+  OrthancStone::LinearAlgebra::AssignVector(a, 0, 1, 0);
+  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
+  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+
+  OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, 1);
+  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
+  ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r));
+  ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b));
+
+  OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, 0);
+  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+  ASSERT_THROW(OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b), Orthanc::OrthancException);
+
+  // TODO: Deal with opposite vectors
+
+  /*
+  OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, -1);
+  OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1);
+  OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b);
+  OrthancStone::LinearAlgebra::Print(r);
+  OrthancStone::LinearAlgebra::Print(boost::numeric::ublas::prod(r, a));
+  */
+}
+
 
 int main(int argc, char **argv)
 {