diff Samples/Sdl/Loader.cpp @ 860:238693c3bc51 am-dev

merge default -> am-dev
author Alain Mazy <alain@mazy.be>
date Mon, 24 Jun 2019 14:35:00 +0200
parents 47fc7919977d
children 2d8ab34c8c91
line wrap: on
line diff
--- a/Samples/Sdl/Loader.cpp	Wed Jun 19 17:36:33 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Mon Jun 24 14:35:00 2019 +0200
@@ -18,2245 +18,36 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-// From Stone
-#include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
-#include "../../Framework/Loaders/BasicFetchingStrategy.h"
-#include "../../Framework/Messages/ICallable.h"
-#include "../../Framework/Messages/IMessage.h"
-#include "../../Framework/Messages/IObservable.h"
-#include "../../Framework/Messages/MessageBroker.h"
-#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Framework/Scene2D/FloatTextureSceneLayer.h"
-#include "../../Framework/Scene2D/Scene2D.h"
+
+#include "../../Framework/Loaders/DicomStructureSetLoader.h"
+#include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h"
+#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+#include "../../Framework/Oracle/SleepOracleCommand.h"
+#include "../../Framework/Oracle/ThreadedOracle.h"
+#include "../../Framework/Scene2D/CairoCompositor.h"
+#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
+#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h"
 #include "../../Framework/StoneInitialization.h"
-#include "../../Framework/Toolbox/GeometryToolbox.h"
-#include "../../Framework/Toolbox/SlicesSorter.h"
-#include "../../Framework/Volumes/ImageBuffer3D.h"
+#include "../../Framework/Volumes/VolumeSceneLayerSource.h"
+#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h"
+#include "../../Framework/Volumes/DicomVolumeImageReslicer.h"
 
 // From Orthanc framework
-#include <Core/Compression/GzipCompressor.h>
-#include <Core/Compression/ZlibCompressor.h>
-#include <Core/DicomFormat/DicomArray.h>
-#include <Core/DicomFormat/DicomImageInformation.h>
-#include <Core/HttpClient.h>
-#include <Core/IDynamicObject.h>
-#include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
-#include <Core/Images/JpegReader.h>
-#include <Core/Images/PamReader.h>
-#include <Core/Images/PngReader.h>
 #include <Core/Images/PngWriter.h>
 #include <Core/Logging.h>
-#include <Core/MultiThreading/SharedMessageQueue.h>
 #include <Core/OrthancException.h>
 #include <Core/SystemToolbox.h>
-#include <Core/Toolbox.h>
-
-#include <json/reader.h>
-#include <json/value.h>
-#include <json/writer.h>
-
-#include <list>
-#include <stdio.h>
-
-
-
-namespace Refactoring
-{
-  class IOracleCommand : public boost::noncopyable
-  {
-  public:
-    enum Type
-    {
-      Type_OrthancRestApi,
-      Type_GetOrthancImage,
-      Type_GetOrthancWebViewerJpeg
-    };
-
-    virtual ~IOracleCommand()
-    {
-    }
-
-    virtual Type GetType() const = 0;
-  };
-
-
-  class IMessageEmitter : public boost::noncopyable
-  {
-  public:
-    virtual ~IMessageEmitter()
-    {
-    }
-
-    virtual void EmitMessage(const OrthancStone::IObserver& observer,
-                             const OrthancStone::IMessage& message) = 0;
-  };
-
-
-  class IOracle : public boost::noncopyable
-  {
-  public:
-    virtual ~IOracle()
-    {
-    }
-
-    virtual void Schedule(const OrthancStone::IObserver& receiver,
-                          IOracleCommand* command) = 0;  // Takes ownership
-  };
-
-
-
-  class IVolumeSlicer : public boost::noncopyable
-  {
-  public:
-    virtual ~IVolumeSlicer()
-    {
-    }
-
-    virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) = 0;
-  };
-
-
-
-  class OracleCommandWithPayload : public IOracleCommand
-  {
-  private:
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-
-  public:
-    void SetPayload(Orthanc::IDynamicObject* payload)
-    {
-      if (payload == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-      else
-      {
-        payload_.reset(payload);
-      }    
-    }
-
-    bool HasPayload() const
-    {
-      return (payload_.get() != NULL);
-    }
-
-    const Orthanc::IDynamicObject& GetPayload() const
-    {
-      if (HasPayload())
-      {
-        return *payload_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-  };
-
-
-
-  class OracleCommandExceptionMessage : public OrthancStone::IMessage
-  {
-    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-  private:
-    const IOracleCommand&       command_;
-    Orthanc::OrthancException   exception_;
-
-  public:
-    OracleCommandExceptionMessage(const IOracleCommand& command,
-                                  const Orthanc::OrthancException& exception) :
-      command_(command),
-      exception_(exception)
-    {
-    }
-
-    OracleCommandExceptionMessage(const IOracleCommand& command,
-                                  const Orthanc::ErrorCode& error) :
-      command_(command),
-      exception_(error)
-    {
-    }
-
-    const IOracleCommand& GetCommand() const
-    {
-      return command_;
-    }
-    
-    const Orthanc::OrthancException& GetException() const
-    {
-      return exception_;
-    }
-  };
-  
-
-  typedef std::map<std::string, std::string>  HttpHeaders;
-
-  class OrthancRestApiCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      HttpHeaders   headers_;
-      std::string   answer_;
-
-    public:
-      SuccessMessage(const OrthancRestApiCommand& command,
-                     const HttpHeaders& answerHeaders,
-                     std::string& answer  /* will be swapped to avoid a memcpy() */) :
-        OriginMessage(command),
-        headers_(answerHeaders),
-        answer_(answer)
-      {
-      }
-
-      const std::string& GetAnswer() const
-      {
-        return answer_;
-      }
-
-      void ParseJsonBody(Json::Value& target) const
-      {
-        Json::Reader reader;
-        if (!reader.parse(answer_, target))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      }
-
-      const HttpHeaders&  GetAnswerHeaders() const
-      {
-        return headers_;
-      }
-    };
-
-
-  private:
-    Orthanc::HttpMethod  method_;
-    std::string          uri_;
-    std::string          body_;
-    HttpHeaders          headers_;
-    unsigned int         timeout_;
-
-    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
-
-  public:
-    OrthancRestApiCommand() :
-      method_(Orthanc::HttpMethod_Get),
-      uri_("/"),
-      timeout_(10)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_OrthancRestApi;
-    }
-
-    void SetMethod(Orthanc::HttpMethod method)
-    {
-      method_ = method;
-    }
-
-    void SetUri(const std::string& uri)
-    {
-      uri_ = uri;
-    }
-
-    void SetBody(const std::string& body)
-    {
-      body_ = body;
-    }
-
-    void SetBody(const Json::Value& json)
-    {
-      Json::FastWriter writer;
-      body_ = writer.write(json);
-    }
-
-    void SetHttpHeaders(const HttpHeaders& headers)
-    {
-      headers_ = headers;
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    Orthanc::HttpMethod GetMethod() const
-    {
-      return method_;
-    }
-
-    const std::string& GetUri() const
-    {
-      return uri_;
-    }
-
-    const std::string& GetBody() const
-    {
-      if (method_ == Orthanc::HttpMethod_Post ||
-          method_ == Orthanc::HttpMethod_Put)
-      {
-        return body_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-  };
-
-
-
-
-  class GetOrthancImageCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      std::auto_ptr<Orthanc::ImageAccessor>  image_;
-      Orthanc::MimeType                      mime_;
-
-    public:
-      SuccessMessage(const GetOrthancImageCommand& command,
-                     Orthanc::ImageAccessor* image,   // Takes ownership
-                     Orthanc::MimeType mime) :
-        OriginMessage(command),
-        image_(image),
-        mime_(mime)
-      {
-        if (image == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      const Orthanc::ImageAccessor& GetImage() const
-      {
-        return *image_;
-      }
-
-      Orthanc::MimeType GetMimeType() const
-      {
-        return mime_;
-      }
-    };
-
-
-  private:
-    std::string           uri_;
-    HttpHeaders           headers_;
-    unsigned int          timeout_;
-    bool                  hasExpectedFormat_;
-    Orthanc::PixelFormat  expectedFormat_;
-
-    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
-
-  public:
-    GetOrthancImageCommand() :
-      uri_("/"),
-      timeout_(10),
-      hasExpectedFormat_(false)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_GetOrthancImage;
-    }
-
-    void SetExpectedPixelFormat(Orthanc::PixelFormat format)
-    {
-      hasExpectedFormat_ = true;
-      expectedFormat_ = format;
-    }
-
-    void SetUri(const std::string& uri)
-    {
-      uri_ = uri;
-    }
-
-    void SetInstanceUri(const std::string& instance,
-                        Orthanc::PixelFormat pixelFormat)
-    {
-      uri_ = "/instances/" + instance;
-          
-      switch (pixelFormat)
-      {
-        case Orthanc::PixelFormat_RGB24:
-          uri_ += "/preview";
-          break;
-      
-        case Orthanc::PixelFormat_Grayscale16:
-          uri_ += "/image-uint16";
-          break;
-      
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          uri_ += "/image-int16";
-          break;
-      
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    const std::string& GetUri() const
-    {
-      return uri_;
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    void ProcessHttpAnswer(IMessageEmitter& emitter,
-                           const OrthancStone::IObserver& receiver,
-                           const std::string& answer,
-                           const HttpHeaders& answerHeaders) const
-    {
-      Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
-
-      for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
-           it != answerHeaders.end(); ++it)
-      {
-        std::string s;
-        Orthanc::Toolbox::ToLowerCase(s, it->first);
-
-        if (s == "content-type")
-        {
-          contentType = Orthanc::StringToMimeType(it->second);
-          break;
-        }
-      }
-
-      std::auto_ptr<Orthanc::ImageAccessor> image;
-
-      switch (contentType)
-      {
-        case Orthanc::MimeType_Png:
-        {
-          image.reset(new Orthanc::PngReader);
-          dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
-          break;
-        }
-
-        case Orthanc::MimeType_Pam:
-        {
-          image.reset(new Orthanc::PamReader);
-          dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
-          break;
-        }
-
-        case Orthanc::MimeType_Jpeg:
-        {
-          image.reset(new Orthanc::JpegReader);
-          dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
-          break;
-        }
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                          "Unsupported HTTP Content-Type for an image: " + 
-                                          std::string(Orthanc::EnumerationToString(contentType)));
-      }
-
-      if (hasExpectedFormat_)
-      {
-        if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
-            image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-        {
-          image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-        }
-
-        if (expectedFormat_ != image->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-        }
-      }
-
-      SuccessMessage message(*this, image.release(), contentType);
-      emitter.EmitMessage(receiver, message);
-    }
-  };
-
-
-
-  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      std::auto_ptr<Orthanc::ImageAccessor>  image_;
-
-    public:
-      SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
-                     Orthanc::ImageAccessor* image) :   // Takes ownership
-        OriginMessage(command),
-        image_(image)
-      {
-        if (image == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      const Orthanc::ImageAccessor& GetImage() const
-      {
-        return *image_;
-      }
-    };
-
-  private:
-    std::string           instanceId_;
-    unsigned int          frame_;
-    unsigned int          quality_;
-    HttpHeaders           headers_;
-    unsigned int          timeout_;
-    Orthanc::PixelFormat  expectedFormat_;
-
-    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
-
-  public:
-    GetOrthancWebViewerJpegCommand() :
-      frame_(0),
-      quality_(95),
-      timeout_(10),
-      expectedFormat_(Orthanc::PixelFormat_Grayscale8)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_GetOrthancWebViewerJpeg;
-    }
-
-    void SetExpectedPixelFormat(Orthanc::PixelFormat format)
-    {
-      expectedFormat_ = format;
-    }
-
-    void SetInstance(const std::string& instanceId)
-    {
-      instanceId_ = instanceId;
-    }
-
-    void SetFrame(unsigned int frame)
-    {
-      frame_ = frame;
-    }
-
-    void SetQuality(unsigned int quality)
-    {
-      if (quality <= 0 ||
-          quality > 100)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        quality_ = quality;
-      }
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    Orthanc::PixelFormat GetExpectedPixelFormat() const
-    {
-      return expectedFormat_;
-    }
-
-    const std::string& GetInstanceId() const
-    {
-      return instanceId_;
-    }
-
-    unsigned int GetFrame() const
-    {
-      return frame_;
-    }
-
-    unsigned int GetQuality() const
-    {
-      return quality_;
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-
-    std::string GetUri() const
-    {
-      return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
-              "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
-    }
-
-    void ProcessHttpAnswer(IMessageEmitter& emitter,
-                           const OrthancStone::IObserver& receiver,
-                           const std::string& answer) const
-    {
-      // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
-      
-      Json::Value encoded;
-
-      {
-        Json::Reader reader;
-        if (!reader.parse(answer, encoded))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      }
-
-      if (encoded.type() != Json::objectValue ||
-          !encoded.isMember("Orthanc") ||
-          encoded["Orthanc"].type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      const Json::Value& info = encoded["Orthanc"];
-      if (!info.isMember("PixelData") ||
-          !info.isMember("Stretched") ||
-          !info.isMember("Compression") ||
-          info["Compression"].type() != Json::stringValue ||
-          info["PixelData"].type() != Json::stringValue ||
-          info["Stretched"].type() != Json::booleanValue ||
-          info["Compression"].asString() != "Jpeg")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      bool isSigned = false;
-      bool isStretched = info["Stretched"].asBool();
-    
-      if (info.isMember("IsSigned"))
-      {
-        if (info["IsSigned"].type() != Json::booleanValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          isSigned = info["IsSigned"].asBool();
-        }
-      }
-    
-      std::auto_ptr<Orthanc::ImageAccessor> reader;
-    
-      {
-        std::string jpeg;
-        Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
-      
-        reader.reset(new Orthanc::JpegReader);
-        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
-      }
-    
-      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
-      {
-        if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      
-        if (isSigned || isStretched)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          SuccessMessage message(*this, reader.release());
-          emitter.EmitMessage(receiver, message);
-          return;
-        }
-      }
-    
-      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      if (!isStretched)
-      {
-        if (expectedFormat_ != reader->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          SuccessMessage message(*this, reader.release());
-          emitter.EmitMessage(receiver, message);
-          return;
-        }
-      }
-    
-      int32_t stretchLow = 0;
-      int32_t stretchHigh = 0;
-    
-      if (!info.isMember("StretchLow") ||
-          !info.isMember("StretchHigh") ||
-          info["StretchLow"].type() != Json::intValue ||
-          info["StretchHigh"].type() != Json::intValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      stretchLow = info["StretchLow"].asInt();
-      stretchHigh = info["StretchHigh"].asInt();
-    
-      if (stretchLow < -32768 ||
-          stretchHigh > 65535 ||
-          (stretchLow < 0 && stretchHigh > 32767))
-      {
-        // This range cannot be represented with a uint16_t or an int16_t
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    
-      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
-      std::auto_ptr<Orthanc::ImageAccessor> image
-        (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false));
-
-      Orthanc::ImageProcessing::Convert(*image, *reader);
-      reader.reset();
-    
-      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-    
-      if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
-      {
-        float offset = static_cast<float>(stretchLow) / scaling;
-        Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
-      }
-    
-      SuccessMessage message(*this, image.release());
-      emitter.EmitMessage(receiver, message);
-    }
-  };
-
-
-
-
-
-  class DicomInstanceParameters :
-    public Orthanc::IDynamicObject  /* to be used as a payload of SlicesSorter */
-  {
-  private:
-    struct Data   // Struct to ease the copy constructor
-    {
-      std::string                       orthancInstanceId_;
-      std::string                       studyInstanceUid_;
-      std::string                       seriesInstanceUid_;
-      std::string                       sopInstanceUid_;
-      Orthanc::DicomImageInformation    imageInformation_;
-      OrthancStone::SopClassUid         sopClassUid_;
-      double                            thickness_;
-      double                            pixelSpacingX_;
-      double                            pixelSpacingY_;
-      OrthancStone::CoordinateSystem3D  geometry_;
-      OrthancStone::Vector              frameOffsets_;
-      bool                              isColor_;
-      bool                              hasRescale_;
-      double                            rescaleIntercept_;
-      double                            rescaleSlope_;
-      bool                              hasDefaultWindowing_;
-      float                             defaultWindowingCenter_;
-      float                             defaultWindowingWidth_;
-      Orthanc::PixelFormat              expectedPixelFormat_;
-
-      void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
-      {
-        // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
-
-        {
-          std::string increment;
-
-          if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
-          {
-            Orthanc::Toolbox::ToUpperCase(increment);
-            if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
-            {
-              LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
-              return;
-            }
-          }
-        }
-
-        if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
-            frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
-        {
-          LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
-          frameOffsets_.clear();
-        }
-        else
-        {
-          if (frameOffsets_.size() >= 2)
-          {
-            thickness_ = frameOffsets_[1] - frameOffsets_[0];
-
-            if (thickness_ < 0)
-            {
-              thickness_ = -thickness_;
-            }
-          }
-        }
-      }
-
-      Data(const Orthanc::DicomMap& dicom) :
-        imageInformation_(dicom)
-      {
-        if (imageInformation_.GetNumberOfFrames() <= 0)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
-            !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
-            !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        
-        std::string s;
-        if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          sopClassUid_ = OrthancStone::StringToSopClassUid(s);
-        }
-
-        if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
-        {
-          thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
-        }
-
-        OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
-
-        std::string position, orientation;
-        if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
-            dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
-        {
-          geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
-        }
-
-        if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
-        {
-          ComputeDoseOffsets(dicom);
-        }
-
-        isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
-                    imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
-
-        double doseGridScaling;
-
-        if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
-            dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
-        {
-          hasRescale_ = true;
-        }
-        else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
-        {
-          hasRescale_ = true;
-          rescaleIntercept_ = 0;
-          rescaleSlope_ = doseGridScaling;
-        }
-        else
-        {
-          hasRescale_ = false;
-        }
-
-        OrthancStone::Vector c, w;
-        if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-            OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
-            c.size() > 0 && 
-            w.size() > 0)
-        {
-          hasDefaultWindowing_ = true;
-          defaultWindowingCenter_ = static_cast<float>(c[0]);
-          defaultWindowingWidth_ = static_cast<float>(w[0]);
-        }
-        else
-        {
-          hasDefaultWindowing_ = false;
-        }
-
-        if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
-        {
-          switch (imageInformation_.GetBitsStored())
-          {
-            case 16:
-              expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-              break;
-
-            case 32:
-              expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          } 
-        }
-        else if (isColor_)
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
-        }
-        else if (imageInformation_.IsSigned())
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
-        }
-        else
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-        }
-      }
-
-      OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
-      {
-        if (frame == 0)
-        {
-          return geometry_;
-        }
-        else if (frame >= imageInformation_.GetNumberOfFrames())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-        else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
-        {
-          if (frame >= frameOffsets_.size())
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-
-          return OrthancStone::CoordinateSystem3D(
-            geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
-            geometry_.GetAxisX(),
-            geometry_.GetAxisY());
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-      }
-
-      // TODO - Is this necessary?
-      bool FrameContainsPlane(unsigned int frame,
-                              const OrthancStone::CoordinateSystem3D& plane) const
-      {
-        if (frame >= imageInformation_.GetNumberOfFrames())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        OrthancStone::CoordinateSystem3D tmp = geometry_;
-
-        if (frame != 0)
-        {
-          tmp = GetFrameGeometry(frame);
-        }
-
-        double distance;
-
-        return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
-                distance <= thickness_ / 2.0);
-      }
-
-      
-      void ApplyRescale(Orthanc::ImageAccessor& image,
-                        bool useDouble) const
-      {
-        if (image.GetFormat() != Orthanc::PixelFormat_Float32)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-        }
-    
-        if (hasRescale_)
-        {
-          const unsigned int width = image.GetWidth();
-          const unsigned int height = image.GetHeight();
-        
-          for (unsigned int y = 0; y < height; y++)
-          {
-            float* p = reinterpret_cast<float*>(image.GetRow(y));
-
-            if (useDouble)
-            {
-              // Slower, accurate implementation using double
-              for (unsigned int x = 0; x < width; x++, p++)
-              {
-                double value = static_cast<double>(*p);
-                *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
-              }
-            }
-            else
-            {
-              // Fast, approximate implementation using float
-              for (unsigned int x = 0; x < width; x++, p++)
-              {
-                *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
-              }
-            }
-          }
-        }
-      }
-    };
-
-    
-    Data  data_;
 
 
-  public:
-    DicomInstanceParameters(const DicomInstanceParameters& other) :
-      data_(other.data_)
-    {
-    }
-
-    DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
-      data_(dicom)
-    {
-    }
-
-    void SetOrthancInstanceIdentifier(const std::string& id)
-    {
-      data_.orthancInstanceId_ = id;
-    }
-
-    const std::string& GetOrthancInstanceIdentifier() const
-    {
-      return data_.orthancInstanceId_;
-    }
-
-    const Orthanc::DicomImageInformation& GetImageInformation() const
-    {
-      return data_.imageInformation_;
-    }
-
-    const std::string& GetStudyInstanceUid() const
-    {
-      return data_.studyInstanceUid_;
-    }
-
-    const std::string& GetSeriesInstanceUid() const
-    {
-      return data_.seriesInstanceUid_;
-    }
-
-    const std::string& GetSopInstanceUid() const
-    {
-      return data_.sopInstanceUid_;
-    }
-
-    OrthancStone::SopClassUid GetSopClassUid() const
-    {
-      return data_.sopClassUid_;
-    }
-
-    double GetThickness() const
-    {
-      return data_.thickness_;
-    }
-
-    double GetPixelSpacingX() const
-    {
-      return data_.pixelSpacingX_;
-    }
-
-    double GetPixelSpacingY() const
-    {
-      return data_.pixelSpacingY_;
-    }
-
-    const OrthancStone::CoordinateSystem3D&  GetGeometry() const
-    {
-      return data_.geometry_;
-    }
-
-    OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
-    {
-      return data_.GetFrameGeometry(frame);
-    }
-
-    // TODO - Is this necessary?
-    bool FrameContainsPlane(unsigned int frame,
-                            const OrthancStone::CoordinateSystem3D& plane) const
-    {
-      return data_.FrameContainsPlane(frame, plane);
-    }
-
-    bool IsColor() const
-    {
-      return data_.isColor_;
-    }
-
-    bool HasRescale() const
-    {
-      return data_.hasRescale_;
-    }
-
-    double GetRescaleIntercept() const
-    {
-      if (data_.hasRescale_)
-      {
-        return data_.rescaleIntercept_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    double GetRescaleSlope() const
-    {
-      if (data_.hasRescale_)
-      {
-        return data_.rescaleSlope_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    bool HasDefaultWindowing() const
-    {
-      return data_.hasDefaultWindowing_;
-    }
-
-    float GetDefaultWindowingCenter() const
-    {
-      if (data_.hasDefaultWindowing_)
-      {
-        return data_.defaultWindowingCenter_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    float GetDefaultWindowingWidth() const
-    {
-      if (data_.hasDefaultWindowing_)
-      {
-        return data_.defaultWindowingWidth_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    Orthanc::PixelFormat GetExpectedPixelFormat() const
-    {
-      return data_.expectedPixelFormat_;
-    }
-
-
-    OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const
-    {
-      assert(sizeof(float) == 4);
-
-      Orthanc::PixelFormat sourceFormat = source.GetFormat();
-
-      if (sourceFormat != GetExpectedPixelFormat())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      if (sourceFormat == Orthanc::PixelFormat_RGB24)
-      {
-        // This is the case of a color image. No conversion has to be done.
-        return new OrthancStone::ColorTextureSceneLayer(source);
-      }
-      else
-      {
-        if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
-            sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
-            sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-
-        std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture;
-        
-        {
-          // This is the case of a grayscale frame. Convert it to Float32.
-          std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
-                                                                     source.GetWidth(), 
-                                                                     source.GetHeight(),
-                                                                     false));
-          Orthanc::ImageProcessing::Convert(*converted, source);
-
-          // Correct rescale slope/intercept if need be
-          data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
-
-          texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted));
-        }
-
-        if (data_.hasDefaultWindowing_)
-        {
-          texture->SetCustomWindowing(data_.defaultWindowingCenter_,
-                                      data_.defaultWindowingWidth_);
-        }
-        
-        return texture.release();
-      }
-    }
-  };
-
-
-  class DicomVolumeImage : public boost::noncopyable
-  {
-  private:
-    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;
-    std::vector<DicomInstanceParameters*>       slices_;
-    uint64_t                                    revision_;
-    std::vector<uint64_t>                       slicesRevision_;
-    std::vector<unsigned int>                   slicesQuality_;
-
-    void CheckSlice(size_t index,
-                    const DicomInstanceParameters& reference) const
-    {
-      const DicomInstanceParameters& slice = *slices_[index];
-      
-      if (!OrthancStone::GeometryToolbox::IsParallel(
-            reference.GetGeometry().GetNormal(),
-            slice.GetGeometry().GetNormal()))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "A slice in the volume image is not parallel to the others");
-      }
-
-      if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
-                                        "The pixel format changes across the slices of the volume image");
-      }
-
-      if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
-          reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
-                                        "The width/height of slices are not constant in the volume image");
-      }
-
-      if (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
-          !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "The pixel spacing of the slices change across the volume image");
-      }
-    }
-
-    
-    void CheckVolume() const
-    {
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        assert(slices_[i] != NULL);
-        if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                          "This class does not support multi-frame images");
-        }
-      }
-
-      if (slices_.size() != 0)
-      {
-        const DicomInstanceParameters& reference = *slices_[0];
-
-        for (size_t i = 1; i < slices_.size(); i++)
-        {
-          CheckSlice(i, reference);
-        }
-      }
-    }
-
-
-    void Clear()
-    {
-      image_.reset();
-      
-      for (size_t i = 0; i < slices_.size(); i++)
-      {
-        assert(slices_[i] != NULL);
-        delete slices_[i];
-      }
-
-      slices_.clear();
-      slicesRevision_.clear();
-      slicesQuality_.clear();
-    }
-
-
-    void CheckSliceIndex(size_t index) const
-    {
-      assert(slices_.size() == image_->GetDepth() &&
-             slices_.size() == slicesRevision_.size());
-
-      if (!HasGeometry())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else if (index >= slices_.size())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    
-  public:
-    DicomVolumeImage()
-    {
-    }
-
-    ~DicomVolumeImage()
-    {
-      Clear();
-    }
-
-    // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
-    void SetGeometry(OrthancStone::SlicesSorter& slices)
-    {
-      Clear();
-      
-      if (!slices.Sort())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                        "Cannot sort the 3D slices of a DICOM series");          
-      }
-
-      if (slices.GetSlicesCount() == 0)
-      {
-        // Empty volume
-        image_.reset(new OrthancStone::ImageBuffer3D(Orthanc::PixelFormat_Grayscale8, 0, 0, 0,
-                                                     false /* don't compute range */));
-      }
-      else
-      {
-        slices_.reserve(slices.GetSlicesCount());
-        slicesRevision_.resize(slices.GetSlicesCount(), 0);
-        slicesQuality_.resize(slices.GetSlicesCount(), 0);
-
-        for (size_t i = 0; i < slices.GetSlicesCount(); i++)
-        {
-          const DicomInstanceParameters& slice =
-            dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
-          slices_.push_back(new DicomInstanceParameters(slice));
-        }
-
-        CheckVolume();
-
-        const double spacingZ = slices.ComputeSpacingBetweenSlices();
-        LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
-      
-        const DicomInstanceParameters& parameters = *slices_[0];
-
-        image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(),
-                                                     parameters.GetImageInformation().GetWidth(),
-                                                     parameters.GetImageInformation().GetHeight(),
-                                                     slices.GetSlicesCount(), false /* don't compute range */));      
-
-        image_->GetGeometry().SetAxialGeometry(slices.GetSliceGeometry(0));
-        image_->GetGeometry().SetVoxelDimensions(parameters.GetPixelSpacingX(),
-                                                 parameters.GetPixelSpacingY(), spacingZ);
-      }
-      
-      image_->Clear();
-
-      revision_++;
-    }
-
-    uint64_t GetRevision() const
-    {
-      return revision_;
-    }
-
-    bool HasGeometry() const
-    {
-      return (image_.get() != NULL);
-    }
-
-    const OrthancStone::ImageBuffer3D& GetImage() const
-    {
-      if (!HasGeometry())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        return *image_;
-      }
-    }
-
-    size_t GetSlicesCount() const
-    {
-      if (!HasGeometry())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        return slices_.size();
-      }
-    }
-
-    const DicomInstanceParameters& GetSliceParameters(size_t index) const
-    {
-      CheckSliceIndex(index);
-      return *slices_[index];
-    }
-
-    uint64_t GetSliceRevision(size_t index) const
-    {
-      CheckSliceIndex(index);
-      return slicesRevision_[index];
-    }
-
-    void SetSliceContent(size_t index,
-                         const Orthanc::ImageAccessor& image,
-                         unsigned int quality)
-    {
-      CheckSliceIndex(index);
-
-      // If a better image quality is already available, don't update the content
-      if (quality >= slicesQuality_[index])
-      {
-        {
-          OrthancStone::ImageBuffer3D::SliceWriter writer
-            (*image_, OrthancStone::VolumeProjection_Axial, index);
-          Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
-        }
-        
-        revision_ ++;
-        slicesRevision_[index] += 1;
-      }
-    }
-  };
-
-
-
-  class IDicomVolumeSource : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomVolumeSource()
-    {
-    }
-
-    virtual const DicomVolumeImage& GetVolume() const = 0;
-
-    virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0;
-  };
-  
-  
-
-  class VolumeSeriesOrthancLoader :
-    public OrthancStone::IObserver,
-    public IDicomVolumeSource
-  {
-  private:
-    static const unsigned int LOW_QUALITY = 0;
-    static const unsigned int MIDDLE_QUALITY = 1;
-    static const unsigned int BEST_QUALITY = 2;
-    
-    
-    static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
-    {
-      return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
-    }
-
-
-    void ScheduleNextSliceDownload()
-    {
-      assert(strategy_.get() != NULL);
-      
-      unsigned int sliceIndex, quality;
-      
-      if (strategy_->GetNext(sliceIndex, quality))
-      {
-        assert(quality <= BEST_QUALITY);
-
-        const DicomInstanceParameters& slice = volume_.GetSliceParameters(sliceIndex);
-          
-        const std::string& instance = slice.GetOrthancInstanceIdentifier();
-        if (instance.empty())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        std::auto_ptr<Refactoring::OracleCommandWithPayload> command;
-        
-        if (quality == BEST_QUALITY)
-        {
-          std::auto_ptr<Refactoring::GetOrthancImageCommand> tmp(
-            new Refactoring::GetOrthancImageCommand);
-          tmp->SetHttpHeader("Accept-Encoding", "gzip");
-          tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-          tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());          
-          tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-          command.reset(tmp.release());
-        }
-        else
-        {
-          std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> tmp(
-            new Refactoring::GetOrthancWebViewerJpegCommand);
-          tmp->SetHttpHeader("Accept-Encoding", "gzip");
-          tmp->SetInstance(instance);
-          tmp->SetQuality((quality == 0 ? 50 : 90));
-          tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-          command.reset(tmp.release());
-        }
-
-        command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
-        oracle_.Schedule(*this, command.release());
-      }
-    }
-
-
-    void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      Json::Value body;
-      message.ParseJsonBody(body);
-      
-      if (body.type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      {
-        Json::Value::Members instances = body.getMemberNames();
-
-        OrthancStone::SlicesSorter slices;
-        
-        for (size_t i = 0; i < instances.size(); i++)
-        {
-          Orthanc::DicomMap dicom;
-          dicom.FromDicomAsJson(body[instances[i]]);
-
-          std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
-          instance->SetOrthancInstanceIdentifier(instances[i]);
-
-          OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
-          slices.AddSlice(geometry, instance.release());
-        }
-
-        volume_.SetGeometry(slices);
-      }
-
-      if (volume_.GetSlicesCount() != 0)
-      {
-        strategy_.reset(new OrthancStone::BasicFetchingStrategy(
-                          new OrthancStone::BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY));
-
-        for (unsigned int i = 0; i < 4; i++)   // Schedule up to 4 simultaneous downloads (TODO - parameter)
-        {
-          ScheduleNextSliceDownload();
-        }
-      }
-    }
-
-
-    void LoadBestQualitySliceContent(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
-    {      
-      volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()),
-                              message.GetImage(), BEST_QUALITY);
-
-      ScheduleNextSliceDownload();
-    }
-
-
-    void LoadJpegSliceContent(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
-    {
-      unsigned int quality;
-      
-      switch (message.GetOrigin().GetQuality())
-      {
-        case 50:
-          quality = LOW_QUALITY;
-          break;
-
-        case 90:
-          quality = MIDDLE_QUALITY;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      
-      volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
-
-      ScheduleNextSliceDownload();
-    }
-
-
-    IOracle&          oracle_;
-    bool              active_;
-    DicomVolumeImage  volume_;
-    
-    std::auto_ptr<OrthancStone::IFetchingStrategy>   strategy_;
-
-  public:
-    VolumeSeriesOrthancLoader(IOracle& oracle,
-                              OrthancStone::IObservable& oracleObservable) :
-      IObserver(oracleObservable.GetBroker()),
-      oracle_(oracle),
-      active_(false)
-    {
-      oracleObservable.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::LoadGeometry));
-
-      oracleObservable.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent));
-
-      oracleObservable.RegisterObserverCallback(
-        new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent));
-    }
-
-    void LoadSeries(const std::string& seriesId)
-    {
-      if (active_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      active_ = true;
-
-      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
-      command->SetUri("/series/" + seriesId + "/instances-tags");
-
-      oracle_.Schedule(*this, command.release());
-    }
-    
-
-    virtual const DicomVolumeImage& GetVolume() const
-    {
-      return volume_;
-    }
-
-    
-    virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex)
-    {
-      if (strategy_.get() == NULL)
-      {
-        // Should have called GetVolume().HasGeometry() before
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        strategy_->SetCurrent(sliceIndex);
-      }
-    }
-  };
-
-
-
-#if 0
-  void LoadInstance(const std::string& instanceId)
-  {
-    if (active_)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    active_ = true;
-
-    // Tag "3004-000c" is "Grid Frame Offset Vector", which is
-    // mandatory to read RT DOSE, but is too long to be returned by default
-
-    // TODO => Should be part of a second call if needed
-
-    std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
-    command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
-    command->SetPayload(new LoadInstanceGeometryHandler(*this));
-
-    oracle_.Schedule(*this, command.release());
-  }
-#endif
-
-
-  /*  class VolumeSlicerBase : public IVolumeSlicer
-      {
-      private:
-      OrthancStone::Scene2D&            scene_;
-      int                               layerDepth_;
-      bool                              first_;
-      OrthancStone::CoordinateSystem3D  lastPlane_;
-
-      protected:
-      bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const
-      {
-      if (first_ ||
-      !OrthancStone::LinearAlgebra::IsCloseToZero(
-      boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal())))
-      {
-      // This is the first rendering, or the plane has not the same orientation
-      return false;
-      }
-      else
-      {
-      double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin());
-      double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin());
-      return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1);
-      }
-      }
-
-      void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
-      {
-      first_ = false;
-      lastPlane_ = plane;
-      }
-
-      void SetLayer(OrthancStone::ISceneLayer* layer)
-      {
-      scene_.SetLayer(layerDepth_, layer);
-      }
-
-      void DeleteLayer()
-      {
-      scene_.DeleteLayer(layerDepth_);
-      }
-    
-      public:
-      VolumeSlicerBase(OrthancStone::Scene2D& scene,
-      int layerDepth) :
-      scene_(scene),
-      layerDepth_(layerDepth),
-      first_(true)
-      {
-      }
-      };*/
-  
-
-
-  class DicomVolumeMPRSlicer : public IVolumeSlicer
-  {
-  private:
-    bool                            linearInterpolation_;
-    OrthancStone::Scene2D&          scene_;
-    int                             layerDepth_;
-    IDicomVolumeSource&             source_;
-    bool                            first_;
-    OrthancStone::VolumeProjection  lastProjection_;
-    unsigned int                    lastSliceIndex_;
-    uint64_t                        lastSliceRevision_;
-
-  public:
-    DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene,
-                         int layerDepth,
-                         IDicomVolumeSource& source) :
-      linearInterpolation_(false),
-      scene_(scene),
-      layerDepth_(layerDepth),
-      source_(source),
-      first_(true)
-    {
-    }
-
-    void SetLinearInterpolation(bool enabled)
-    {
-      linearInterpolation_ = enabled;
-    }
-
-    bool IsLinearInterpolation() const
-    {
-      return linearInterpolation_;
-    }
-    
-    virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
-    {
-      if (!source_.GetVolume().HasGeometry() ||
-          source_.GetVolume().GetSlicesCount() == 0)
-      {
-        scene_.DeleteLayer(layerDepth_);
-        return;
-      }
-
-      const OrthancStone::VolumeImageGeometry& geometry = source_.GetVolume().GetImage().GetGeometry();
-
-      OrthancStone::VolumeProjection projection;
-      unsigned int sliceIndex;
-      if (!geometry.DetectSlice(projection, sliceIndex, plane))
-      {
-        // The cutting plane is neither axial, nor coronal, nor
-        // sagittal. Could use "VolumeReslicer" here.
-        scene_.DeleteLayer(layerDepth_);
-        return;
-      }
-
-      uint64_t sliceRevision;
-      if (projection == OrthancStone::VolumeProjection_Axial)
-      {
-        sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex);
-
-        if (first_ ||
-            lastSliceIndex_ != sliceIndex)
-        {
-          // Reorder the prefetching queue
-          source_.NotifyAxialSliceAccessed(sliceIndex);
-        }
-      }
-      else
-      {
-        // For coronal and sagittal projections, we take the global
-        // revision of the volume
-        sliceRevision = source_.GetVolume().GetRevision();
-      }
-
-      if (first_ ||
-          lastProjection_ != projection ||
-          lastSliceIndex_ != sliceIndex ||
-          lastSliceRevision_ != sliceRevision)
-      {
-        // Either the viewport plane, or the content of the slice have not
-        // changed since the last time the layer was set: Update is needed
-
-        first_ = false;
-        lastProjection_ = projection;
-        lastSliceIndex_ = sliceIndex;
-        lastSliceRevision_ = sliceRevision;
-
-        std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture;
-        
-        {
-          const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters
-            (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0);
-
-          OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex);
-          texture.reset(parameters.CreateTexture(reader.GetAccessor()));
-        }
-
-        const OrthancStone::CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection);
-
-        double x0, y0, x1, y1;
-        system.ProjectPoint(x0, y0, system.GetOrigin());
-        system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX());
-        texture->SetOrigin(x0, y0);
-
-        double dx = x1 - x0;
-        double dy = y1 - y0;
-        if (!OrthancStone::LinearAlgebra::IsCloseToZero(dx) ||
-            !OrthancStone::LinearAlgebra::IsCloseToZero(dy))
-        {
-          texture->SetAngle(atan2(dy, dx));
-        }
-        
-        OrthancStone::Vector tmp;
-        geometry.GetVoxelDimensions(projection);
-        texture->SetPixelSpacing(tmp[0], tmp[1]);
-
-        texture->SetLinearInterpolation(linearInterpolation_);
-    
-        scene_.SetLayer(layerDepth_, texture.release());    
-      }
-    }
-  };
-  
-  
-
-
-
-  class NativeOracle : public IOracle
-  {
-  private:
-    class Item : public Orthanc::IDynamicObject
-    {
-    private:
-      const OrthancStone::IObserver&  receiver_;
-      std::auto_ptr<IOracleCommand>   command_;
-
-    public:
-      Item(const OrthancStone::IObserver& receiver,
-           IOracleCommand* command) :
-        receiver_(receiver),
-        command_(command)
-      {
-        if (command == NULL)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-
-      const OrthancStone::IObserver& GetReceiver() const
-      {
-        return receiver_;
-      }
-
-      const IOracleCommand& GetCommand() const
-      {
-        assert(command_.get() != NULL);
-        return *command_;
-      }
-    };
-
-
-    enum State
-    {
-      State_Setup,
-      State_Running,
-      State_Stopped
-    };
-
-
-    IMessageEmitter&               emitter_;
-    Orthanc::WebServiceParameters  orthanc_;
-    Orthanc::SharedMessageQueue    queue_;
-    State                          state_;
-    boost::mutex                   mutex_;
-    std::vector<boost::thread*>    workers_;
-
-
-    void CopyHttpHeaders(Orthanc::HttpClient& client,
-                         const HttpHeaders& headers)
-    {
-      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
-      {
-        client.AddHeader(it->first, it->second);
-      }
-    }
-
-
-    void DecodeAnswer(std::string& answer,
-                      const HttpHeaders& headers)
-    {
-      Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
-
-      for (HttpHeaders::const_iterator it = headers.begin(); 
-           it != headers.end(); ++it)
-      {
-        std::string s;
-        Orthanc::Toolbox::ToLowerCase(s, it->first);
-
-        if (s == "content-encoding")
-        {
-          if (it->second == "gzip")
-          {
-            contentEncoding = Orthanc::HttpCompression_Gzip;
-          }
-          else 
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                            "Unsupported HTTP Content-Encoding: " + it->second);
-          }
-
-          break;
-        }
-      }
-
-      if (contentEncoding == Orthanc::HttpCompression_Gzip)
-      {
-        std::string compressed;
-        answer.swap(compressed);
-          
-        Orthanc::GzipCompressor compressor;
-        compressor.Uncompress(answer, compressed.c_str(), compressed.size());
-      }
-    }
-
-
-    void Execute(const OrthancStone::IObserver& receiver,
-                 const OrthancRestApiCommand& command)
-    {
-      Orthanc::HttpClient client(orthanc_, command.GetUri());
-      client.SetMethod(command.GetMethod());
-      client.SetTimeout(command.GetTimeout());
-
-      CopyHttpHeaders(client, command.GetHttpHeaders());
-
-      if (command.GetMethod() == Orthanc::HttpMethod_Post ||
-          command.GetMethod() == Orthanc::HttpMethod_Put)
-      {
-        client.SetBody(command.GetBody());
-      }
-
-      std::string answer;
-      HttpHeaders answerHeaders;
-      client.ApplyAndThrowException(answer, answerHeaders);
-
-      DecodeAnswer(answer, answerHeaders);
-
-      OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
-      emitter_.EmitMessage(receiver, message);
-    }
-
-
-    void Execute(const OrthancStone::IObserver& receiver,
-                 const GetOrthancImageCommand& command)
-    {
-      Orthanc::HttpClient client(orthanc_, command.GetUri());
-      client.SetTimeout(command.GetTimeout());
-
-      CopyHttpHeaders(client, command.GetHttpHeaders());
-
-      std::string answer;
-      HttpHeaders answerHeaders;
-      client.ApplyAndThrowException(answer, answerHeaders);
-
-      DecodeAnswer(answer, answerHeaders);
-
-      command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders);
-    }
-
-
-    void Execute(const OrthancStone::IObserver& receiver,
-                 const GetOrthancWebViewerJpegCommand& command)
-    {
-      Orthanc::HttpClient client(orthanc_, command.GetUri());
-      client.SetTimeout(command.GetTimeout());
-
-      CopyHttpHeaders(client, command.GetHttpHeaders());
-
-      std::string answer;
-      HttpHeaders answerHeaders;
-      client.ApplyAndThrowException(answer, answerHeaders);
-
-      DecodeAnswer(answer, answerHeaders);
-
-      command.ProcessHttpAnswer(emitter_, receiver, answer);
-    }
-
-
-    void Step()
-    {
-      std::auto_ptr<Orthanc::IDynamicObject>  object(queue_.Dequeue(100));
-
-      if (object.get() != NULL)
-      {
-        const Item& item = dynamic_cast<Item&>(*object);
-
-        try
-        {
-          switch (item.GetCommand().GetType())
-          {
-            case IOracleCommand::Type_OrthancRestApi:
-              Execute(item.GetReceiver(), 
-                      dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
-              break;
-
-            case IOracleCommand::Type_GetOrthancImage:
-              Execute(item.GetReceiver(), 
-                      dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
-              break;
-
-            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-              Execute(item.GetReceiver(), 
-                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          }
-        }
-        catch (Orthanc::OrthancException& e)
-        {
-          LOG(ERROR) << "Exception within the oracle: " << e.What();
-          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
-        }
-        catch (...)
-        {
-          LOG(ERROR) << "Native exception within the oracle";
-          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
-                               (item.GetCommand(), Orthanc::ErrorCode_InternalError));
-        }
-      }
-    }
-
-
-    static void Worker(NativeOracle* that)
-    {
-      assert(that != NULL);
-      
-      for (;;)
-      {
-        {
-          boost::mutex::scoped_lock lock(that->mutex_);
-          if (that->state_ != State_Running)
-          {
-            return;
-          }
-        }
-
-        that->Step();
-      }
-    }
-
-
-    void StopInternal()
-    {
-      {
-        boost::mutex::scoped_lock lock(mutex_);
-
-        if (state_ == State_Setup ||
-            state_ == State_Stopped)
-        {
-          return;
-        }
-        else
-        {
-          state_ = State_Stopped;
-        }
-      }
-
-      for (size_t i = 0; i < workers_.size(); i++)
-      {
-        if (workers_[i] != NULL)
-        {
-          if (workers_[i]->joinable())
-          {
-            workers_[i]->join();
-          }
-
-          delete workers_[i];
-        }
-      } 
-    }
-
-
-  public:
-    NativeOracle(IMessageEmitter& emitter) :
-      emitter_(emitter),
-      state_(State_Setup),
-      workers_(4)
-    {
-    }
-
-    virtual ~NativeOracle()
-    {
-      StopInternal();
-    }
-
-    void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (state_ != State_Setup)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        orthanc_ = orthanc;
-      }
-    }
-
-    void SetWorkersCount(unsigned int count)
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (count <= 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      else if (state_ != State_Setup)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        workers_.resize(count);
-      }
-    }
-
-    void Start()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (state_ != State_Setup)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        state_ = State_Running;
-
-        for (unsigned int i = 0; i < workers_.size(); i++)
-        {
-          workers_[i] = new boost::thread(Worker, this);
-        }
-      }      
-    }
-
-    void Stop()
-    {
-      StopInternal();
-    }
-
-    virtual void Schedule(const OrthancStone::IObserver& receiver,
-                          IOracleCommand* command)
-    {
-      queue_.Enqueue(new Item(receiver, command));
-    }
-  };
-
-
+namespace OrthancStone
+{
   class NativeApplicationContext : public IMessageEmitter
   {
   private:
-    boost::shared_mutex            mutex_;
-    OrthancStone::MessageBroker    broker_;
-    OrthancStone::IObservable      oracleObservable_;
+    boost::shared_mutex  mutex_;
+    MessageBroker        broker_;
+    IObservable          oracleObservable_;
 
   public:
     NativeApplicationContext() :
@@ -2265,8 +56,8 @@
     }
 
 
-    virtual void EmitMessage(const OrthancStone::IObserver& observer,
-                             const OrthancStone::IMessage& message)
+    virtual void EmitMessage(const IObserver& observer,
+                             const IMessage& message) ORTHANC_OVERRIDE
     {
       try
       {
@@ -2308,12 +99,12 @@
       {
       }
 
-      OrthancStone::MessageBroker& GetBroker() 
+      MessageBroker& GetBroker() 
       {
         return that_.broker_;
       }
 
-      OrthancStone::IObservable& GetOracleObservable()
+      IObservable& GetOracleObservable()
       {
         return that_.oracleObservable_;
       }
@@ -2326,7 +117,88 @@
 class Toto : public OrthancStone::IObserver
 {
 private:
-  void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message)
+  OrthancStone::CoordinateSystem3D  plane_;
+  OrthancStone::IOracle&            oracle_;
+  OrthancStone::Scene2D             scene_;
+  std::auto_ptr<OrthancStone::VolumeSceneLayerSource>  source1_, source2_, source3_;
+
+
+  void Refresh()
+  {
+    if (source1_.get() != NULL)
+    {
+      source1_->Update(plane_);
+    }
+      
+    if (source2_.get() != NULL)
+    {
+      source2_->Update(plane_);
+    }
+
+    if (source3_.get() != NULL)
+    {
+      source3_->Update(plane_);
+    }
+
+    scene_.FitContent(1024, 768);
+      
+    {
+      OrthancStone::CairoCompositor compositor(scene_, 1024, 768);
+      compositor.Refresh();
+        
+      Orthanc::ImageAccessor accessor;
+      compositor.GetCanvas().GetReadOnlyAccessor(accessor);
+
+      Orthanc::Image tmp(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false);
+      Orthanc::ImageProcessing::Convert(tmp, accessor);
+        
+      static unsigned int count = 0;
+      char buf[64];
+      sprintf(buf, "scene-%06d.png", count++);
+        
+      Orthanc::PngWriter writer;
+      writer.WriteToFile(buf, tmp);
+    }
+  }
+
+  
+  void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message)
+  {
+    printf("Geometry ready\n");
+    
+    plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry();
+    //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry();
+    //plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry();
+    plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f));
+
+    Refresh();
+  }
+  
+  
+  void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message)
+  {
+    if (message.GetOrigin().HasPayload())
+    {
+      printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue());
+    }
+    else
+    {
+      printf("TIMEOUT\n");
+
+      Refresh();
+
+      /**
+       * The sleep() leads to a crash if the oracle is still running,
+       * while this object is destroyed. Always stop the oracle before
+       * destroying active objects.  (*)
+       **/
+      // boost::this_thread::sleep(boost::posix_time::seconds(2));
+
+      oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay()));
+    }
+  }
+
+  void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
   {
     Json::Value v;
     message.ParseJsonBody(v);
@@ -2334,24 +206,24 @@
     printf("ICI [%s]\n", v.toStyledString().c_str());
   }
 
-  void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
+  void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
   {
     printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
   }
 
-  void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
   {
     printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
   }
 
-  void Handle(const Refactoring::OracleCommandExceptionMessage& message)
+  void Handle(const OrthancStone::OracleCommandExceptionMessage& message)
   {
     printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());
 
     switch (message.GetCommand().GetType())
     {
-      case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg:
-        printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&>
+      case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg:
+        printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>
                (message.GetCommand()).GetUri().c_str());
         break;
       
@@ -2361,48 +233,133 @@
   }
 
 public:
-  Toto(OrthancStone::IObservable& oracle) :
-    IObserver(oracle.GetBroker())
+  Toto(OrthancStone::IOracle& oracle,
+       OrthancStone::IObservable& oracleObservable) :
+    IObserver(oracleObservable.GetBroker()),
+    oracle_(oracle)
   {
-    oracle.RegisterObserverCallback
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle));
+
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracleObservable.RegisterObserverCallback
       (new OrthancStone::Callable
-       <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
 
-    oracle.RegisterObserverCallback
+    oracleObservable.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle));
+  }
+
+  void SetReferenceLoader(OrthancStone::IObservable& loader)
+  {
+    loader.RegisterObserverCallback
       (new OrthancStone::Callable
-       <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle));
+  }
+
+  void SetVolume1(int depth,
+                  const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+                  OrthancStone::ILayerStyleConfigurator* style)
+  {
+    source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
+
+    if (style != NULL)
+    {
+      source1_->SetConfigurator(style);
+    }
+  }
 
-    oracle.RegisterObserverCallback
-      (new OrthancStone::Callable
-       <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
+  void SetVolume2(int depth,
+                  const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
+                  OrthancStone::ILayerStyleConfigurator* style)
+  {
+    source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
 
-    oracle.RegisterObserverCallback
-      (new OrthancStone::Callable
-       <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle));
+    if (style != NULL)
+    {
+      source2_->SetConfigurator(style);
+    }
   }
+
+  void SetStructureSet(int depth,
+                       const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
+  {
+    source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
+  }
+                       
 };
 
 
-void Run(Refactoring::NativeApplicationContext& context,
-         Refactoring::IOracle& oracle)
+void Run(OrthancStone::NativeApplicationContext& context,
+         OrthancStone::ThreadedOracle& oracle)
 {
-  std::auto_ptr<Toto> toto;
-  std::auto_ptr<Refactoring::VolumeSeriesOrthancLoader> loader1, loader2;
+  // the oracle has been supplied with the context (as an IEmitter) upon
+  // creation
+  boost::shared_ptr<OrthancStone::DicomVolumeImage>  ct(new OrthancStone::DicomVolumeImage);
+  boost::shared_ptr<OrthancStone::DicomVolumeImage>  dose(new OrthancStone::DicomVolumeImage);
+
+  
+  boost::shared_ptr<Toto> toto;
+  boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader;
+  boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader;
+  boost::shared_ptr<OrthancStone::DicomStructureSetLoader>  rtstructLoader;
 
   {
-    Refactoring::NativeApplicationContext::WriterLock lock(context);
-    toto.reset(new Toto(lock.GetOracleObservable()));
-    loader1.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable()));
-    loader2.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable()));
+    OrthancStone::NativeApplicationContext::WriterLock lock(context);
+    toto.reset(new Toto(oracle, lock.GetOracleObservable()));
+
+    // the oracle is used to schedule commands
+    // the oracleObservable is used by the loaders to:
+    // - request the broker (lifetime mgmt)
+    // - register the loader callbacks (called indirectly by the oracle)
+    ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable()));
+    doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable()));
+    rtstructLoader.reset(new OrthancStone::DicomStructureSetLoader(oracle, lock.GetOracleObservable()));
   }
 
-  if (1)
+
+  //toto->SetReferenceLoader(*ctLoader);
+  toto->SetReferenceLoader(*doseLoader);
+
+
+#if 1
+  toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator);
+#else
+  {
+    boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::DicomVolumeImageReslicer(ct));
+    toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator);
+  }
+#endif  
+  
+
+  {
+    std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator);
+    config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
+
+    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose));
+    toto->SetVolume2(1, tmp, config.release());
+  }
+
+  toto->SetStructureSet(2, rtstructLoader);
+  
+  oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100));
+
+  if (0)
   {
     Json::Value v = Json::objectValue;
     v["Level"] = "Series";
     v["Query"] = Json::objectValue;
 
-    std::auto_ptr<Refactoring::OrthancRestApiCommand>  command(new Refactoring::OrthancRestApiCommand);
+    std::auto_ptr<OrthancStone::OrthancRestApiCommand>  command(new OrthancStone::OrthancRestApiCommand);
     command->SetMethod(Orthanc::HttpMethod_Post);
     command->SetUri("/tools/find");
     command->SetBody(v);
@@ -2410,67 +367,112 @@
     oracle.Schedule(*toto, command.release());
   }
   
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
-    oracle.Schedule(*toto, command.release());
-  }
-  
-  if (1)
+  if(0)
   {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
-    oracle.Schedule(*toto, command.release());
-  }
-  
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
-    oracle.Schedule(*toto, command.release());
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+      oracle.Schedule(*toto, command.release());
+    }
+    
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
+      command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+      command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+      oracle.Schedule(*toto, command.release());
+    }
+
+    if (0)
+    {
+      std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand>  command(new OrthancStone::GetOrthancWebViewerJpegCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
+      command->SetQuality(90);
+      oracle.Schedule(*toto, command.release());
+    }
+
+
+    if (0)
+    {
+      for (unsigned int i = 0; i < 10; i++)
+      {
+        std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000));
+        command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i));
+        oracle.Schedule(*toto, command.release());
+      }
+    }
   }
   
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept-Encoding", "gzip");
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
-    oracle.Schedule(*toto, command.release());
-  }
+  // 2017-11-17-Anonymized
+#if 0
+  // BGO data
+  ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
+  doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb");  // RT-DOSE
+  //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
+#else
+  //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
+  //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+  //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6");  // RT-STRUCT
+
+  // 2017-05-16
+  ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
+  doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad");  // RT-DOSE
+  rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
+#endif
+  // 2015-01-28-Multiframe
+  //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279");  // Multiframe CT
   
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
-    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
-    oracle.Schedule(*toto, command.release());
-  }
-
-  if (1)
-  {
-    std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand>  command(new Refactoring::GetOrthancWebViewerJpegCommand);
-    command->SetHttpHeader("Accept-Encoding", "gzip");
-    command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
-    command->SetQuality(90);
-    oracle.Schedule(*toto, command.release());
-  }
+  // Delphine
+  //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e");  // CT
+  //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");  // Lung 1/10mm
 
 
-  // 2017-11-17-Anonymized
-  //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
-  //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+  {
+    LOG(WARNING) << "...Waiting for Ctrl-C...";
+
+    oracle.Start();
+
+    Orthanc::SystemToolbox::ServerBarrier();
 
-  // Delphine
-  loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e");  // CT
+    /**
+     * WARNING => The oracle must be stopped BEFORE the objects using
+     * it are destroyed!!! This forces to wait for the completion of
+     * the running callback methods. Otherwise, the callbacks methods
+     * might still be running while their parent object is destroyed,
+     * resulting in crashes. This is very visible if adding a sleep(),
+     * as in (*).
+     **/
 
-  LOG(WARNING) << "...Waiting for Ctrl-C...";
-  Orthanc::SystemToolbox::ServerBarrier();
-  //boost::this_thread::sleep(boost::posix_time::seconds(1));
+    oracle.Stop();
+  }
 }
 
 
@@ -2483,13 +485,14 @@
 int main(int argc, char* argv[])
 {
   OrthancStone::StoneInitialize();
-  Orthanc::Logging::EnableInfoLevel(true);
+  //Orthanc::Logging::EnableInfoLevel(true);
 
   try
   {
-    Refactoring::NativeApplicationContext context;
+    OrthancStone::NativeApplicationContext context;
 
-    Refactoring::NativeOracle oracle(context);
+    OrthancStone::ThreadedOracle oracle(context);
+    //oracle.SetThreadsCount(1);
 
     {
       Orthanc::WebServiceParameters p;
@@ -2498,11 +501,11 @@
       oracle.SetOrthancParameters(p);
     }
 
-    oracle.Start();
+    //oracle.Start();
 
     Run(context, oracle);
-
-    oracle.Stop();
+    
+    //oracle.Stop();
   }
   catch (Orthanc::OrthancException& e)
   {