changeset 51:b340879da9bd

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 27 Apr 2017 14:50:20 +0200
parents c45f368de5c0
children 37e504582af6 ea377c770ef4
files Applications/BasicApplicationContext.cpp Applications/BasicApplicationContext.h Applications/BinarySemaphore.cpp Applications/BinarySemaphore.h Applications/IBasicApplication.cpp Applications/IBasicApplication.h Applications/Samples/BasicPetCtFusionApplication.h Applications/Samples/EmptyApplication.h Applications/Samples/LayoutPetCtFusionApplication.h Applications/Samples/SampleApplicationBase.h Applications/Samples/SampleInteractor.h Applications/Samples/SampleMainSdl.cpp Applications/Samples/SingleFrameApplication.h Applications/Samples/SingleVolumeApplication.h Applications/Samples/SynchronizedSeriesApplication.h Applications/Samples/TestPatternApplication.h Applications/Sdl/SdlBuffering.cpp Applications/Sdl/SdlBuffering.h Applications/Sdl/SdlEngine.cpp Applications/Sdl/SdlEngine.h Applications/Sdl/SdlWindow.cpp Applications/Sdl/SdlWindow.h CMakeLists.txt Framework/Applications/BasicApplicationContext.cpp Framework/Applications/BasicApplicationContext.h Framework/Applications/IBasicApplication.cpp Framework/Applications/IBasicApplication.h Framework/Applications/Sdl/SdlBuffering.cpp Framework/Applications/Sdl/SdlBuffering.h Framework/Applications/Sdl/SdlEngine.cpp Framework/Applications/Sdl/SdlEngine.h Framework/Applications/Sdl/SdlWindow.cpp Framework/Applications/Sdl/SdlWindow.h Framework/Toolbox/BinarySemaphore.cpp Framework/Toolbox/BinarySemaphore.h Resources/CMake/OrthancStone.cmake Samples/BasicPetCtFusionApplication.h Samples/EmptyApplication.h Samples/LayoutPetCtFusionApplication.h Samples/SampleApplicationBase.h Samples/SampleInteractor.h Samples/SampleMainSdl.cpp Samples/SingleFrameApplication.h Samples/SingleVolumeApplication.h Samples/SynchronizedSeriesApplication.h Samples/TestPatternApplication.h
diffstat 46 files changed, 2880 insertions(+), 2879 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/BasicApplicationContext.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,135 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "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)
+  {
+  }
+
+
+  BasicApplicationContext::~BasicApplicationContext()
+  {
+    for (Interactors::iterator it = interactors_.begin(); it != interactors_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+
+    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+
+    for (StructureSets::iterator it = structureSets_.begin(); it != structureSets_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+  }
+
+
+  IWidget& BasicApplicationContext::SetCentralWidget(IWidget* widget)   // Takes ownership
+  {
+    viewport_.SetCentralWidget(widget);
+    return *widget;
+  }
+
+
+  VolumeImage& BasicApplicationContext::AddSeriesVolume(const std::string& series,
+                                                        bool isProgressiveDownload,
+                                                        size_t downloadThreadCount)
+  {
+    std::auto_ptr<VolumeImage> volume(new VolumeImage(new OrthancSeriesLoader(orthanc_, series)));
+
+    if (isProgressiveDownload)
+    {
+      volume->SetDownloadPolicy(new VolumeImageProgressivePolicy);
+    }
+    else
+    {
+      volume->SetDownloadPolicy(new VolumeImageSimplePolicy);
+    }
+
+    volume->SetThreadCount(downloadThreadCount);
+
+    VolumeImage& result = *volume;
+    volumes_.push_back(volume.release());
+
+    return result;
+  }
+
+
+  DicomStructureSet& BasicApplicationContext::AddStructureSet(const std::string& instance)
+  {
+    std::auto_ptr<DicomStructureSet> structureSet(new DicomStructureSet(orthanc_, instance));
+
+    DicomStructureSet& result = *structureSet;
+    structureSets_.push_back(structureSet.release());
+
+    return result;
+  }
+
+
+  IWorldSceneInteractor& BasicApplicationContext::AddInteractor(IWorldSceneInteractor* interactor)
+  {
+    if (interactor == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    interactors_.push_back(interactor);
+
+    return *interactor;
+  }
+
+
+  void BasicApplicationContext::Start()
+  {
+    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    {
+      assert(*it != NULL);
+      (*it)->Start();
+    }
+
+    viewport_.Start();
+  }
+
+
+  void BasicApplicationContext::Stop()
+  {
+    viewport_.Stop();
+
+    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
+    {
+      assert(*it != NULL);
+      (*it)->Stop();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/BasicApplicationContext.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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/Volumes/VolumeImage.h"
+#include "../../Framework/Viewport/WidgetViewport.h"
+#include "../../Framework/Widgets/IWorldSceneInteractor.h"
+#include "../../Framework/Toolbox/DicomStructureSet.h"
+
+#include <list>
+
+namespace OrthancStone
+{
+  class BasicApplicationContext : public boost::noncopyable
+  {
+  private:
+    typedef std::list<ISliceableVolume*>       Volumes;
+    typedef std::list<IWorldSceneInteractor*>  Interactors;
+    typedef std::list<DicomStructureSet*>      StructureSets;
+
+    OrthancPlugins::IOrthancConnection&  orthanc_;
+
+    WidgetViewport   viewport_;
+    Volumes          volumes_;
+    Interactors      interactors_;
+    StructureSets    structureSets_;
+
+  public:
+    BasicApplicationContext(OrthancPlugins::IOrthancConnection& orthanc);
+
+    ~BasicApplicationContext();
+
+    IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
+
+    IViewport& GetViewport()
+    {
+      return viewport_;
+    }
+
+    OrthancPlugins::IOrthancConnection& GetOrthancConnection()
+    {
+      return orthanc_;
+    }
+
+    VolumeImage& AddSeriesVolume(const std::string& series,
+                                 bool isProgressiveDownload,
+                                 size_t downloadThreadCount);
+
+    DicomStructureSet& AddStructureSet(const std::string& instance);
+
+    IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor);
+
+    void Start();
+
+    void Stop();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/BinarySemaphore.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,50 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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/Applications/BinarySemaphore.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,43 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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/Applications/IBasicApplication.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,273 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "IBasicApplication.h"
+
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/HttpClient.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h"
+#include "Sdl/SdlEngine.h"
+
+namespace OrthancStone
+{
+  // Anonymous namespace to avoid clashes against other compilation modules
+  namespace
+  {
+    class LogStatusBar : public IStatusBar
+    {
+    public:
+      virtual void ClearMessage()
+      {
+      }
+
+      virtual void SetMessage(const std::string& message)
+      {
+        LOG(WARNING) << message;
+      }
+    };
+  }
+
+
+#if ORTHANC_ENABLE_SDL == 1
+  static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options)
+  {
+    // Declare the supported parameters
+    boost::program_options::options_description generic("Generic options");
+    generic.add_options()
+      ("help", "Display this help and exit")
+      ("verbose", "Be verbose in logs")
+      ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
+       "URL to the Orthanc server")
+      ("username", "Username for the Orthanc server")
+      ("password", "Password for the Orthanc server")
+      ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates")
+      ;
+
+    options.add(generic);
+
+    boost::program_options::options_description sdl("SDL options");
+    sdl.add_options()
+      ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window")
+      ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window")
+      ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL")
+      ;
+
+    options.add(sdl);
+  }
+
+
+  int IBasicApplication::ExecuteWithSdl(IBasicApplication& application,
+                                        int argc, 
+                                        char* argv[])
+  {
+    /******************************************************************
+     * Initialize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    Orthanc::Logging::Initialize();
+    Orthanc::HttpClient::InitializeOpenSsl();
+    Orthanc::HttpClient::GlobalInitialize();
+    SdlEngine::GlobalInitialize();
+
+
+    /******************************************************************
+     * Declare and parse the command-line options of the application
+     ******************************************************************/
+
+    boost::program_options::options_description options;
+    DeclareSdlCommandLineOptions(options);   
+    application.DeclareCommandLineOptions(options);
+
+    boost::program_options::variables_map parameters;
+    bool error = false;
+
+    try
+    {
+      boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
+                                    options(options).run(), parameters);
+      boost::program_options::notify(parameters);    
+    }
+    catch (boost::program_options::error& e)
+    {
+      LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
+      error = true;
+    }
+
+
+    /******************************************************************
+     * Configure the application with the command-line parameters
+     ******************************************************************/
+
+    if (error || parameters.count("help")) 
+    {
+      std::cout << std::endl
+                << "Usage: " << argv[0] << " [OPTION]..."
+                << std::endl
+                << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
+                << std::endl << std::endl
+                << "Demonstration application of Orthanc Stone using SDL."
+                << std::endl;
+
+      std::cout << options << "\n";
+      return error ? -1 : 0;
+    }
+
+    if (parameters.count("https-verify") &&
+        !parameters["https-verify"].as<bool>())
+    {
+      LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)";
+      Orthanc::HttpClient::ConfigureSsl(false, "");
+    }
+
+    if (parameters.count("verbose"))
+    {
+      Orthanc::Logging::EnableInfoLevel(true);
+    }
+
+    if (!parameters.count("width") ||
+        !parameters.count("height") ||
+        !parameters.count("opengl"))
+    {
+      LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing";
+      return -1;
+    }
+
+    int w = parameters["width"].as<int>();
+    int h = parameters["height"].as<int>();
+    if (w <= 0 || h <= 0)
+    {
+      LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive";
+      return -1;
+    }
+
+    unsigned int width = static_cast<unsigned int>(w);
+    unsigned int height = static_cast<unsigned int>(h);
+    LOG(WARNING) << "Initial display size: " << width << "x" << height;
+
+    bool opengl = parameters["opengl"].as<bool>();
+    if (opengl)
+    {
+      LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes";
+    }
+    else
+    {
+      LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance";
+    }
+
+    bool success = true;
+    try
+    {
+      /****************************************************************
+       * Initialize the connection to the Orthanc server
+       ****************************************************************/
+
+      Orthanc::WebServiceParameters webService;
+
+      if (parameters.count("orthanc"))
+      {
+        webService.SetUrl(parameters["orthanc"].as<std::string>());
+      }
+
+      if (parameters.count("username"))
+      {
+        webService.SetUsername(parameters["username"].as<std::string>());
+      }
+
+      if (parameters.count("password"))
+      {
+        webService.SetPassword(parameters["password"].as<std::string>());
+      }
+
+      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);
+      }
+
+
+      /****************************************************************
+       * Initialize the application
+       ****************************************************************/
+
+      LogStatusBar statusBar;
+      BasicApplicationContext context(orthanc);
+
+      application.Initialize(context, statusBar, parameters);
+      context.GetViewport().SetStatusBar(statusBar);
+
+      std::string title = application.GetTitle();
+      if (title.empty())
+      {
+        title = "Stone of Orthanc";
+      }
+
+      context.Start();
+
+      {
+        /**************************************************************
+         * Run the application inside a SDL window
+         **************************************************************/
+
+        LOG(WARNING) << "Starting the application";
+
+        SdlWindow window(title.c_str(), width, height, opengl);
+        SdlEngine sdl(window, context.GetViewport());
+
+        sdl.Run();
+
+        LOG(WARNING) << "Stopping the application";
+      }
+
+
+      /****************************************************************
+       * Finalize the application
+       ****************************************************************/
+
+      context.Stop();
+
+      LOG(WARNING) << "The application has stopped";
+
+      context.GetViewport().ResetStatusBar();
+      application.Finalize();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "EXCEPTION: " << e.What();
+      success = false;
+    }
+
+
+    /******************************************************************
+     * Finalize all the subcomponents of Orthanc Stone
+     ******************************************************************/
+
+    SdlEngine::GlobalFinalize();
+    Orthanc::HttpClient::GlobalFinalize();
+    Orthanc::HttpClient::FinalizeOpenSsl();
+
+    return (success ? 0 : -1);
+  }
+#endif
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/IBasicApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,57 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "BasicApplicationContext.h"
+
+#include <boost/program_options.hpp>
+
+#if ORTHANC_ENABLE_SDL == 1
+#  include <SDL.h>   // Necessary to avoid undefined reference to `SDL_main'
+#endif
+
+namespace OrthancStone
+{
+  class IBasicApplication : public boost::noncopyable
+  {
+  public:
+    virtual ~IBasicApplication()
+    {
+    }
+
+    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0;
+
+    virtual std::string GetTitle() const = 0;
+
+    virtual void Initialize(BasicApplicationContext& context,
+                            IStatusBar& statusBar,
+                            const boost::program_options::variables_map& parameters) = 0;
+
+    virtual void Finalize() = 0;
+
+#if ORTHANC_ENABLE_SDL == 1
+    static int ExecuteWithSdl(IBasicApplication& application,
+                              int argc, 
+                              char* argv[]);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/BasicPetCtFusionApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,202 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleInteractor.h"
+
+#include "../../Resources/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class BasicPetCtFusionApplication : public SampleApplicationBase
+    {
+    private:
+      class Interactor : public SampleInteractor
+      {
+      public:
+        static void SetStyle(LayeredSceneWidget& widget,
+                             bool ct,
+                             bool pet)
+        {
+          if (ct)
+          {
+            RenderStyle style;
+            style.windowing_ = ImageWindowing_Bone;
+            widget.SetLayerStyle(0, style);
+          }
+          else
+          {
+            RenderStyle style;
+            style.visible_ = false;
+            widget.SetLayerStyle(0, style);
+          }
+
+          if (ct && pet)
+          {
+            RenderStyle style;
+            style.applyLut_ = true;
+            style.alpha_ = 0.5;
+            widget.SetLayerStyle(1, style);
+          }
+          else if (pet)
+          {
+            RenderStyle style;
+            style.applyLut_ = true;
+            widget.SetLayerStyle(1, style);
+          }
+          else
+          {
+            RenderStyle style;
+            style.visible_ = false;
+            widget.SetLayerStyle(1, style);
+          }
+        }
+
+
+        static bool IsVisible(LayeredSceneWidget& widget,
+                              size_t layer)
+        {
+          RenderStyle style = widget.GetLayerStyle(layer);
+          return style.visible_;
+        }
+
+
+        static void ToggleInterpolation(LayeredSceneWidget& widget,
+                                        size_t layer)
+        {
+          RenderStyle style = widget.GetLayerStyle(layer);
+         
+          if (style.interpolation_ == ImageInterpolation_Linear)
+          {
+            style.interpolation_ = ImageInterpolation_Nearest;
+          }
+          else
+          {
+            style.interpolation_ = ImageInterpolation_Linear;
+          }
+
+          widget.SetLayerStyle(layer, style);
+        }
+
+
+        Interactor(VolumeImage& volume,
+                   VolumeProjection projection, 
+                   bool reverse) :
+          SampleInteractor(volume, projection, reverse)
+        {
+        }
+
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          LayeredSceneWidget& layered = dynamic_cast<LayeredSceneWidget&>(widget);
+
+          switch (key)
+          {
+            case 'c':
+              // Toggle the visibility of the CT layer
+              SetStyle(layered, !IsVisible(layered, 0), IsVisible(layered, 1));
+              break;
+
+            case 'p':
+              // Toggle the visibility of the PET layer
+              SetStyle(layered, IsVisible(layered, 0), !IsVisible(layered, 1));
+              break;
+
+            case 'i':
+            {
+              // Toggle on/off the interpolation
+              ToggleInterpolation(layered, 0);
+              ToggleInterpolation(layered, 1);
+              break;
+            }
+
+            default:
+              break;
+          }
+        }
+      };
+
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("ct", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the CT series")
+          ("pet", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the PET series")
+          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
+           "Number of download threads for the CT series")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        if (parameters.count("ct") != 1 ||
+            parameters.count("pet") != 1)
+        {
+          LOG(ERROR) << "The series ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string ct = parameters["ct"].as<std::string>();
+        std::string pet = parameters["pet"].as<std::string>();
+        unsigned int threads = parameters["threads"].as<unsigned int>();
+
+        VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads);
+        VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1);
+
+        // Take the PET volume as the reference for the slices
+        std::auto_ptr<Interactor> interactor(new Interactor(petVolume, VolumeProjection_Axial, false /* don't reverse normal */));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->AddLayer(new VolumeImage::LayerFactory(ctVolume));
+        widget->AddLayer(new VolumeImage::LayerFactory(petVolume));
+        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
+        widget->SetInteractor(*interactor);
+
+        Interactor::SetStyle(*widget, true, true);   // Initially, show both CT and PET layers
+
+        context.AddInteractor(interactor.release());
+        context.SetCentralWidget(widget.release());
+
+        statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode");
+        statusBar.SetMessage("Use the key \"c\" to show/hide the CT layer");
+        statusBar.SetMessage("Use the key \"p\" to show/hide the PET layer");
+        statusBar.SetMessage("Use the key \"i\" to toggle the smoothing of the images");
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/EmptyApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,59 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleApplicationBase.h"
+
+#include "../../Framework/Widgets/EmptyWidget.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class EmptyApplication : public SampleApplicationBase
+    {
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("red", boost::program_options::value<int>()->default_value(255), "Background color: red channel")
+          ("green", boost::program_options::value<int>()->default_value(0), "Background color: green channel")
+          ("blue", boost::program_options::value<int>()->default_value(0), "Background color: blue channel")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        int red = parameters["red"].as<int>();
+        int green = parameters["green"].as<int>();
+        int blue = parameters["blue"].as<int>();
+
+        context.SetCentralWidget(new EmptyWidget(red, green, blue));
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/LayoutPetCtFusionApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,398 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleInteractor.h"
+
+#include "../../Framework/Layers/SiblingSliceLocationFactory.h"
+#include "../../Framework/Layers/DicomStructureSetRendererFactory.h"
+#include "../../Framework/Widgets/LayoutWidget.h"
+
+#include "../../Resources/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class LayoutPetCtFusionApplication : 
+      public SampleApplicationBase,
+      public LayeredSceneWidget::ISliceObserver,
+      public WorldSceneWidget::IWorldObserver
+    {
+    private:
+      class Interactor : public SampleInteractor
+      {
+      private:
+        LayoutPetCtFusionApplication& that_;
+
+      public:
+        Interactor(LayoutPetCtFusionApplication& that,
+                   VolumeImage& volume,
+                   VolumeProjection projection, 
+                   bool reverse) :
+          SampleInteractor(volume, projection, reverse),
+          that_(that)
+        {
+        }
+
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const SliceGeometry& slice,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            // Center the sibling views over the clicked point
+            Vector p = slice.MapSliceToWorldCoordinates(x, y);
+
+            if (statusBar != NULL)
+            {
+              char buf[64];
+              sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+              statusBar->SetMessage(buf);
+            }
+
+            that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p);
+            that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p);
+            that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p);
+          }
+
+          return NULL;
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          if (key == 's')
+          {
+            that_.SetDefaultView();
+          }
+        }
+      };
+
+      bool                 processingEvent_;
+      Interactor*          interactorAxial_;
+      Interactor*          interactorCoronal_;
+      Interactor*          interactorSagittal_;
+      LayeredSceneWidget*  ctAxial_;
+      LayeredSceneWidget*  ctCoronal_;
+      LayeredSceneWidget*  ctSagittal_;
+      LayeredSceneWidget*  petAxial_;
+      LayeredSceneWidget*  petCoronal_;
+      LayeredSceneWidget*  petSagittal_;
+      LayeredSceneWidget*  fusionAxial_;
+      LayeredSceneWidget*  fusionCoronal_;
+      LayeredSceneWidget*  fusionSagittal_;
+
+
+      void SetDefaultView()
+      {
+        petAxial_->SetDefaultView();
+        petCoronal_->SetDefaultView();
+        petSagittal_->SetDefaultView();
+      }
+
+
+      void AddLayer(LayeredSceneWidget& widget,
+                    VolumeImage& volume,
+                    bool isCt)
+      {
+        size_t layer;
+        widget.AddLayer(layer, new VolumeImage::LayerFactory(volume));
+
+        if (isCt)
+        {
+          RenderStyle style; 
+          style.windowing_ = ImageWindowing_Bone;
+          widget.SetLayerStyle(layer, style);
+        }
+        else
+        {
+          RenderStyle style; 
+          style.applyLut_ = true;
+          style.alpha_ = (layer == 0 ? 1.0f : 0.5f);
+          widget.SetLayerStyle(layer, style);
+        }
+      }
+
+
+      void ConnectSiblingLocations(LayeredSceneWidget& axial,
+                                   LayeredSceneWidget& coronal,
+                                   LayeredSceneWidget& sagittal)
+      {
+        SiblingSliceLocationFactory::Configure(axial, coronal);
+        SiblingSliceLocationFactory::Configure(axial, sagittal);
+        SiblingSliceLocationFactory::Configure(coronal, sagittal);
+      }
+
+
+      void SynchronizeView(const WorldSceneWidget& source,
+                           const ViewportGeometry& view,
+                           LayeredSceneWidget& widget1,
+                           LayeredSceneWidget& widget2,
+                           LayeredSceneWidget& widget3)
+      {
+        if (&source == &widget1 ||
+            &source == &widget2 ||
+            &source == &widget3)
+        {
+          if (&source != &widget1)
+          {
+            widget1.SetView(view);
+          }
+
+          if (&source != &widget2)
+          {
+            widget2.SetView(view);
+          }
+
+          if (&source != &widget3)
+          {
+            widget3.SetView(view);
+          }
+        }
+      }
+
+
+      void SynchronizeSlice(const LayeredSceneWidget& source,
+                            const SliceGeometry& slice,
+                            LayeredSceneWidget& widget1,
+                            LayeredSceneWidget& widget2,
+                            LayeredSceneWidget& widget3)
+      {
+        if (&source == &widget1 ||
+            &source == &widget2 ||
+            &source == &widget3)
+        {
+          if (&source != &widget1)
+          {
+            widget1.SetSlice(slice);
+          }
+
+          if (&source != &widget2)
+          {
+            widget2.SetSlice(slice);
+          }
+
+          if (&source != &widget3)
+          {
+            widget3.SetSlice(slice);
+          }
+        }
+      }
+
+
+      LayeredSceneWidget* CreateWidget()
+      {
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->Register(dynamic_cast<WorldSceneWidget::IWorldObserver&>(*this));
+        widget->Register(dynamic_cast<LayeredSceneWidget::ISliceObserver&>(*this));
+        return widget.release();
+      }
+
+
+      void CreateLayout(BasicApplicationContext& context)
+      {
+        std::auto_ptr<OrthancStone::LayoutWidget> layout(new OrthancStone::LayoutWidget);
+        layout->SetBackgroundCleared(true);
+        //layout->SetBackgroundColor(255,0,0);
+        layout->SetPadding(5);
+
+        OrthancStone::LayoutWidget& layoutA = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layout->AddWidget(new OrthancStone::LayoutWidget));
+        layoutA.SetPadding(0, 0, 0, 0, 5);
+        layoutA.SetVertical();
+        petAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutA.AddWidget(CreateWidget()));
+        OrthancStone::LayoutWidget& layoutA2 = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layoutA.AddWidget(new OrthancStone::LayoutWidget));
+        layoutA2.SetPadding(0, 0, 0, 0, 5);
+        petSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget()));
+        petCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget()));
+
+        OrthancStone::LayoutWidget& layoutB = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layout->AddWidget(new OrthancStone::LayoutWidget));
+        layoutB.SetPadding(0, 0, 0, 0, 5);
+        layoutB.SetVertical();
+        ctAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutB.AddWidget(CreateWidget()));
+        OrthancStone::LayoutWidget& layoutB2 = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layoutB.AddWidget(new OrthancStone::LayoutWidget));
+        layoutB2.SetPadding(0, 0, 0, 0, 5);
+        ctSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget()));
+        ctCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget()));
+
+        OrthancStone::LayoutWidget& layoutC = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layout->AddWidget(new OrthancStone::LayoutWidget));
+        layoutC.SetPadding(0, 0, 0, 0, 5);
+        layoutC.SetVertical();
+        fusionAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutC.AddWidget(CreateWidget()));
+        OrthancStone::LayoutWidget& layoutC2 = dynamic_cast<OrthancStone::LayoutWidget&>
+          (layoutC.AddWidget(new OrthancStone::LayoutWidget));
+        layoutC2.SetPadding(0, 0, 0, 0, 5);
+        fusionSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget()));
+        fusionCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget()));
+  
+        context.SetCentralWidget(layout.release());
+      }
+
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("ct", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the CT series")
+          ("pet", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the PET series")
+          ("rt", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the DICOM RT-STRUCT series (optional)")
+          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
+           "Number of download threads for the CT series")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        processingEvent_ = true;
+
+        if (parameters.count("ct") != 1 ||
+            parameters.count("pet") != 1)
+        {
+          LOG(ERROR) << "The series ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string ct = parameters["ct"].as<std::string>();
+        std::string pet = parameters["pet"].as<std::string>();
+        unsigned int threads = parameters["threads"].as<unsigned int>();
+
+        VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads);
+        VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1);
+
+        // Take the PET volume as the reference for the slices
+        interactorAxial_ = &dynamic_cast<Interactor&>
+          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false)));
+        interactorCoronal_ = &dynamic_cast<Interactor&>
+          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false)));
+        interactorSagittal_ = &dynamic_cast<Interactor&>
+          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true)));
+
+        CreateLayout(context);
+
+        AddLayer(*ctAxial_, ctVolume, true);
+        AddLayer(*ctCoronal_, ctVolume, true);
+        AddLayer(*ctSagittal_, ctVolume, true);
+
+        AddLayer(*petAxial_, petVolume, false);
+        AddLayer(*petCoronal_, petVolume, false);
+        AddLayer(*petSagittal_, petVolume, false);
+
+        AddLayer(*fusionAxial_, ctVolume, true);
+        AddLayer(*fusionAxial_, petVolume, false);
+        AddLayer(*fusionCoronal_, ctVolume, true);
+        AddLayer(*fusionCoronal_, petVolume, false);
+        AddLayer(*fusionSagittal_, ctVolume, true);
+        AddLayer(*fusionSagittal_, petVolume, false);
+
+        if (parameters.count("rt") == 1)
+        {
+          DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as<std::string>());
+
+          Vector p = rtStruct.GetStructureCenter(0);
+          interactorAxial_->GetCursor().LookupSliceContainingPoint(p);
+
+          ctAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
+          petAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
+          fusionAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
+        }        
+
+        ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); 
+        ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); 
+        ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); 
+
+        interactorAxial_->AddWidget(*ctAxial_);
+        interactorAxial_->AddWidget(*petAxial_);
+        interactorAxial_->AddWidget(*fusionAxial_);
+        
+        interactorCoronal_->AddWidget(*ctCoronal_);
+        interactorCoronal_->AddWidget(*petCoronal_);
+        interactorCoronal_->AddWidget(*fusionCoronal_);
+        
+        interactorSagittal_->AddWidget(*ctSagittal_);
+        interactorSagittal_->AddWidget(*petSagittal_);
+        interactorSagittal_->AddWidget(*fusionSagittal_);
+
+        processingEvent_ = false;
+
+        statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode");
+        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
+      }
+
+      virtual void NotifySizeChange(const WorldSceneWidget& source,
+                                    ViewportGeometry& view)
+      {
+        view.SetDefaultView();
+      }
+
+      virtual void NotifyViewChange(const WorldSceneWidget& source,
+                                    const ViewportGeometry& view)
+      {
+        if (!processingEvent_)  // Avoid reentrant calls
+        {
+          processingEvent_ = true;
+
+          SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_);
+          SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_);
+          SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_);
+          
+          processingEvent_ = false;
+        }
+      }
+
+      virtual void NotifySliceChange(const LayeredSceneWidget& source,
+                                     const SliceGeometry& slice)
+      {
+        if (!processingEvent_)  // Avoid reentrant calls
+        {
+          processingEvent_ = true;
+
+          SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_);
+          SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_);
+          SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_);
+          
+          processingEvent_ = false;
+        }
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleApplicationBase.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,47 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "../IBasicApplication.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SampleApplicationBase : public IBasicApplication
+    {
+    public:
+      virtual std::string GetTitle() const
+      {
+        return "Stone of Orthanc - Sample";
+      }
+
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+      }
+
+      virtual void Finalize()
+      {
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleInteractor.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,133 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleApplicationBase.h"
+
+#include "../../Framework/Widgets/LayeredSceneWidget.h"
+#include "../../Framework/Widgets/IWorldSceneInteractor.h"
+#include "../../Framework/Toolbox/ParallelSlicesCursor.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    /**
+     * This is a basic mouse interactor for sample applications. It
+     * contains a set of parallel slices in the 3D space. The mouse
+     * wheel events make the widget change the slice that is
+     * displayed.
+     **/
+    class SampleInteractor : public IWorldSceneInteractor
+    {
+    private:
+      ParallelSlicesCursor   cursor_;
+
+    public:
+      SampleInteractor(VolumeImage& volume,
+                       VolumeProjection projection, 
+                       bool reverse)
+      {
+        std::auto_ptr<ParallelSlices> slices(volume.GetGeometry(projection, reverse));
+        cursor_.SetGeometry(*slices);
+      }
+
+      SampleInteractor(ISeriesLoader& series, 
+                       bool reverse)
+      {
+        if (reverse)
+        {
+          std::auto_ptr<ParallelSlices> slices(series.GetGeometry().Reverse());
+          cursor_.SetGeometry(*slices);
+        }
+        else
+        {
+          cursor_.SetGeometry(series.GetGeometry());
+        }
+      }
+
+      SampleInteractor(const ParallelSlices& slices)
+      {
+        cursor_.SetGeometry(slices);
+      }
+
+      ParallelSlicesCursor& GetCursor()
+      {
+        return cursor_;
+      }
+
+      void AddWidget(LayeredSceneWidget& widget)
+      {
+        widget.SetInteractor(*this);
+        widget.SetSlice(cursor_.GetCurrentSlice());
+      }
+
+      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                          const SliceGeometry& slice,
+                                                          const ViewportGeometry& view,
+                                                          MouseButton button,
+                                                          double x,
+                                                          double y,
+                                                          IStatusBar* statusBar)
+      {
+        return NULL;
+      }
+
+      virtual void MouseOver(CairoContext& context,
+                             WorldSceneWidget& widget,
+                             const SliceGeometry& slice,
+                             const ViewportGeometry& view,
+                             double x,
+                             double y,
+                             IStatusBar* statusBar)
+      {
+      }
+
+      virtual void MouseWheel(WorldSceneWidget& widget,
+                              MouseWheelDirection direction,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+        if (cursor_.ApplyWheelEvent(direction, modifiers))
+        {
+          dynamic_cast<LayeredSceneWidget&>(widget).SetSlice(cursor_.GetCurrentSlice());
+        }
+      }
+
+      virtual void KeyPressed(WorldSceneWidget& widget,
+                              char key,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
+      {
+      }
+
+      void LookupSliceContainingPoint(LayeredSceneWidget& widget,
+                                      const Vector& p)
+      {
+        if (cursor_.LookupSliceContainingPoint(p))
+        {
+          widget.SetSlice(cursor_.GetCurrentSlice());
+        }
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SampleMainSdl.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,62 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script
+
+#if ORTHANC_STONE_SAMPLE == 1
+#include "EmptyApplication.h"
+typedef OrthancStone::Samples::EmptyApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 2
+#include "TestPatternApplication.h"
+typedef OrthancStone::Samples::TestPatternApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 3
+#include "SingleFrameApplication.h"
+typedef OrthancStone::Samples::SingleFrameApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 4
+#include "SingleVolumeApplication.h"
+typedef OrthancStone::Samples::SingleVolumeApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 5
+#include "BasicPetCtFusionApplication.h"
+typedef OrthancStone::Samples::BasicPetCtFusionApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 6
+#include "SynchronizedSeriesApplication.h"
+typedef OrthancStone::Samples::SynchronizedSeriesApplication Application;
+
+#elif ORTHANC_STONE_SAMPLE == 7
+#include "LayoutPetCtFusionApplication.h"
+typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application;
+
+#else
+#error Please set the ORTHANC_STONE_SAMPLE macro
+#endif
+
+
+int main(int argc, char* argv[]) 
+{
+  Application application;
+
+  return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SingleFrameApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,85 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleApplicationBase.h"
+
+#include "../../Framework/Layers/SingleFrameRendererFactory.h"
+#include "../../Framework/Widgets/LayeredSceneWidget.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SingleFrameApplication : public SampleApplicationBase
+    {
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("instance", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the instance")
+          ("frame", boost::program_options::value<unsigned int>()->default_value(0),
+           "Number of the frame, for multi-frame DICOM instances")
+          ("smooth", boost::program_options::value<bool>()->default_value(true), 
+           "Enable linear interpolation to smooth the image")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        if (parameters.count("instance") != 1)
+        {
+          LOG(ERROR) << "The instance ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        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<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->SetSlice(renderer->GetSliceGeometry());
+        widget->AddLayer(renderer.release());
+
+        if (parameters["smooth"].as<bool>())
+        {
+          RenderStyle s; 
+          s.interpolation_ = ImageInterpolation_Linear;
+          widget->SetLayerStyle(0, s);
+        }
+
+        context.SetCentralWidget(widget.release());
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SingleVolumeApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,284 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleInteractor.h"
+
+#include "../../Resources/Orthanc/Core/Toolbox.h"
+#include "../../Framework/Layers/LineMeasureTracker.h"
+#include "../../Framework/Layers/CircleMeasureTracker.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SingleVolumeApplication : public SampleApplicationBase
+    {
+    private:
+      class Interactor : public SampleInteractor
+      {
+      private:
+        enum MouseMode
+        {
+          MouseMode_None,
+          MouseMode_TrackCoordinates,
+          MouseMode_LineMeasure,
+          MouseMode_CircleMeasure
+        };
+
+        MouseMode mouseMode_;
+
+        void SetMouseMode(MouseMode mode,
+                          IStatusBar* statusBar)
+        {
+          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;
+
+              case MouseMode_CircleMeasure:
+                statusBar->SetMessage("Mouse clicks will now draw circles");
+                break;
+
+              default:
+                break;
+            }
+          }
+        }
+
+      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)
+          {
+            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;
+            }
+          }
+
+          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);
+          }
+        }
+
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          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;
+          }
+        }
+      };
+
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("series", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the series")
+          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
+           "Number of download threads")
+          ("projection", boost::program_options::value<std::string>()->default_value("axial"), 
+           "Projection of interest (can be axial, sagittal or coronal)")
+          ("reverse", boost::program_options::value<bool>()->default_value(false), 
+           "Reverse the normal direction of the volume")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        if (parameters.count("series") != 1)
+        {
+          LOG(ERROR) << "The series ID is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::string series = parameters["series"].as<std::string>();
+        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")
+        {
+          projection = VolumeProjection_Axial;
+        }
+        else if (tmp == "sagittal")
+        {
+          projection = VolumeProjection_Sagittal;
+        }
+        else if (tmp == "coronal")
+        {
+          projection = VolumeProjection_Coronal;
+        }
+        else
+        {
+          LOG(ERROR) << "Unknown projection: " << tmp;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        VolumeImage& volume = context.AddSeriesVolume(series, true /* progressive download */, threads);
+
+        std::auto_ptr<Interactor> interactor(new Interactor(volume, projection, reverse));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->AddLayer(new VolumeImage::LayerFactory(volume));
+        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
+        widget->SetInteractor(*interactor);
+
+        context.AddInteractor(interactor.release());
+        context.SetCentralWidget(widget.release());
+
+        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");
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SynchronizedSeriesApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,107 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleInteractor.h"
+
+#include "../../Framework/Toolbox/OrthancSeriesLoader.h"
+#include "../../Framework/Layers/SeriesFrameRendererFactory.h"
+#include "../../Framework/Layers/SiblingSliceLocationFactory.h"
+#include "../../Framework/Widgets/LayoutWidget.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SynchronizedSeriesApplication : public SampleApplicationBase
+    {
+    private:   
+      LayeredSceneWidget* CreateSeriesWidget(BasicApplicationContext& context,
+                                             const std::string& series)
+      {
+        std::auto_ptr<ISeriesLoader> loader(new OrthancSeriesLoader(context.GetOrthancConnection(), series));
+
+        std::auto_ptr<SampleInteractor> interactor(new SampleInteractor(*loader, false));
+
+        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
+        widget->AddLayer(new SeriesFrameRendererFactory(loader.release(), false));
+        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
+        widget->SetInteractor(*interactor);
+
+        context.AddInteractor(interactor.release());
+
+        return widget.release();
+      }
+
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("a", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the 1st series")
+          ("b", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the 2nd series")
+          ("c", boost::program_options::value<std::string>(), 
+           "Orthanc ID of the 3rd series")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        if (parameters.count("a") != 1 ||
+            parameters.count("b") != 1 ||
+            parameters.count("c") != 1)
+        {
+          LOG(ERROR) << "At least one of the three series IDs is missing";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        std::auto_ptr<LayeredSceneWidget> a(CreateSeriesWidget(context, parameters["a"].as<std::string>()));
+        std::auto_ptr<LayeredSceneWidget> b(CreateSeriesWidget(context, parameters["b"].as<std::string>()));
+        std::auto_ptr<LayeredSceneWidget> c(CreateSeriesWidget(context, parameters["c"].as<std::string>()));
+
+        SiblingSliceLocationFactory::Configure(*a, *b);
+        SiblingSliceLocationFactory::Configure(*a, *c);
+        SiblingSliceLocationFactory::Configure(*b, *c);
+
+        std::auto_ptr<LayoutWidget> layout(new LayoutWidget);
+        layout->SetPadding(5);
+        layout->AddWidget(a.release());
+
+        std::auto_ptr<LayoutWidget> layoutB(new LayoutWidget);
+        layoutB->SetVertical();
+        layoutB->SetPadding(5);
+        layoutB->AddWidget(b.release());
+        layoutB->AddWidget(c.release());
+        layout->AddWidget(layoutB.release());
+
+        context.SetCentralWidget(layout.release());        
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/TestPatternApplication.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,63 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SampleApplicationBase.h"
+
+#include "../../Framework/Widgets/TestCairoWidget.h"
+#include "../../Framework/Widgets/TestWorldSceneWidget.h"
+#include "../../Framework/Widgets/LayoutWidget.h"
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class TestPatternApplication : public SampleApplicationBase
+    {
+    public:
+      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+          ("animate", boost::program_options::value<bool>()->default_value(true), "Animate the test pattern")
+          ;
+
+        options.add(generic);    
+      }
+
+      virtual void Initialize(BasicApplicationContext& context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        std::auto_ptr<LayoutWidget> layout(new LayoutWidget);
+        layout->SetPadding(10);
+        layout->SetBackgroundCleared(true);
+        layout->AddWidget(new TestCairoWidget(parameters["animate"].as<bool>()));
+        layout->AddWidget(new TestWorldSceneWidget);
+
+        context.SetCentralWidget(layout.release());
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlBuffering.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,134 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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/Applications/Sdl/SdlBuffering.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,60 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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/Applications/Sdl/SdlEngine.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,319 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SdlEngine.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../../Resources/Orthanc/Core/Logging.h"
+
+#include <SDL.h>
+
+namespace OrthancStone
+{
+  void SdlEngine::RenderFrame()
+  {
+    if (!viewportChanged_)
+    {
+      return;
+    }
+
+    viewportChanged_ = false;
+
+    if (buffering_.RenderOffscreen(viewport_))
+    {
+      // 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);
+    }
+  }
+
+
+  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)
+  {
+    int result = KeyboardModifiers_None;
+
+    if (keyboardState != NULL)
+    {
+      if (SDL_SCANCODE_LSHIFT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LSHIFT])
+      {
+        result |= KeyboardModifiers_Shift;
+      }
+
+      if (SDL_SCANCODE_RSHIFT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RSHIFT])
+      {
+        result |= KeyboardModifiers_Shift;
+      }
+
+      if (SDL_SCANCODE_LCTRL < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LCTRL])
+      {
+        result |= KeyboardModifiers_Control;
+      }
+
+      if (SDL_SCANCODE_RCTRL < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RCTRL])
+      {
+        result |= KeyboardModifiers_Control;
+      }
+
+      if (SDL_SCANCODE_LALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LALT])
+      {
+        result |= KeyboardModifiers_Alt;
+      }
+
+      if (SDL_SCANCODE_RALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_RALT])
+      {
+        result |= KeyboardModifiers_Alt;
+      }
+    }
+
+    return static_cast<KeyboardModifiers>(result);
+  }
+
+
+  void SdlEngine::SetSize(unsigned int width,
+                          unsigned int height)
+  {
+    buffering_.SetSize(width, height, viewport_);
+    viewportChanged_ = true;
+    Refresh();
+  }
+
+
+  void SdlEngine::Stop()
+  {
+    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);
+  }
+
+
+  void SdlEngine::Run()
+  {
+    int scancodeCount = 0;
+    const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+    bool stop = false;
+    while (!stop)
+    {
+      Refresh();
+
+      SDL_Event event;
+
+      while (SDL_PollEvent(&event))
+      {
+        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);
+
+          switch (event.button.button)
+          {
+            case SDL_BUTTON_LEFT:
+              viewport_.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);
+              break;
+            
+            case SDL_BUTTON_MIDDLE:
+              viewport_.MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
+              break;
+
+            default:
+              break;
+          }
+        }
+        else if (event.type == SDL_MOUSEMOTION)
+        {
+          viewport_.MouseMove(event.button.x, event.button.y);
+        }
+        else if (event.type == SDL_MOUSEBUTTONUP)
+        {
+          viewport_.MouseUp();
+        }
+        else if (event.type == SDL_WINDOWEVENT)
+        {
+          switch (event.window.event)
+          {
+            case SDL_WINDOWEVENT_LEAVE:
+              viewport_.MouseLeave();
+              break;
+
+            case SDL_WINDOWEVENT_ENTER:
+              viewport_.MouseEnter();
+              break;
+
+            case SDL_WINDOWEVENT_SIZE_CHANGED:
+              SetSize(event.window.data1, event.window.data2);
+              break;
+
+            default:
+              break;
+          }
+        }
+        else if (event.type == SDL_MOUSEWHEEL)
+        {
+          KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
+
+          int x, y;
+          SDL_GetMouseState(&x, &y);
+
+          if (event.wheel.y > 0)
+          {
+            viewport_.MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
+          }
+          else if (event.wheel.y < 0)
+          {
+            viewport_.MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
+          }
+        }
+        else if (event.type == SDL_KEYDOWN)
+        {
+          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;
+
+            default:
+              break;
+          }
+        }
+      }
+
+      SDL_Delay(10);   // Necessary for mouse wheel events to work
+    }
+
+    Stop();
+  }
+
+
+  void SdlEngine::GlobalInitialize()
+  {
+    SDL_Init(SDL_INIT_VIDEO);
+  }
+
+
+  void SdlEngine::GlobalFinalize()
+  {
+    SDL_Quit();
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlEngine.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SdlBuffering.h"
+#include "../BinarySemaphore.h"
+
+#include <boost/thread.hpp>
+
+namespace OrthancStone
+{
+  class SdlEngine : public IViewport::IChangeObserver
+  {
+  private:
+    SdlWindow&        window_;
+    IViewport&        viewport_;
+    SdlBuffering      buffering_;
+    boost::thread     renderThread_;
+    bool              continue_;
+    BinarySemaphore   renderFrame_;
+    uint32_t          refreshEvent_;
+    bool              viewportChanged_;
+
+    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);
+  
+    virtual ~SdlEngine();
+
+    virtual void NotifyChange(const IViewport& viewport)
+    {
+      viewportChanged_ = true;
+    }
+
+    void Run();
+
+    static void GlobalInitialize();
+
+    static void GlobalFinalize();
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlWindow.cpp	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,150 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "SdlWindow.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../../Resources/Orthanc/Core/Logging.h"
+#include "../../../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  SdlWindow::SdlWindow(const char* title,
+                       unsigned int width,
+                       unsigned int height,
+                       bool enableOpenGl) :
+    maximized_(false)
+  {
+    // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release
+    // build mode, the application crashes whenever the SDL window is
+    // resized or maximized
+
+    uint32_t windowFlags, rendererFlags;
+    if (enableOpenGl)
+    {
+      windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
+      rendererFlags = SDL_RENDERER_ACCELERATED;
+    }
+    else
+    {
+      windowFlags = SDL_WINDOW_RESIZABLE;
+      rendererFlags = SDL_RENDERER_SOFTWARE;
+    }
+    
+    window_ = SDL_CreateWindow(title,
+                               SDL_WINDOWPOS_UNDEFINED,
+                               SDL_WINDOWPOS_UNDEFINED,
+                               width, height, windowFlags);
+
+    if (window_ == NULL) 
+    {
+      LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags);
+    if (!renderer_)
+    {
+      LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError();
+      SDL_DestroyWindow(window_);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  SdlWindow::~SdlWindow()
+  {
+    if (renderer_ != NULL)
+    { 
+      SDL_DestroyRenderer(renderer_);
+    }
+
+    if (window_ != NULL)
+    { 
+      SDL_DestroyWindow(window_);
+    }
+  }
+
+
+  unsigned int SdlWindow::GetWidth() const
+  {
+    int w = -1;
+    SDL_GetWindowSize(window_, &w, NULL);
+
+    if (w < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<unsigned int>(w);
+    }
+  }
+
+
+  unsigned int SdlWindow::GetHeight() const
+  {
+    int h = -1;
+    SDL_GetWindowSize(window_, NULL, &h);
+
+    if (h < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<unsigned int>(h);
+    }
+  }
+
+
+  void SdlWindow::Render(SDL_Surface* surface)
+  {
+    //SDL_RenderClear(renderer_);
+
+    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface);
+    if (texture != NULL)
+    {
+      SDL_RenderCopy(renderer_, texture, NULL, NULL);
+      SDL_DestroyTexture(texture);
+    }
+
+    SDL_RenderPresent(renderer_);
+  }
+
+
+  void SdlWindow::ToggleMaximize()
+  {
+    if (maximized_)
+    {
+      SDL_RestoreWindow(window_);
+      maximized_ = false;
+    }
+    else
+    {
+      SDL_MaximizeWindow(window_);
+      maximized_ = true;
+    }
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlWindow.h	Thu Apr 27 14:50:20 2017 +0200
@@ -0,0 +1,57 @@
+/**
+ * Stone of Orthanc
+ * 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 Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 <SDL_render.h>
+#include <SDL_video.h>
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class SdlWindow : public boost::noncopyable
+  {
+  private:
+    SDL_Window    *window_;
+    SDL_Renderer  *renderer_;
+    bool           maximized_;
+
+  public:
+    SdlWindow(const char* title,
+              unsigned int width,
+              unsigned int height,
+              bool enableOpenGl);
+
+    ~SdlWindow();
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    void Render(SDL_Surface* surface);
+
+    void ToggleMaximize();
+  };
+}
+
+#endif
--- a/CMakeLists.txt	Thu Apr 27 11:09:43 2017 +0200
+++ b/CMakeLists.txt	Thu Apr 27 14:50:20 2017 +0200
@@ -26,7 +26,7 @@
 #####################################################################
 
 macro(BuildSample Target Sample)
-  add_executable(${Target} Samples/SampleMainSdl.cpp)
+  add_executable(${Target} Applications/Samples/SampleMainSdl.cpp)
   set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample})
   target_link_libraries(${Target} OrthancStone)
 endmacro()
--- a/Framework/Applications/BasicApplicationContext.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "BasicApplicationContext.h"
-
-#include "../Toolbox/OrthancSeriesLoader.h"
-#include "../Volumes/VolumeImageSimplePolicy.h"
-#include "../Volumes/VolumeImageProgressivePolicy.h"
-
-namespace OrthancStone
-{
-  BasicApplicationContext::BasicApplicationContext(OrthancPlugins::IOrthancConnection& orthanc) :
-    orthanc_(orthanc)
-  {
-  }
-
-
-  BasicApplicationContext::~BasicApplicationContext()
-  {
-    for (Interactors::iterator it = interactors_.begin(); it != interactors_.end(); ++it)
-    {
-      assert(*it != NULL);
-      delete *it;
-    }
-
-    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
-    {
-      assert(*it != NULL);
-      delete *it;
-    }
-
-    for (StructureSets::iterator it = structureSets_.begin(); it != structureSets_.end(); ++it)
-    {
-      assert(*it != NULL);
-      delete *it;
-    }
-  }
-
-
-  IWidget& BasicApplicationContext::SetCentralWidget(IWidget* widget)   // Takes ownership
-  {
-    viewport_.SetCentralWidget(widget);
-    return *widget;
-  }
-
-
-  VolumeImage& BasicApplicationContext::AddSeriesVolume(const std::string& series,
-                                                        bool isProgressiveDownload,
-                                                        size_t downloadThreadCount)
-  {
-    std::auto_ptr<VolumeImage> volume(new VolumeImage(new OrthancSeriesLoader(orthanc_, series)));
-
-    if (isProgressiveDownload)
-    {
-      volume->SetDownloadPolicy(new VolumeImageProgressivePolicy);
-    }
-    else
-    {
-      volume->SetDownloadPolicy(new VolumeImageSimplePolicy);
-    }
-
-    volume->SetThreadCount(downloadThreadCount);
-
-    VolumeImage& result = *volume;
-    volumes_.push_back(volume.release());
-
-    return result;
-  }
-
-
-  DicomStructureSet& BasicApplicationContext::AddStructureSet(const std::string& instance)
-  {
-    std::auto_ptr<DicomStructureSet> structureSet(new DicomStructureSet(orthanc_, instance));
-
-    DicomStructureSet& result = *structureSet;
-    structureSets_.push_back(structureSet.release());
-
-    return result;
-  }
-
-
-  IWorldSceneInteractor& BasicApplicationContext::AddInteractor(IWorldSceneInteractor* interactor)
-  {
-    if (interactor == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    interactors_.push_back(interactor);
-
-    return *interactor;
-  }
-
-
-  void BasicApplicationContext::Start()
-  {
-    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
-    {
-      assert(*it != NULL);
-      (*it)->Start();
-    }
-
-    viewport_.Start();
-  }
-
-
-  void BasicApplicationContext::Stop()
-  {
-    viewport_.Stop();
-
-    for (Volumes::iterator it = volumes_.begin(); it != volumes_.end(); ++it)
-    {
-      assert(*it != NULL);
-      (*it)->Stop();
-    }
-  }
-}
--- a/Framework/Applications/BasicApplicationContext.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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/VolumeImage.h"
-#include "../Viewport/WidgetViewport.h"
-#include "../Widgets/IWorldSceneInteractor.h"
-#include "../Toolbox/DicomStructureSet.h"
-
-#include <list>
-
-namespace OrthancStone
-{
-  class BasicApplicationContext : public boost::noncopyable
-  {
-  private:
-    typedef std::list<ISliceableVolume*>       Volumes;
-    typedef std::list<IWorldSceneInteractor*>  Interactors;
-    typedef std::list<DicomStructureSet*>      StructureSets;
-
-    OrthancPlugins::IOrthancConnection&  orthanc_;
-
-    WidgetViewport   viewport_;
-    Volumes          volumes_;
-    Interactors      interactors_;
-    StructureSets    structureSets_;
-
-  public:
-    BasicApplicationContext(OrthancPlugins::IOrthancConnection& orthanc);
-
-    ~BasicApplicationContext();
-
-    IWidget& SetCentralWidget(IWidget* widget);   // Takes ownership
-
-    IViewport& GetViewport()
-    {
-      return viewport_;
-    }
-
-    OrthancPlugins::IOrthancConnection& GetOrthancConnection()
-    {
-      return orthanc_;
-    }
-
-    VolumeImage& AddSeriesVolume(const std::string& series,
-                                 bool isProgressiveDownload,
-                                 size_t downloadThreadCount);
-
-    DicomStructureSet& AddStructureSet(const std::string& instance);
-
-    IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor);
-
-    void Start();
-
-    void Stop();
-  };
-}
--- a/Framework/Applications/IBasicApplication.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,273 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "IBasicApplication.h"
-
-#include "../../Resources/Orthanc/Core/Logging.h"
-#include "../../Resources/Orthanc/Core/HttpClient.h"
-#include "../../Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h"
-#include "Sdl/SdlEngine.h"
-
-namespace OrthancStone
-{
-  // Anonymous namespace to avoid clashes against other compilation modules
-  namespace
-  {
-    class LogStatusBar : public IStatusBar
-    {
-    public:
-      virtual void ClearMessage()
-      {
-      }
-
-      virtual void SetMessage(const std::string& message)
-      {
-        LOG(WARNING) << message;
-      }
-    };
-  }
-
-
-#if ORTHANC_ENABLE_SDL == 1
-  static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options)
-  {
-    // Declare the supported parameters
-    boost::program_options::options_description generic("Generic options");
-    generic.add_options()
-      ("help", "Display this help and exit")
-      ("verbose", "Be verbose in logs")
-      ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"),
-       "URL to the Orthanc server")
-      ("username", "Username for the Orthanc server")
-      ("password", "Password for the Orthanc server")
-      ("https-verify", boost::program_options::value<bool>()->default_value(true), "Check HTTPS certificates")
-      ;
-
-    options.add(generic);
-
-    boost::program_options::options_description sdl("SDL options");
-    sdl.add_options()
-      ("width", boost::program_options::value<int>()->default_value(1024), "Initial width of the SDL window")
-      ("height", boost::program_options::value<int>()->default_value(768), "Initial height of the SDL window")
-      ("opengl", boost::program_options::value<bool>()->default_value(true), "Enable OpenGL in SDL")
-      ;
-
-    options.add(sdl);
-  }
-
-
-  int IBasicApplication::ExecuteWithSdl(IBasicApplication& application,
-                                        int argc, 
-                                        char* argv[])
-  {
-    /******************************************************************
-     * Initialize all the subcomponents of Orthanc Stone
-     ******************************************************************/
-
-    Orthanc::Logging::Initialize();
-    Orthanc::HttpClient::InitializeOpenSsl();
-    Orthanc::HttpClient::GlobalInitialize();
-    SdlEngine::GlobalInitialize();
-
-
-    /******************************************************************
-     * Declare and parse the command-line options of the application
-     ******************************************************************/
-
-    boost::program_options::options_description options;
-    DeclareSdlCommandLineOptions(options);   
-    application.DeclareCommandLineOptions(options);
-
-    boost::program_options::variables_map parameters;
-    bool error = false;
-
-    try
-    {
-      boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
-                                    options(options).run(), parameters);
-      boost::program_options::notify(parameters);    
-    }
-    catch (boost::program_options::error& e)
-    {
-      LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what();
-      error = true;
-    }
-
-
-    /******************************************************************
-     * Configure the application with the command-line parameters
-     ******************************************************************/
-
-    if (error || parameters.count("help")) 
-    {
-      std::cout << std::endl
-                << "Usage: " << argv[0] << " [OPTION]..."
-                << std::endl
-                << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research."
-                << std::endl << std::endl
-                << "Demonstration application of Orthanc Stone using SDL."
-                << std::endl;
-
-      std::cout << options << "\n";
-      return error ? -1 : 0;
-    }
-
-    if (parameters.count("https-verify") &&
-        !parameters["https-verify"].as<bool>())
-    {
-      LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)";
-      Orthanc::HttpClient::ConfigureSsl(false, "");
-    }
-
-    if (parameters.count("verbose"))
-    {
-      Orthanc::Logging::EnableInfoLevel(true);
-    }
-
-    if (!parameters.count("width") ||
-        !parameters.count("height") ||
-        !parameters.count("opengl"))
-    {
-      LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing";
-      return -1;
-    }
-
-    int w = parameters["width"].as<int>();
-    int h = parameters["height"].as<int>();
-    if (w <= 0 || h <= 0)
-    {
-      LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive";
-      return -1;
-    }
-
-    unsigned int width = static_cast<unsigned int>(w);
-    unsigned int height = static_cast<unsigned int>(h);
-    LOG(WARNING) << "Initial display size: " << width << "x" << height;
-
-    bool opengl = parameters["opengl"].as<bool>();
-    if (opengl)
-    {
-      LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes";
-    }
-    else
-    {
-      LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance";
-    }
-
-    bool success = true;
-    try
-    {
-      /****************************************************************
-       * Initialize the connection to the Orthanc server
-       ****************************************************************/
-
-      Orthanc::WebServiceParameters webService;
-
-      if (parameters.count("orthanc"))
-      {
-        webService.SetUrl(parameters["orthanc"].as<std::string>());
-      }
-
-      if (parameters.count("username"))
-      {
-        webService.SetUsername(parameters["username"].as<std::string>());
-      }
-
-      if (parameters.count("password"))
-      {
-        webService.SetPassword(parameters["password"].as<std::string>());
-      }
-
-      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);
-      }
-
-
-      /****************************************************************
-       * Initialize the application
-       ****************************************************************/
-
-      LogStatusBar statusBar;
-      BasicApplicationContext context(orthanc);
-
-      application.Initialize(context, statusBar, parameters);
-      context.GetViewport().SetStatusBar(statusBar);
-
-      std::string title = application.GetTitle();
-      if (title.empty())
-      {
-        title = "Stone of Orthanc";
-      }
-
-      context.Start();
-
-      {
-        /**************************************************************
-         * Run the application inside a SDL window
-         **************************************************************/
-
-        LOG(WARNING) << "Starting the application";
-
-        SdlWindow window(title.c_str(), width, height, opengl);
-        SdlEngine sdl(window, context.GetViewport());
-
-        sdl.Run();
-
-        LOG(WARNING) << "Stopping the application";
-      }
-
-
-      /****************************************************************
-       * Finalize the application
-       ****************************************************************/
-
-      context.Stop();
-
-      LOG(WARNING) << "The application has stopped";
-
-      context.GetViewport().ResetStatusBar();
-      application.Finalize();
-    }
-    catch (Orthanc::OrthancException& e)
-    {
-      LOG(ERROR) << "EXCEPTION: " << e.What();
-      success = false;
-    }
-
-
-    /******************************************************************
-     * Finalize all the subcomponents of Orthanc Stone
-     ******************************************************************/
-
-    SdlEngine::GlobalFinalize();
-    Orthanc::HttpClient::GlobalFinalize();
-    Orthanc::HttpClient::FinalizeOpenSsl();
-
-    return (success ? 0 : -1);
-  }
-#endif
-
-}
--- a/Framework/Applications/IBasicApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "BasicApplicationContext.h"
-
-#include <boost/program_options.hpp>
-
-#if ORTHANC_ENABLE_SDL == 1
-#  include <SDL.h>   // Necessary to avoid undefined reference to `SDL_main'
-#endif
-
-namespace OrthancStone
-{
-  class IBasicApplication : public boost::noncopyable
-  {
-  public:
-    virtual ~IBasicApplication()
-    {
-    }
-
-    virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0;
-
-    virtual std::string GetTitle() const = 0;
-
-    virtual void Initialize(BasicApplicationContext& context,
-                            IStatusBar& statusBar,
-                            const boost::program_options::variables_map& parameters) = 0;
-
-    virtual void Finalize() = 0;
-
-#if ORTHANC_ENABLE_SDL == 1
-    static int ExecuteWithSdl(IBasicApplication& application,
-                              int argc, 
-                              char* argv[]);
-#endif
-  };
-}
--- a/Framework/Applications/Sdl/SdlBuffering.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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/Framework/Applications/Sdl/SdlBuffering.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "../../Viewport/CairoSurface.h"
-#include "../../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/Framework/Applications/Sdl/SdlEngine.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,319 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SdlEngine.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "../../../Resources/Orthanc/Core/Logging.h"
-
-#include <SDL.h>
-
-namespace OrthancStone
-{
-  void SdlEngine::RenderFrame()
-  {
-    if (!viewportChanged_)
-    {
-      return;
-    }
-
-    viewportChanged_ = false;
-
-    if (buffering_.RenderOffscreen(viewport_))
-    {
-      // 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);
-    }
-  }
-
-
-  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)
-  {
-    int result = KeyboardModifiers_None;
-
-    if (keyboardState != NULL)
-    {
-      if (SDL_SCANCODE_LSHIFT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LSHIFT])
-      {
-        result |= KeyboardModifiers_Shift;
-      }
-
-      if (SDL_SCANCODE_RSHIFT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_RSHIFT])
-      {
-        result |= KeyboardModifiers_Shift;
-      }
-
-      if (SDL_SCANCODE_LCTRL < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LCTRL])
-      {
-        result |= KeyboardModifiers_Control;
-      }
-
-      if (SDL_SCANCODE_RCTRL < scancodeCount &&
-          keyboardState[SDL_SCANCODE_RCTRL])
-      {
-        result |= KeyboardModifiers_Control;
-      }
-
-      if (SDL_SCANCODE_LALT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LALT])
-      {
-        result |= KeyboardModifiers_Alt;
-      }
-
-      if (SDL_SCANCODE_RALT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_RALT])
-      {
-        result |= KeyboardModifiers_Alt;
-      }
-    }
-
-    return static_cast<KeyboardModifiers>(result);
-  }
-
-
-  void SdlEngine::SetSize(unsigned int width,
-                          unsigned int height)
-  {
-    buffering_.SetSize(width, height, viewport_);
-    viewportChanged_ = true;
-    Refresh();
-  }
-
-
-  void SdlEngine::Stop()
-  {
-    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);
-  }
-
-
-  void SdlEngine::Run()
-  {
-    int scancodeCount = 0;
-    const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-
-    bool stop = false;
-    while (!stop)
-    {
-      Refresh();
-
-      SDL_Event event;
-
-      while (SDL_PollEvent(&event))
-      {
-        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);
-
-          switch (event.button.button)
-          {
-            case SDL_BUTTON_LEFT:
-              viewport_.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);
-              break;
-            
-            case SDL_BUTTON_MIDDLE:
-              viewport_.MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers);
-              break;
-
-            default:
-              break;
-          }
-        }
-        else if (event.type == SDL_MOUSEMOTION)
-        {
-          viewport_.MouseMove(event.button.x, event.button.y);
-        }
-        else if (event.type == SDL_MOUSEBUTTONUP)
-        {
-          viewport_.MouseUp();
-        }
-        else if (event.type == SDL_WINDOWEVENT)
-        {
-          switch (event.window.event)
-          {
-            case SDL_WINDOWEVENT_LEAVE:
-              viewport_.MouseLeave();
-              break;
-
-            case SDL_WINDOWEVENT_ENTER:
-              viewport_.MouseEnter();
-              break;
-
-            case SDL_WINDOWEVENT_SIZE_CHANGED:
-              SetSize(event.window.data1, event.window.data2);
-              break;
-
-            default:
-              break;
-          }
-        }
-        else if (event.type == SDL_MOUSEWHEEL)
-        {
-          KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount);
-
-          int x, y;
-          SDL_GetMouseState(&x, &y);
-
-          if (event.wheel.y > 0)
-          {
-            viewport_.MouseWheel(MouseWheelDirection_Up, x, y, modifiers);
-          }
-          else if (event.wheel.y < 0)
-          {
-            viewport_.MouseWheel(MouseWheelDirection_Down, x, y, modifiers);
-          }
-        }
-        else if (event.type == SDL_KEYDOWN)
-        {
-          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;
-
-            default:
-              break;
-          }
-        }
-      }
-
-      SDL_Delay(10);   // Necessary for mouse wheel events to work
-    }
-
-    Stop();
-  }
-
-
-  void SdlEngine::GlobalInitialize()
-  {
-    SDL_Init(SDL_INIT_VIDEO);
-  }
-
-
-  void SdlEngine::GlobalFinalize()
-  {
-    SDL_Quit();
-  }
-}
-
-#endif
--- a/Framework/Applications/Sdl/SdlEngine.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SdlBuffering.h"
-#include "../../Toolbox/BinarySemaphore.h"
-
-#include <boost/thread.hpp>
-
-namespace OrthancStone
-{
-  class SdlEngine : public IViewport::IChangeObserver
-  {
-  private:
-    SdlWindow&        window_;
-    IViewport&        viewport_;
-    SdlBuffering      buffering_;
-    boost::thread     renderThread_;
-    bool              continue_;
-    BinarySemaphore   renderFrame_;
-    uint32_t          refreshEvent_;
-    bool              viewportChanged_;
-
-    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);
-  
-    virtual ~SdlEngine();
-
-    virtual void NotifyChange(const IViewport& viewport)
-    {
-      viewportChanged_ = true;
-    }
-
-    void Run();
-
-    static void GlobalInitialize();
-
-    static void GlobalFinalize();
-  };
-}
-
-#endif
--- a/Framework/Applications/Sdl/SdlWindow.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SdlWindow.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "../../../Resources/Orthanc/Core/Logging.h"
-#include "../../../Resources/Orthanc/Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  SdlWindow::SdlWindow(const char* title,
-                       unsigned int width,
-                       unsigned int height,
-                       bool enableOpenGl) :
-    maximized_(false)
-  {
-    // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release
-    // build mode, the application crashes whenever the SDL window is
-    // resized or maximized
-
-    uint32_t windowFlags, rendererFlags;
-    if (enableOpenGl)
-    {
-      windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
-      rendererFlags = SDL_RENDERER_ACCELERATED;
-    }
-    else
-    {
-      windowFlags = SDL_WINDOW_RESIZABLE;
-      rendererFlags = SDL_RENDERER_SOFTWARE;
-    }
-    
-    window_ = SDL_CreateWindow(title,
-                               SDL_WINDOWPOS_UNDEFINED,
-                               SDL_WINDOWPOS_UNDEFINED,
-                               width, height, windowFlags);
-
-    if (window_ == NULL) 
-    {
-      LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError();
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-
-    renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags);
-    if (!renderer_)
-    {
-      LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError();
-      SDL_DestroyWindow(window_);
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  }
-
-
-  SdlWindow::~SdlWindow()
-  {
-    if (renderer_ != NULL)
-    { 
-      SDL_DestroyRenderer(renderer_);
-    }
-
-    if (window_ != NULL)
-    { 
-      SDL_DestroyWindow(window_);
-    }
-  }
-
-
-  unsigned int SdlWindow::GetWidth() const
-  {
-    int w = -1;
-    SDL_GetWindowSize(window_, &w, NULL);
-
-    if (w < 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      return static_cast<unsigned int>(w);
-    }
-  }
-
-
-  unsigned int SdlWindow::GetHeight() const
-  {
-    int h = -1;
-    SDL_GetWindowSize(window_, NULL, &h);
-
-    if (h < 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-    else
-    {
-      return static_cast<unsigned int>(h);
-    }
-  }
-
-
-  void SdlWindow::Render(SDL_Surface* surface)
-  {
-    //SDL_RenderClear(renderer_);
-
-    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface);
-    if (texture != NULL)
-    {
-      SDL_RenderCopy(renderer_, texture, NULL, NULL);
-      SDL_DestroyTexture(texture);
-    }
-
-    SDL_RenderPresent(renderer_);
-  }
-
-
-  void SdlWindow::ToggleMaximize()
-  {
-    if (maximized_)
-    {
-      SDL_RestoreWindow(window_);
-      maximized_ = false;
-    }
-    else
-    {
-      SDL_MaximizeWindow(window_);
-      maximized_ = true;
-    }
-  }
-}
-
-#endif
--- a/Framework/Applications/Sdl/SdlWindow.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 <SDL_render.h>
-#include <SDL_video.h>
-#include <boost/noncopyable.hpp>
-
-namespace OrthancStone
-{
-  class SdlWindow : public boost::noncopyable
-  {
-  private:
-    SDL_Window    *window_;
-    SDL_Renderer  *renderer_;
-    bool           maximized_;
-
-  public:
-    SdlWindow(const char* title,
-              unsigned int width,
-              unsigned int height,
-              bool enableOpenGl);
-
-    ~SdlWindow();
-
-    unsigned int GetWidth() const;
-
-    unsigned int GetHeight() const;
-
-    void Render(SDL_Surface* surface);
-
-    void ToggleMaximize();
-  };
-}
-
-#endif
--- a/Framework/Toolbox/BinarySemaphore.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /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 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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/Framework/Toolbox/BinarySemaphore.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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/Resources/CMake/OrthancStone.cmake	Thu Apr 27 11:09:43 2017 +0200
+++ b/Resources/CMake/OrthancStone.cmake	Thu Apr 27 14:50:20 2017 +0200
@@ -176,11 +176,13 @@
 #####################################################################
 
 list(APPEND ORTHANC_STONE_SOURCES
-  ${ORTHANC_STONE_DIR}/Framework/Applications/BasicApplicationContext.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Applications/IBasicApplication.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Applications/Sdl/SdlBuffering.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Applications/Sdl/SdlEngine.cpp
-  ${ORTHANC_STONE_DIR}/Framework/Applications/Sdl/SdlWindow.cpp
+  ${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
@@ -192,7 +194,6 @@
   ${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/BinarySemaphore.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomFrameConverter.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DicomStructureSet.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/DownloadStack.cpp
--- a/Samples/BasicPetCtFusionApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /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 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleInteractor.h"
-
-#include "../Resources/Orthanc/Core/Logging.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class BasicPetCtFusionApplication : public SampleApplicationBase
-    {
-    private:
-      class Interactor : public SampleInteractor
-      {
-      public:
-        static void SetStyle(LayeredSceneWidget& widget,
-                             bool ct,
-                             bool pet)
-        {
-          if (ct)
-          {
-            RenderStyle style;
-            style.windowing_ = ImageWindowing_Bone;
-            widget.SetLayerStyle(0, style);
-          }
-          else
-          {
-            RenderStyle style;
-            style.visible_ = false;
-            widget.SetLayerStyle(0, style);
-          }
-
-          if (ct && pet)
-          {
-            RenderStyle style;
-            style.applyLut_ = true;
-            style.alpha_ = 0.5;
-            widget.SetLayerStyle(1, style);
-          }
-          else if (pet)
-          {
-            RenderStyle style;
-            style.applyLut_ = true;
-            widget.SetLayerStyle(1, style);
-          }
-          else
-          {
-            RenderStyle style;
-            style.visible_ = false;
-            widget.SetLayerStyle(1, style);
-          }
-        }
-
-
-        static bool IsVisible(LayeredSceneWidget& widget,
-                              size_t layer)
-        {
-          RenderStyle style = widget.GetLayerStyle(layer);
-          return style.visible_;
-        }
-
-
-        static void ToggleInterpolation(LayeredSceneWidget& widget,
-                                        size_t layer)
-        {
-          RenderStyle style = widget.GetLayerStyle(layer);
-         
-          if (style.interpolation_ == ImageInterpolation_Linear)
-          {
-            style.interpolation_ = ImageInterpolation_Nearest;
-          }
-          else
-          {
-            style.interpolation_ = ImageInterpolation_Linear;
-          }
-
-          widget.SetLayerStyle(layer, style);
-        }
-
-
-        Interactor(VolumeImage& volume,
-                   VolumeProjection projection, 
-                   bool reverse) :
-          SampleInteractor(volume, projection, reverse)
-        {
-        }
-
-
-        virtual void KeyPressed(WorldSceneWidget& widget,
-                                char key,
-                                KeyboardModifiers modifiers,
-                                IStatusBar* statusBar)
-        {
-          LayeredSceneWidget& layered = dynamic_cast<LayeredSceneWidget&>(widget);
-
-          switch (key)
-          {
-            case 'c':
-              // Toggle the visibility of the CT layer
-              SetStyle(layered, !IsVisible(layered, 0), IsVisible(layered, 1));
-              break;
-
-            case 'p':
-              // Toggle the visibility of the PET layer
-              SetStyle(layered, IsVisible(layered, 0), !IsVisible(layered, 1));
-              break;
-
-            case 'i':
-            {
-              // Toggle on/off the interpolation
-              ToggleInterpolation(layered, 0);
-              ToggleInterpolation(layered, 1);
-              break;
-            }
-
-            default:
-              break;
-          }
-        }
-      };
-
-
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("ct", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the CT series")
-          ("pet", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the PET series")
-          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
-           "Number of download threads for the CT series")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        using namespace OrthancStone;
-
-        if (parameters.count("ct") != 1 ||
-            parameters.count("pet") != 1)
-        {
-          LOG(ERROR) << "The series ID is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        std::string ct = parameters["ct"].as<std::string>();
-        std::string pet = parameters["pet"].as<std::string>();
-        unsigned int threads = parameters["threads"].as<unsigned int>();
-
-        VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads);
-        VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1);
-
-        // Take the PET volume as the reference for the slices
-        std::auto_ptr<Interactor> interactor(new Interactor(petVolume, VolumeProjection_Axial, false /* don't reverse normal */));
-
-        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->AddLayer(new VolumeImage::LayerFactory(ctVolume));
-        widget->AddLayer(new VolumeImage::LayerFactory(petVolume));
-        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
-        widget->SetInteractor(*interactor);
-
-        Interactor::SetStyle(*widget, true, true);   // Initially, show both CT and PET layers
-
-        context.AddInteractor(interactor.release());
-        context.SetCentralWidget(widget.release());
-
-        statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode");
-        statusBar.SetMessage("Use the key \"c\" to show/hide the CT layer");
-        statusBar.SetMessage("Use the key \"p\" to show/hide the PET layer");
-        statusBar.SetMessage("Use the key \"i\" to toggle the smoothing of the images");
-      }
-    };
-  }
-}
--- a/Samples/EmptyApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleApplicationBase.h"
-
-#include "../Framework/Widgets/EmptyWidget.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class EmptyApplication : public SampleApplicationBase
-    {
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("red", boost::program_options::value<int>()->default_value(255), "Background color: red channel")
-          ("green", boost::program_options::value<int>()->default_value(0), "Background color: green channel")
-          ("blue", boost::program_options::value<int>()->default_value(0), "Background color: blue channel")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        int red = parameters["red"].as<int>();
-        int green = parameters["green"].as<int>();
-        int blue = parameters["blue"].as<int>();
-
-        context.SetCentralWidget(new EmptyWidget(red, green, blue));
-      }
-    };
-  }
-}
--- a/Samples/LayoutPetCtFusionApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,398 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleInteractor.h"
-
-#include "../Framework/Layers/SiblingSliceLocationFactory.h"
-#include "../Framework/Layers/DicomStructureSetRendererFactory.h"
-#include "../Framework/Widgets/LayoutWidget.h"
-
-#include "../Resources/Orthanc/Core/Logging.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class LayoutPetCtFusionApplication : 
-      public SampleApplicationBase,
-      public LayeredSceneWidget::ISliceObserver,
-      public WorldSceneWidget::IWorldObserver
-    {
-    private:
-      class Interactor : public SampleInteractor
-      {
-      private:
-        LayoutPetCtFusionApplication& that_;
-
-      public:
-        Interactor(LayoutPetCtFusionApplication& that,
-                   VolumeImage& volume,
-                   VolumeProjection projection, 
-                   bool reverse) :
-          SampleInteractor(volume, projection, reverse),
-          that_(that)
-        {
-        }
-
-        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                            const SliceGeometry& slice,
-                                                            const ViewportGeometry& view,
-                                                            MouseButton button,
-                                                            double x,
-                                                            double y,
-                                                            IStatusBar* statusBar)
-        {
-          if (button == MouseButton_Left)
-          {
-            // Center the sibling views over the clicked point
-            Vector p = slice.MapSliceToWorldCoordinates(x, y);
-
-            if (statusBar != NULL)
-            {
-              char buf[64];
-              sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
-              statusBar->SetMessage(buf);
-            }
-
-            that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p);
-            that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p);
-            that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p);
-          }
-
-          return NULL;
-        }
-
-        virtual void KeyPressed(WorldSceneWidget& widget,
-                                char key,
-                                KeyboardModifiers modifiers,
-                                IStatusBar* statusBar)
-        {
-          if (key == 's')
-          {
-            that_.SetDefaultView();
-          }
-        }
-      };
-
-      bool                 processingEvent_;
-      Interactor*          interactorAxial_;
-      Interactor*          interactorCoronal_;
-      Interactor*          interactorSagittal_;
-      LayeredSceneWidget*  ctAxial_;
-      LayeredSceneWidget*  ctCoronal_;
-      LayeredSceneWidget*  ctSagittal_;
-      LayeredSceneWidget*  petAxial_;
-      LayeredSceneWidget*  petCoronal_;
-      LayeredSceneWidget*  petSagittal_;
-      LayeredSceneWidget*  fusionAxial_;
-      LayeredSceneWidget*  fusionCoronal_;
-      LayeredSceneWidget*  fusionSagittal_;
-
-
-      void SetDefaultView()
-      {
-        petAxial_->SetDefaultView();
-        petCoronal_->SetDefaultView();
-        petSagittal_->SetDefaultView();
-      }
-
-
-      void AddLayer(LayeredSceneWidget& widget,
-                    VolumeImage& volume,
-                    bool isCt)
-      {
-        size_t layer;
-        widget.AddLayer(layer, new VolumeImage::LayerFactory(volume));
-
-        if (isCt)
-        {
-          RenderStyle style; 
-          style.windowing_ = ImageWindowing_Bone;
-          widget.SetLayerStyle(layer, style);
-        }
-        else
-        {
-          RenderStyle style; 
-          style.applyLut_ = true;
-          style.alpha_ = (layer == 0 ? 1.0f : 0.5f);
-          widget.SetLayerStyle(layer, style);
-        }
-      }
-
-
-      void ConnectSiblingLocations(LayeredSceneWidget& axial,
-                                   LayeredSceneWidget& coronal,
-                                   LayeredSceneWidget& sagittal)
-      {
-        SiblingSliceLocationFactory::Configure(axial, coronal);
-        SiblingSliceLocationFactory::Configure(axial, sagittal);
-        SiblingSliceLocationFactory::Configure(coronal, sagittal);
-      }
-
-
-      void SynchronizeView(const WorldSceneWidget& source,
-                           const ViewportGeometry& view,
-                           LayeredSceneWidget& widget1,
-                           LayeredSceneWidget& widget2,
-                           LayeredSceneWidget& widget3)
-      {
-        if (&source == &widget1 ||
-            &source == &widget2 ||
-            &source == &widget3)
-        {
-          if (&source != &widget1)
-          {
-            widget1.SetView(view);
-          }
-
-          if (&source != &widget2)
-          {
-            widget2.SetView(view);
-          }
-
-          if (&source != &widget3)
-          {
-            widget3.SetView(view);
-          }
-        }
-      }
-
-
-      void SynchronizeSlice(const LayeredSceneWidget& source,
-                            const SliceGeometry& slice,
-                            LayeredSceneWidget& widget1,
-                            LayeredSceneWidget& widget2,
-                            LayeredSceneWidget& widget3)
-      {
-        if (&source == &widget1 ||
-            &source == &widget2 ||
-            &source == &widget3)
-        {
-          if (&source != &widget1)
-          {
-            widget1.SetSlice(slice);
-          }
-
-          if (&source != &widget2)
-          {
-            widget2.SetSlice(slice);
-          }
-
-          if (&source != &widget3)
-          {
-            widget3.SetSlice(slice);
-          }
-        }
-      }
-
-
-      LayeredSceneWidget* CreateWidget()
-      {
-        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->Register(dynamic_cast<WorldSceneWidget::IWorldObserver&>(*this));
-        widget->Register(dynamic_cast<LayeredSceneWidget::ISliceObserver&>(*this));
-        return widget.release();
-      }
-
-
-      void CreateLayout(BasicApplicationContext& context)
-      {
-        std::auto_ptr<OrthancStone::LayoutWidget> layout(new OrthancStone::LayoutWidget);
-        layout->SetBackgroundCleared(true);
-        //layout->SetBackgroundColor(255,0,0);
-        layout->SetPadding(5);
-
-        OrthancStone::LayoutWidget& layoutA = dynamic_cast<OrthancStone::LayoutWidget&>
-          (layout->AddWidget(new OrthancStone::LayoutWidget));
-        layoutA.SetPadding(0, 0, 0, 0, 5);
-        layoutA.SetVertical();
-        petAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutA.AddWidget(CreateWidget()));
-        OrthancStone::LayoutWidget& layoutA2 = dynamic_cast<OrthancStone::LayoutWidget&>
-          (layoutA.AddWidget(new OrthancStone::LayoutWidget));
-        layoutA2.SetPadding(0, 0, 0, 0, 5);
-        petSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget()));
-        petCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget()));
-
-        OrthancStone::LayoutWidget& layoutB = dynamic_cast<OrthancStone::LayoutWidget&>
-          (layout->AddWidget(new OrthancStone::LayoutWidget));
-        layoutB.SetPadding(0, 0, 0, 0, 5);
-        layoutB.SetVertical();
-        ctAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutB.AddWidget(CreateWidget()));
-        OrthancStone::LayoutWidget& layoutB2 = dynamic_cast<OrthancStone::LayoutWidget&>
-          (layoutB.AddWidget(new OrthancStone::LayoutWidget));
-        layoutB2.SetPadding(0, 0, 0, 0, 5);
-        ctSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget()));
-        ctCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget()));
-
-        OrthancStone::LayoutWidget& layoutC = dynamic_cast<OrthancStone::LayoutWidget&>
-          (layout->AddWidget(new OrthancStone::LayoutWidget));
-        layoutC.SetPadding(0, 0, 0, 0, 5);
-        layoutC.SetVertical();
-        fusionAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutC.AddWidget(CreateWidget()));
-        OrthancStone::LayoutWidget& layoutC2 = dynamic_cast<OrthancStone::LayoutWidget&>
-          (layoutC.AddWidget(new OrthancStone::LayoutWidget));
-        layoutC2.SetPadding(0, 0, 0, 0, 5);
-        fusionSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget()));
-        fusionCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget()));
-  
-        context.SetCentralWidget(layout.release());
-      }
-
-
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("ct", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the CT series")
-          ("pet", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the PET series")
-          ("rt", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the DICOM RT-STRUCT series (optional)")
-          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
-           "Number of download threads for the CT series")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        using namespace OrthancStone;
-
-        processingEvent_ = true;
-
-        if (parameters.count("ct") != 1 ||
-            parameters.count("pet") != 1)
-        {
-          LOG(ERROR) << "The series ID is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        std::string ct = parameters["ct"].as<std::string>();
-        std::string pet = parameters["pet"].as<std::string>();
-        unsigned int threads = parameters["threads"].as<unsigned int>();
-
-        VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads);
-        VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1);
-
-        // Take the PET volume as the reference for the slices
-        interactorAxial_ = &dynamic_cast<Interactor&>
-          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false)));
-        interactorCoronal_ = &dynamic_cast<Interactor&>
-          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false)));
-        interactorSagittal_ = &dynamic_cast<Interactor&>
-          (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true)));
-
-        CreateLayout(context);
-
-        AddLayer(*ctAxial_, ctVolume, true);
-        AddLayer(*ctCoronal_, ctVolume, true);
-        AddLayer(*ctSagittal_, ctVolume, true);
-
-        AddLayer(*petAxial_, petVolume, false);
-        AddLayer(*petCoronal_, petVolume, false);
-        AddLayer(*petSagittal_, petVolume, false);
-
-        AddLayer(*fusionAxial_, ctVolume, true);
-        AddLayer(*fusionAxial_, petVolume, false);
-        AddLayer(*fusionCoronal_, ctVolume, true);
-        AddLayer(*fusionCoronal_, petVolume, false);
-        AddLayer(*fusionSagittal_, ctVolume, true);
-        AddLayer(*fusionSagittal_, petVolume, false);
-
-        if (parameters.count("rt") == 1)
-        {
-          DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as<std::string>());
-
-          Vector p = rtStruct.GetStructureCenter(0);
-          interactorAxial_->GetCursor().LookupSliceContainingPoint(p);
-
-          ctAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
-          petAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
-          fusionAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct));
-        }        
-
-        ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); 
-        ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); 
-        ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); 
-
-        interactorAxial_->AddWidget(*ctAxial_);
-        interactorAxial_->AddWidget(*petAxial_);
-        interactorAxial_->AddWidget(*fusionAxial_);
-        
-        interactorCoronal_->AddWidget(*ctCoronal_);
-        interactorCoronal_->AddWidget(*petCoronal_);
-        interactorCoronal_->AddWidget(*fusionCoronal_);
-        
-        interactorSagittal_->AddWidget(*ctSagittal_);
-        interactorSagittal_->AddWidget(*petSagittal_);
-        interactorSagittal_->AddWidget(*fusionSagittal_);
-
-        processingEvent_ = false;
-
-        statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode");
-        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
-      }
-
-      virtual void NotifySizeChange(const WorldSceneWidget& source,
-                                    ViewportGeometry& view)
-      {
-        view.SetDefaultView();
-      }
-
-      virtual void NotifyViewChange(const WorldSceneWidget& source,
-                                    const ViewportGeometry& view)
-      {
-        if (!processingEvent_)  // Avoid reentrant calls
-        {
-          processingEvent_ = true;
-
-          SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_);
-          SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_);
-          SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_);
-          
-          processingEvent_ = false;
-        }
-      }
-
-      virtual void NotifySliceChange(const LayeredSceneWidget& source,
-                                     const SliceGeometry& slice)
-      {
-        if (!processingEvent_)  // Avoid reentrant calls
-        {
-          processingEvent_ = true;
-
-          SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_);
-          SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_);
-          SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_);
-          
-          processingEvent_ = false;
-        }
-      }
-    };
-  }
-}
--- a/Samples/SampleApplicationBase.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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/Applications/IBasicApplication.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class SampleApplicationBase : public IBasicApplication
-    {
-    public:
-      virtual std::string GetTitle() const
-      {
-        return "Stone of Orthanc - Sample";
-      }
-
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-      }
-
-      virtual void Finalize()
-      {
-      }
-    };
-  }
-}
--- a/Samples/SampleInteractor.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleApplicationBase.h"
-
-#include "../Framework/Widgets/LayeredSceneWidget.h"
-#include "../Framework/Widgets/IWorldSceneInteractor.h"
-#include "../Framework/Toolbox/ParallelSlicesCursor.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    /**
-     * This is a basic mouse interactor for sample applications. It
-     * contains a set of parallel slices in the 3D space. The mouse
-     * wheel events make the widget change the slice that is
-     * displayed.
-     **/
-    class SampleInteractor : public IWorldSceneInteractor
-    {
-    private:
-      ParallelSlicesCursor   cursor_;
-
-    public:
-      SampleInteractor(VolumeImage& volume,
-                       VolumeProjection projection, 
-                       bool reverse)
-      {
-        std::auto_ptr<ParallelSlices> slices(volume.GetGeometry(projection, reverse));
-        cursor_.SetGeometry(*slices);
-      }
-
-      SampleInteractor(ISeriesLoader& series, 
-                       bool reverse)
-      {
-        if (reverse)
-        {
-          std::auto_ptr<ParallelSlices> slices(series.GetGeometry().Reverse());
-          cursor_.SetGeometry(*slices);
-        }
-        else
-        {
-          cursor_.SetGeometry(series.GetGeometry());
-        }
-      }
-
-      SampleInteractor(const ParallelSlices& slices)
-      {
-        cursor_.SetGeometry(slices);
-      }
-
-      ParallelSlicesCursor& GetCursor()
-      {
-        return cursor_;
-      }
-
-      void AddWidget(LayeredSceneWidget& widget)
-      {
-        widget.SetInteractor(*this);
-        widget.SetSlice(cursor_.GetCurrentSlice());
-      }
-
-      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
-                                                          const SliceGeometry& slice,
-                                                          const ViewportGeometry& view,
-                                                          MouseButton button,
-                                                          double x,
-                                                          double y,
-                                                          IStatusBar* statusBar)
-      {
-        return NULL;
-      }
-
-      virtual void MouseOver(CairoContext& context,
-                             WorldSceneWidget& widget,
-                             const SliceGeometry& slice,
-                             const ViewportGeometry& view,
-                             double x,
-                             double y,
-                             IStatusBar* statusBar)
-      {
-      }
-
-      virtual void MouseWheel(WorldSceneWidget& widget,
-                              MouseWheelDirection direction,
-                              KeyboardModifiers modifiers,
-                              IStatusBar* statusBar)
-      {
-        if (cursor_.ApplyWheelEvent(direction, modifiers))
-        {
-          dynamic_cast<LayeredSceneWidget&>(widget).SetSlice(cursor_.GetCurrentSlice());
-        }
-      }
-
-      virtual void KeyPressed(WorldSceneWidget& widget,
-                              char key,
-                              KeyboardModifiers modifiers,
-                              IStatusBar* statusBar)
-      {
-      }
-
-      void LookupSliceContainingPoint(LayeredSceneWidget& widget,
-                                      const Vector& p)
-      {
-        if (cursor_.LookupSliceContainingPoint(p))
-        {
-          widget.SetSlice(cursor_.GetCurrentSlice());
-        }
-      }
-    };
-  }
-}
--- a/Samples/SampleMainSdl.cpp	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script
-
-#if ORTHANC_STONE_SAMPLE == 1
-#include "EmptyApplication.h"
-typedef OrthancStone::Samples::EmptyApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 2
-#include "TestPatternApplication.h"
-typedef OrthancStone::Samples::TestPatternApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 3
-#include "SingleFrameApplication.h"
-typedef OrthancStone::Samples::SingleFrameApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 4
-#include "SingleVolumeApplication.h"
-typedef OrthancStone::Samples::SingleVolumeApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 5
-#include "BasicPetCtFusionApplication.h"
-typedef OrthancStone::Samples::BasicPetCtFusionApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 6
-#include "SynchronizedSeriesApplication.h"
-typedef OrthancStone::Samples::SynchronizedSeriesApplication Application;
-
-#elif ORTHANC_STONE_SAMPLE == 7
-#include "LayoutPetCtFusionApplication.h"
-typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application;
-
-#else
-#error Please set the ORTHANC_STONE_SAMPLE macro
-#endif
-
-
-int main(int argc, char* argv[]) 
-{
-  Application application;
-
-  return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv);
-}
--- a/Samples/SingleFrameApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleApplicationBase.h"
-
-#include "../Framework/Layers/SingleFrameRendererFactory.h"
-#include "../Framework/Widgets/LayeredSceneWidget.h"
-#include "../Resources/Orthanc/Core/Logging.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class SingleFrameApplication : public SampleApplicationBase
-    {
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("instance", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the instance")
-          ("frame", boost::program_options::value<unsigned int>()->default_value(0),
-           "Number of the frame, for multi-frame DICOM instances")
-          ("smooth", boost::program_options::value<bool>()->default_value(true), 
-           "Enable linear interpolation to smooth the image")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        using namespace OrthancStone;
-
-        if (parameters.count("instance") != 1)
-        {
-          LOG(ERROR) << "The instance ID is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        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<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->SetSlice(renderer->GetSliceGeometry());
-        widget->AddLayer(renderer.release());
-
-        if (parameters["smooth"].as<bool>())
-        {
-          RenderStyle s; 
-          s.interpolation_ = ImageInterpolation_Linear;
-          widget->SetLayerStyle(0, s);
-        }
-
-        context.SetCentralWidget(widget.release());
-      }
-    };
-  }
-}
--- a/Samples/SingleVolumeApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-/**
- * Stone of Orthanc
- * 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 Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleInteractor.h"
-
-#include "../Resources/Orthanc/Core/Toolbox.h"
-#include "../Framework/Layers/LineMeasureTracker.h"
-#include "../Framework/Layers/CircleMeasureTracker.h"
-#include "../Resources/Orthanc/Core/Logging.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class SingleVolumeApplication : public SampleApplicationBase
-    {
-    private:
-      class Interactor : public SampleInteractor
-      {
-      private:
-        enum MouseMode
-        {
-          MouseMode_None,
-          MouseMode_TrackCoordinates,
-          MouseMode_LineMeasure,
-          MouseMode_CircleMeasure
-        };
-
-        MouseMode mouseMode_;
-
-        void SetMouseMode(MouseMode mode,
-                          IStatusBar* statusBar)
-        {
-          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;
-
-              case MouseMode_CircleMeasure:
-                statusBar->SetMessage("Mouse clicks will now draw circles");
-                break;
-
-              default:
-                break;
-            }
-          }
-        }
-
-      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)
-          {
-            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;
-            }
-          }
-
-          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);
-          }
-        }
-
-
-        virtual void KeyPressed(WorldSceneWidget& widget,
-                                char key,
-                                KeyboardModifiers modifiers,
-                                IStatusBar* statusBar)
-        {
-          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;
-          }
-        }
-      };
-
-
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("series", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the series")
-          ("threads", boost::program_options::value<unsigned int>()->default_value(3), 
-           "Number of download threads")
-          ("projection", boost::program_options::value<std::string>()->default_value("axial"), 
-           "Projection of interest (can be axial, sagittal or coronal)")
-          ("reverse", boost::program_options::value<bool>()->default_value(false), 
-           "Reverse the normal direction of the volume")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        using namespace OrthancStone;
-
-        if (parameters.count("series") != 1)
-        {
-          LOG(ERROR) << "The series ID is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        std::string series = parameters["series"].as<std::string>();
-        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")
-        {
-          projection = VolumeProjection_Axial;
-        }
-        else if (tmp == "sagittal")
-        {
-          projection = VolumeProjection_Sagittal;
-        }
-        else if (tmp == "coronal")
-        {
-          projection = VolumeProjection_Coronal;
-        }
-        else
-        {
-          LOG(ERROR) << "Unknown projection: " << tmp;
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        VolumeImage& volume = context.AddSeriesVolume(series, true /* progressive download */, threads);
-
-        std::auto_ptr<Interactor> interactor(new Interactor(volume, projection, reverse));
-
-        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->AddLayer(new VolumeImage::LayerFactory(volume));
-        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
-        widget->SetInteractor(*interactor);
-
-        context.AddInteractor(interactor.release());
-        context.SetCentralWidget(widget.release());
-
-        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");
-      }
-    };
-  }
-}
--- a/Samples/SynchronizedSeriesApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /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 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleInteractor.h"
-
-#include "../Framework/Toolbox/OrthancSeriesLoader.h"
-#include "../Framework/Layers/SeriesFrameRendererFactory.h"
-#include "../Framework/Layers/SiblingSliceLocationFactory.h"
-#include "../Framework/Widgets/LayoutWidget.h"
-#include "../Resources/Orthanc/Core/Logging.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class SynchronizedSeriesApplication : public SampleApplicationBase
-    {
-    private:   
-      LayeredSceneWidget* CreateSeriesWidget(BasicApplicationContext& context,
-                                             const std::string& series)
-      {
-        std::auto_ptr<ISeriesLoader> loader(new OrthancSeriesLoader(context.GetOrthancConnection(), series));
-
-        std::auto_ptr<SampleInteractor> interactor(new SampleInteractor(*loader, false));
-
-        std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget);
-        widget->AddLayer(new SeriesFrameRendererFactory(loader.release(), false));
-        widget->SetSlice(interactor->GetCursor().GetCurrentSlice());
-        widget->SetInteractor(*interactor);
-
-        context.AddInteractor(interactor.release());
-
-        return widget.release();
-      }
-
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("a", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the 1st series")
-          ("b", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the 2nd series")
-          ("c", boost::program_options::value<std::string>(), 
-           "Orthanc ID of the 3rd series")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        if (parameters.count("a") != 1 ||
-            parameters.count("b") != 1 ||
-            parameters.count("c") != 1)
-        {
-          LOG(ERROR) << "At least one of the three series IDs is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        std::auto_ptr<LayeredSceneWidget> a(CreateSeriesWidget(context, parameters["a"].as<std::string>()));
-        std::auto_ptr<LayeredSceneWidget> b(CreateSeriesWidget(context, parameters["b"].as<std::string>()));
-        std::auto_ptr<LayeredSceneWidget> c(CreateSeriesWidget(context, parameters["c"].as<std::string>()));
-
-        SiblingSliceLocationFactory::Configure(*a, *b);
-        SiblingSliceLocationFactory::Configure(*a, *c);
-        SiblingSliceLocationFactory::Configure(*b, *c);
-
-        std::auto_ptr<LayoutWidget> layout(new LayoutWidget);
-        layout->SetPadding(5);
-        layout->AddWidget(a.release());
-
-        std::auto_ptr<LayoutWidget> layoutB(new LayoutWidget);
-        layoutB->SetVertical();
-        layoutB->SetPadding(5);
-        layoutB->AddWidget(b.release());
-        layoutB->AddWidget(c.release());
-        layout->AddWidget(layoutB.release());
-
-        context.SetCentralWidget(layout.release());        
-      }
-    };
-  }
-}
--- a/Samples/TestPatternApplication.h	Thu Apr 27 11:09:43 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You 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 "SampleApplicationBase.h"
-
-#include "../Framework/Widgets/TestCairoWidget.h"
-#include "../Framework/Widgets/TestWorldSceneWidget.h"
-#include "../Framework/Widgets/LayoutWidget.h"
-
-namespace OrthancStone
-{
-  namespace Samples
-  {
-    class TestPatternApplication : public SampleApplicationBase
-    {
-    public:
-      virtual void DeclareCommandLineOptions(boost::program_options::options_description& options)
-      {
-        boost::program_options::options_description generic("Sample options");
-        generic.add_options()
-          ("animate", boost::program_options::value<bool>()->default_value(true), "Animate the test pattern")
-          ;
-
-        options.add(generic);    
-      }
-
-      virtual void Initialize(BasicApplicationContext& context,
-                              IStatusBar& statusBar,
-                              const boost::program_options::variables_map& parameters)
-      {
-        using namespace OrthancStone;
-
-        std::auto_ptr<LayoutWidget> layout(new LayoutWidget);
-        layout->SetPadding(10);
-        layout->SetBackgroundCleared(true);
-        layout->AddWidget(new TestCairoWidget(parameters["animate"].as<bool>()));
-        layout->AddWidget(new TestWorldSceneWidget);
-
-        context.SetCentralWidget(layout.release());
-      }
-    };
-  }
-}