changeset 801:7efcbae4717e

Added HttpClient init to Stone init
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 28 May 2019 14:15:03 +0200
parents 98a89b116b62 (diff) 20262f5e5e88 (current diff)
children f38c1fc08655
files
diffstat 20 files changed, 1151 insertions(+), 845 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Applications/Generic/NativeStoneApplicationRunner.cpp	Tue May 28 14:15:03 2019 +0200
@@ -25,7 +25,7 @@
 
 #include "NativeStoneApplicationRunner.h"
 
-#include "../../Framework/Toolbox/MessagingToolbox.h"
+#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h"
 #include "../../Platforms/Generic/OracleWebService.h"
 #include "../../Platforms/Generic/OracleDelayedCallExecutor.h"
 #include "NativeStoneApplicationContext.h"
@@ -180,7 +180,7 @@
 
       {
         OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters);
-        if (!MessagingToolbox::CheckOrthancVersion(orthanc))
+        if (!Deprecated::MessagingToolbox::CheckOrthancVersion(orthanc))
         {
           LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of "
             << "Orthanc, please upgrade";
--- a/Applications/Sdl/SdlStoneApplicationRunner.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp	Tue May 28 14:15:03 2019 +0200
@@ -25,7 +25,6 @@
 
 #include "SdlStoneApplicationRunner.h"
 
-#include "../../Framework/Toolbox/MessagingToolbox.h"
 #include "../../Platforms/Generic/OracleWebService.h"
 #include "SdlEngine.h"
 
--- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp	Tue May 28 14:15:03 2019 +0200
@@ -80,7 +80,7 @@
              const OrthancStone::CoordinateSystem3D& plane) :
       plane_(plane)
     {
-      for (size_t k = 0; k < structureSet.GetStructureCount(); k++)
+      for (size_t k = 0; k < structureSet.GetStructuresCount(); k++)
       {
         structures_.push_back(new Structure(structureSet, plane, k));
       }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/MessagingToolbox.cpp	Tue May 28 14:15:03 2019 +0200
@@ -0,0 +1,456 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MessagingToolbox.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PngReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+#include <Core/Logging.h>
+
+#include <boost/lexical_cast.hpp>
+#include <json/reader.h>
+#include <json/writer.h>
+
+namespace Deprecated
+{
+  namespace MessagingToolbox
+  {
+    static bool ParseVersion(std::string& version,
+                             unsigned int& major,
+                             unsigned int& minor,
+                             unsigned int& patch,
+                             const Json::Value& info)
+    {
+      if (info.type() != Json::objectValue ||
+          !info.isMember("Version") ||
+          info["Version"].type() != Json::stringValue)
+      {
+        return false;
+      }
+
+      version = info["Version"].asString();
+      if (version == "mainline")
+      {
+        // Some arbitrary high values Orthanc versions will never reach ;)
+        major = 999;
+        minor = 999;
+        patch = 999;
+        return true;
+      }
+
+      std::vector<std::string> tokens;
+      Orthanc::Toolbox::TokenizeString(tokens, version, '.');
+      
+      if (tokens.size() != 2 &&
+          tokens.size() != 3)
+      {
+        return false;
+      }
+
+      int a, b, c;
+      try
+      {
+        a = boost::lexical_cast<int>(tokens[0]);
+        b = boost::lexical_cast<int>(tokens[1]);
+
+        if (tokens.size() == 3)
+        {
+          c = boost::lexical_cast<int>(tokens[2]);
+        }
+        else
+        {
+          c = 0;
+        }
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return false;
+      }
+
+      if (a < 0 ||
+          b < 0 ||
+          c < 0)
+      {
+        return false;
+      }
+      else
+      {
+        major = static_cast<unsigned int>(a);
+        minor = static_cast<unsigned int>(b);
+        patch = static_cast<unsigned int>(c);
+        return true;
+      }         
+    }
+
+
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size)
+    {
+      Json::Reader reader;
+      return reader.parse(reinterpret_cast<const char*>(content),
+                          reinterpret_cast<const char*>(content) + size,
+                          target);
+    }
+
+    void JsonToString(std::string& target,
+                      const Json::Value& source)
+    {
+      Json::FastWriter writer;
+      target = writer.write(source);
+    }
+
+    static void ParseJsonException(Json::Value& target,
+                                   const std::string& source)
+    {
+      Json::Reader reader;
+      if (!reader.parse(source, target))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+
+
+    void RestApiGet(Json::Value& target,
+                    OrthancPlugins::IOrthancConnection& orthanc,
+                    const std::string& uri)
+    {
+      std::string tmp;
+      orthanc.RestApiGet(tmp, uri);
+      ParseJsonException(target, tmp);
+    }
+
+
+    void RestApiPost(Json::Value& target,
+                     OrthancPlugins::IOrthancConnection& orthanc,
+                     const std::string& uri,
+                     const std::string& body)
+    {
+      std::string tmp;
+      orthanc.RestApiPost(tmp, uri, body);
+      ParseJsonException(target, tmp);
+    }
+
+
+    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc)
+    {
+      try
+      {
+        Json::Value json;
+        RestApiGet(json, orthanc, "/plugins/web-viewer");
+        return json.type() == Json::objectValue;
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        return false;
+      }
+    }
+
+
+    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc)
+    {
+      Json::Value json;
+      std::string version;
+      unsigned int major, minor, patch;
+
+      try
+      {
+        RestApiGet(json, orthanc, "/system");
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(ERROR) << "Cannot connect to your Orthanc server";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+        
+      if (!ParseVersion(version, major, minor, patch, json))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version;
+
+      // Stone is only compatible with Orthanc >= 1.3.1
+      if (major < 1 ||
+          (major == 1 && minor < 3) ||
+          (major == 1 && minor == 3 && patch < 1))
+      {
+        return false;
+      }
+
+      try
+      {
+        RestApiGet(json, orthanc, "/plugins/web-viewer");       
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        // The Web viewer is not installed, this is OK
+        LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled";
+        return true;
+      }
+
+      if (!ParseVersion(version, major, minor, patch, json))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version;
+
+      return (major >= 3 ||
+              (major == 2 && minor >= 2));
+    }
+
+
+    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                        const std::string& instance,
+                                        unsigned int frame,
+                                        Orthanc::PixelFormat targetFormat)
+    {
+      std::string uri = ("instances/" + instance + "/frames/" + 
+                         boost::lexical_cast<std::string>(frame));
+
+      std::string compressed;
+
+      switch (targetFormat)
+      {
+        case Orthanc::PixelFormat_RGB24:
+          orthanc.RestApiGet(compressed, uri + "/preview");
+          break;
+
+        case Orthanc::PixelFormat_Grayscale16:
+          orthanc.RestApiGet(compressed, uri + "/image-uint16");
+          break;
+
+        case Orthanc::PixelFormat_SignedGrayscale16:
+          orthanc.RestApiGet(compressed, uri + "/image-int16");
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader);
+      result->ReadFromMemory(compressed);
+
+      if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16)
+      {
+        if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+        {
+          result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+      }
+
+      return result.release();
+    }
+
+
+    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                            const std::string& instance,
+                                            unsigned int frame,
+                                            unsigned int quality,
+                                            Orthanc::PixelFormat targetFormat)
+    {
+      if (quality <= 0 || 
+          quality > 100)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      // This requires the official Web viewer plugin to be installed!
+      std::string uri = ("web-viewer/instances/jpeg" + 
+                         boost::lexical_cast<std::string>(quality) + 
+                         "-" + instance + "_" + 
+                         boost::lexical_cast<std::string>(frame));
+
+      Json::Value encoded;
+      RestApiGet(encoded, orthanc, uri);
+
+      if (encoded.type() != Json::objectValue ||
+          !encoded.isMember("Orthanc") ||
+          encoded["Orthanc"].type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      Json::Value& info = encoded["Orthanc"];
+      if (!info.isMember("PixelData") ||
+          !info.isMember("Stretched") ||
+          !info.isMember("Compression") ||
+          info["Compression"].type() != Json::stringValue ||
+          info["PixelData"].type() != Json::stringValue ||
+          info["Stretched"].type() != Json::booleanValue ||
+          info["Compression"].asString() != "Jpeg")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }          
+
+      bool isSigned = false;
+      bool isStretched = info["Stretched"].asBool();
+
+      if (info.isMember("IsSigned"))
+      {
+        if (info["IsSigned"].type() != Json::booleanValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }          
+        else
+        {
+          isSigned = info["IsSigned"].asBool();
+        }
+      }
+
+      std::string jpeg;
+      Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+
+      std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
+      reader->ReadFromMemory(jpeg);
+
+      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+      {
+        if (targetFormat != Orthanc::PixelFormat_RGB24)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+
+        if (isSigned || isStretched)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+        else
+        {
+          return reader.release();
+        }
+      }
+
+      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      if (!isStretched)
+      {
+        if (targetFormat != reader->GetFormat())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+        }
+
+        return reader.release();
+      }
+
+      int32_t stretchLow = 0;
+      int32_t stretchHigh = 0;
+
+      if (!info.isMember("StretchLow") ||
+          !info.isMember("StretchHigh") ||
+          info["StretchLow"].type() != Json::intValue ||
+          info["StretchHigh"].type() != Json::intValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      stretchLow = info["StretchLow"].asInt();
+      stretchHigh = info["StretchHigh"].asInt();
+
+      if (stretchLow < -32768 ||
+          stretchHigh > 65535 ||
+          (stretchLow < 0 && stretchHigh > 32767))
+      {
+        // This range cannot be represented with a uint16_t or an int16_t
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
+      }
+
+      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+      std::auto_ptr<Orthanc::ImageAccessor> image
+        (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false));
+
+      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+      float offset = static_cast<float>(stretchLow) / scaling;
+      
+      Orthanc::ImageProcessing::Convert(*image, *reader);
+      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+
+#if 0
+      /*info.removeMember("PixelData");
+        std::cout << info.toStyledString();*/
+      
+      int64_t a, b;
+      Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image);
+      std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl;
+#endif
+
+      return image.release();
+    }
+
+
+    static void AddTag(Orthanc::DicomMap& target,
+                       const OrthancPlugins::IDicomDataset& source,
+                       const Orthanc::DicomTag& tag)
+    {
+      OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement());
+      
+      std::string value;
+      if (source.GetStringValue(value, key))
+      {
+        target.SetValue(tag, value, false);
+      }
+    }
+
+    
+    void ConvertDataset(Orthanc::DicomMap& target,
+                        const OrthancPlugins::IDicomDataset& source)
+    {
+      target.Clear();
+
+      AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED);
+      AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED);
+      AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS);
+      AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING);
+      AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER);
+      AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR);
+      AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT);
+      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT);
+      AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES);
+      AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION);
+      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION);
+      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING);
+      AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION);
+      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
+      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE);
+      AddTag(target, source, Orthanc::DICOM_TAG_ROWS);
+      AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL);
+      AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
+      AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS);
+      AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID);
+      AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID);
+      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER);
+      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Toolbox/MessagingToolbox.h	Tue May 28 14:15:03 2019 +0200
@@ -0,0 +1,75 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../StoneEnumerations.h"
+
+#include <Core/DicomFormat/DicomMap.h>
+#include <Core/Images/ImageAccessor.h>
+#include <Plugins/Samples/Common/IDicomDataset.h>
+#include <Plugins/Samples/Common/IOrthancConnection.h>
+
+#include <json/value.h>
+
+namespace Deprecated
+{
+  namespace MessagingToolbox
+  {
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size);
+
+    void JsonToString(std::string& target,
+                      const Json::Value& source);
+
+
+    void RestApiGet(Json::Value& target,
+                    OrthancPlugins::IOrthancConnection& orthanc,
+                    const std::string& uri);
+
+    void RestApiPost(Json::Value& target,
+                     OrthancPlugins::IOrthancConnection& orthanc,
+                     const std::string& uri,
+                     const std::string& body);
+
+    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc);
+
+    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc);
+
+    // This downloads the image from Orthanc and keeps its pixel
+    // format unchanged (will be either Grayscale8, Grayscale16,
+    // SignedGrayscale16, or RGB24)
+    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                        const std::string& instance,
+                                        unsigned int frame,
+                                        Orthanc::PixelFormat targetFormat);
+
+    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
+                                            const std::string& instance,
+                                            unsigned int frame,
+                                            unsigned int quality,
+                                            Orthanc::PixelFormat targetFormat);
+
+    void ConvertDataset(Orthanc::DicomMap& target,
+                        const OrthancPlugins::IDicomDataset& source);
+  }
+}
--- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp	Tue May 28 14:15:03 2019 +0200
@@ -20,7 +20,7 @@
 
 #include "OrthancApiClient.h"
 
-#include "../../Toolbox/MessagingToolbox.h"
+#include "../Toolbox/MessagingToolbox.h"
 
 #include <Core/OrthancException.h>
 
@@ -139,7 +139,7 @@
       else if (jsonHandler_.get() != NULL)
       {
         Json::Value response;
-        if (OrthancStone::MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize()))
+        if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize()))
         {
           jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage
                               (message.GetUri(), response, userPayload_.get()));
@@ -270,7 +270,7 @@
       Orthanc::IDynamicObject* payload)
   {
     std::string body;
-    OrthancStone::MessagingToolbox::JsonToString(body, data);
+    MessagingToolbox::JsonToString(body, data);
     return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload);
   }
 
@@ -279,7 +279,7 @@
       const Json::Value& data)
   {
     std::string body;
-    OrthancStone::MessagingToolbox::JsonToString(body, data);
+    MessagingToolbox::JsonToString(body, data);
     return PostBinaryAsync(uri, body);
   }
 
@@ -291,7 +291,7 @@
       Orthanc::IDynamicObject* payload   /* takes ownership */)
   {
     std::string body;
-    OrthancStone::MessagingToolbox::JsonToString(body, data);
+    MessagingToolbox::JsonToString(body, data);
     return PostBinaryAsync(uri, body, successCallback, failureCallback, payload);
   }
 
--- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp	Tue May 28 14:15:03 2019 +0200
@@ -21,7 +21,7 @@
 
 #include "OrthancSlicesLoader.h"
 
-#include "../../Toolbox/MessagingToolbox.h"
+#include "../Toolbox/MessagingToolbox.h"
 
 #include <Core/Compression/GzipCompressor.h>
 #include <Core/Endianness.h>
@@ -231,7 +231,7 @@
       OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
       
       Orthanc::DicomMap dicom;
-      OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
+      MessagingToolbox::ConvertDataset(dicom, dataset);
       
       unsigned int frames;
       if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
@@ -265,7 +265,7 @@
     OrthancPlugins::FullOrthancDataset dataset(tags);
     
     Orthanc::DicomMap dicom;
-    OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
+    MessagingToolbox::ConvertDataset(dicom, dataset);
 
     unsigned int frames;
     if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
@@ -306,7 +306,7 @@
     state_ = State_GeometryReady;
     
     Orthanc::DicomMap dicom;
-    OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
+    MessagingToolbox::ConvertDataset(dicom, dataset);
     
     std::auto_ptr<Slice> slice(new Slice);
     if (slice->ParseOrthancFrame(dicom, instanceId, frame))
--- a/Framework/Deprecated/Volumes/StructureSetLoader.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp	Tue May 28 14:15:03 2019 +0200
@@ -21,7 +21,7 @@
 
 #include "StructureSetLoader.h"
 
-#include "../../Toolbox/MessagingToolbox.h"
+#include "../Toolbox/MessagingToolbox.h"
 
 #include <Core/OrthancException.h>
 
@@ -41,7 +41,7 @@
     OrthancPlugins::FullOrthancDataset dataset(message.GetJson());
 
     Orthanc::DicomMap slice;
-    OrthancStone::MessagingToolbox::ConvertDataset(slice, dataset);
+    MessagingToolbox::ConvertDataset(slice, dataset);
     structureSet_->AddReferencedSlice(slice);
 
     BroadcastMessage(ContentChangedMessage(*this));
@@ -113,4 +113,47 @@
       return *structureSet_;
     }
   }
+
+
+  OrthancStone::DicomStructureSet* StructureSetLoader::SynchronousLoad(
+    OrthancPlugins::IOrthancConnection& orthanc,
+    const std::string& instanceId)
+  {
+    const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
+    OrthancPlugins::FullOrthancDataset dataset(orthanc, uri);
+
+    std::auto_ptr<OrthancStone::DicomStructureSet> result
+      (new OrthancStone::DicomStructureSet(dataset));
+
+    std::set<std::string> instances;
+    result->GetReferencedInstances(instances);
+
+    for (std::set<std::string>::const_iterator it = instances.begin();
+         it != instances.end(); ++it)
+    {
+      Json::Value lookup;
+      MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it);
+
+      if (lookup.type() != Json::arrayValue ||
+          lookup.size() != 1 ||
+          !lookup[0].isMember("Type") ||
+          !lookup[0].isMember("Path") ||
+          lookup[0]["Type"].type() != Json::stringValue ||
+          lookup[0]["ID"].type() != Json::stringValue ||
+          lookup[0]["Type"].asString() != "Instance")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
+      }
+
+      OrthancPlugins::FullOrthancDataset slice
+        (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags");
+      Orthanc::DicomMap m;
+      MessagingToolbox::ConvertDataset(m, slice);
+      result->AddReferencedSlice(m);
+    }
+
+    result->CheckReferencedSlices();
+
+    return result.release();
+  }
 }
--- a/Framework/Deprecated/Volumes/StructureSetLoader.h	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Deprecated/Volumes/StructureSetLoader.h	Tue May 28 14:15:03 2019 +0200
@@ -53,5 +53,9 @@
     }
 
     OrthancStone::DicomStructureSet& GetStructureSet();
+
+    static OrthancStone::DicomStructureSet* SynchronousLoad(
+      OrthancPlugins::IOrthancConnection& orthanc,
+      const std::string& instanceId);
   };
 }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp	Tue May 28 14:15:03 2019 +0200
@@ -63,9 +63,9 @@
 
     for (size_t i = 0; i < 256; i++)
     {
-      rgb[3 * i] = i;
-      rgb[3 * i + 1] = i;
-      rgb[3 * i + 2] = i;
+      rgb[3 * i]     = static_cast<uint8_t>(i);
+      rgb[3 * i + 1] = static_cast<uint8_t>(i);
+      rgb[3 * i + 2] = static_cast<uint8_t>(i);
     }
 
     SetLookupTableRgb(rgb);
--- a/Framework/Toolbox/DicomStructureSet.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet.cpp	Tue May 28 14:15:03 2019 +0200
@@ -22,7 +22,6 @@
 #include "DicomStructureSet.h"
 
 #include "../Toolbox/GeometryToolbox.h"
-#include "../Toolbox/MessagingToolbox.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -31,7 +30,6 @@
 
 #include <limits>
 #include <stdio.h>
-#include <boost/lexical_cast.hpp>
 #include <boost/geometry.hpp>
 #include <boost/geometry/geometries/point_xy.hpp>
 #include <boost/geometry/geometries/polygon.hpp>
@@ -422,7 +420,6 @@
                    << static_cast<int>(structures_[i].green_) << ","
                    << static_cast<int>(structures_[i].blue_) << ")";
 
-
       // These temporary variables avoid allocating many vectors in the loop below
       OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
                                                 DICOM_TAG_CONTOUR_SEQUENCE, 0,
@@ -680,50 +677,9 @@
   }
 
   
-  DicomStructureSet* DicomStructureSet::SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc,
-                                                        const std::string& instanceId)
-  {
-    const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
-    OrthancPlugins::FullOrthancDataset dataset(orthanc, uri);
-
-    std::auto_ptr<DicomStructureSet> result(new DicomStructureSet(dataset));
-
-    std::set<std::string> instances;
-    result->GetReferencedInstances(instances);
-
-    for (std::set<std::string>::const_iterator it = instances.begin();
-         it != instances.end(); ++it)
-    {
-      Json::Value lookup;
-      MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it);
-
-      if (lookup.type() != Json::arrayValue ||
-          lookup.size() != 1 ||
-          !lookup[0].isMember("Type") ||
-          !lookup[0].isMember("Path") ||
-          lookup[0]["Type"].type() != Json::stringValue ||
-          lookup[0]["ID"].type() != Json::stringValue ||
-          lookup[0]["Type"].asString() != "Instance")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
-      }
-
-      OrthancPlugins::FullOrthancDataset slice
-        (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags");
-      Orthanc::DicomMap m;
-      MessagingToolbox::ConvertDataset(m, slice);
-      result->AddReferencedSlice(m);
-    }
-
-    result->CheckReferencedSlices();
-
-    return result.release();
-  }
-
-
   bool DicomStructureSet::ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
-                                           Structure& structure,
-                                           const CoordinateSystem3D& slice)
+                                           const Structure& structure,
+                                           const CoordinateSystem3D& slice) const
   {
     polygons.clear();
 
@@ -734,7 +690,7 @@
     {
       // This is an axial projection
 
-      for (Polygons::iterator polygon = structure.polygons_.begin();
+      for (Polygons::const_iterator polygon = structure.polygons_.begin();
            polygon != structure.polygons_.end(); ++polygon)
       {
         if (polygon->IsOnSlice(slice))
@@ -760,7 +716,7 @@
       // Sagittal or coronal projection
       std::vector<BoostPolygon> projected;
   
-      for (Polygons::iterator polygon = structure.polygons_.begin();
+      for (Polygons::const_iterator polygon = structure.polygons_.begin();
            polygon != structure.polygons_.end(); ++polygon)
       {
         double x1, y1, x2, y2;
--- a/Framework/Toolbox/DicomStructureSet.h	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Toolbox/DicomStructureSet.h	Tue May 28 14:15:03 2019 +0200
@@ -135,13 +135,13 @@
     Structure& GetStructure(size_t index);
   
     bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
-                          Structure& structure,
-                          const CoordinateSystem3D& slice);
+                          const Structure& structure,
+                          const CoordinateSystem3D& slice) const;
 
   public:
     DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance);
 
-    size_t GetStructureCount() const
+    size_t GetStructuresCount() const
     {
       return structures_.size();
     }
@@ -170,13 +170,9 @@
 
     Vector GetNormal() const;
 
-    // TODO - Remove
-    static DicomStructureSet* SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc,
-                                              const std::string& instanceId);
-
     bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons,
                           size_t index,
-                          const CoordinateSystem3D& slice)
+                          const CoordinateSystem3D& slice) const
     {
       return ProjectStructure(polygons, GetStructure(index), slice);
     }
--- a/Framework/Toolbox/LinearAlgebra.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Toolbox/LinearAlgebra.cpp	Tue May 28 14:15:03 2019 +0200
@@ -26,7 +26,6 @@
 #include <Core/Toolbox.h>
 
 #include <stdio.h>
-#include <boost/lexical_cast.hpp>
 #include <boost/numeric/ublas/lu.hpp>
 
 namespace OrthancStone
@@ -71,9 +70,16 @@
       {
         try
         {
-          target[i] = boost::lexical_cast<double>(items[i]);
+          /**
+           * We don't use "boost::lexical_cast<>" here, as it is very
+           * slow. As we are parsing many doubles, we prefer to use
+           * the standard "std::stod" function:
+           * http://www.cplusplus.com/reference/string/stod/
+           **/
+          
+          target[i] = std::stod(items[i]);
         }
-        catch (boost::bad_lexical_cast&)
+        catch (std::exception&)
         {
           target.clear();
           return false;
--- a/Framework/Toolbox/MessagingToolbox.cpp	Tue May 28 11:37:50 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,456 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "MessagingToolbox.h"
-
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Images/JpegReader.h>
-#include <Core/Images/PngReader.h>
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-#include <Core/Logging.h>
-
-#include <boost/lexical_cast.hpp>
-#include <json/reader.h>
-#include <json/writer.h>
-
-namespace OrthancStone
-{
-  namespace MessagingToolbox
-  {
-    static bool ParseVersion(std::string& version,
-                             unsigned int& major,
-                             unsigned int& minor,
-                             unsigned int& patch,
-                             const Json::Value& info)
-    {
-      if (info.type() != Json::objectValue ||
-          !info.isMember("Version") ||
-          info["Version"].type() != Json::stringValue)
-      {
-        return false;
-      }
-
-      version = info["Version"].asString();
-      if (version == "mainline")
-      {
-        // Some arbitrary high values Orthanc versions will never reach ;)
-        major = 999;
-        minor = 999;
-        patch = 999;
-        return true;
-      }
-
-      std::vector<std::string> tokens;
-      Orthanc::Toolbox::TokenizeString(tokens, version, '.');
-      
-      if (tokens.size() != 2 &&
-          tokens.size() != 3)
-      {
-        return false;
-      }
-
-      int a, b, c;
-      try
-      {
-        a = boost::lexical_cast<int>(tokens[0]);
-        b = boost::lexical_cast<int>(tokens[1]);
-
-        if (tokens.size() == 3)
-        {
-          c = boost::lexical_cast<int>(tokens[2]);
-        }
-        else
-        {
-          c = 0;
-        }
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return false;
-      }
-
-      if (a < 0 ||
-          b < 0 ||
-          c < 0)
-      {
-        return false;
-      }
-      else
-      {
-        major = static_cast<unsigned int>(a);
-        minor = static_cast<unsigned int>(b);
-        patch = static_cast<unsigned int>(c);
-        return true;
-      }         
-    }
-
-
-    bool ParseJson(Json::Value& target,
-                   const void* content,
-                   size_t size)
-    {
-      Json::Reader reader;
-      return reader.parse(reinterpret_cast<const char*>(content),
-                          reinterpret_cast<const char*>(content) + size,
-                          target);
-    }
-
-    void JsonToString(std::string& target,
-                      const Json::Value& source)
-    {
-      Json::FastWriter writer;
-      target = writer.write(source);
-    }
-
-    static void ParseJsonException(Json::Value& target,
-                                   const std::string& source)
-    {
-      Json::Reader reader;
-      if (!reader.parse(source, target))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-    }
-
-
-    void RestApiGet(Json::Value& target,
-                    OrthancPlugins::IOrthancConnection& orthanc,
-                    const std::string& uri)
-    {
-      std::string tmp;
-      orthanc.RestApiGet(tmp, uri);
-      ParseJsonException(target, tmp);
-    }
-
-
-    void RestApiPost(Json::Value& target,
-                     OrthancPlugins::IOrthancConnection& orthanc,
-                     const std::string& uri,
-                     const std::string& body)
-    {
-      std::string tmp;
-      orthanc.RestApiPost(tmp, uri, body);
-      ParseJsonException(target, tmp);
-    }
-
-
-    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc)
-    {
-      try
-      {
-        Json::Value json;
-        RestApiGet(json, orthanc, "/plugins/web-viewer");
-        return json.type() == Json::objectValue;
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        return false;
-      }
-    }
-
-
-    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc)
-    {
-      Json::Value json;
-      std::string version;
-      unsigned int major, minor, patch;
-
-      try
-      {
-        RestApiGet(json, orthanc, "/system");
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        LOG(ERROR) << "Cannot connect to your Orthanc server";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-        
-      if (!ParseVersion(version, major, minor, patch, json))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version;
-
-      // Stone is only compatible with Orthanc >= 1.3.1
-      if (major < 1 ||
-          (major == 1 && minor < 3) ||
-          (major == 1 && minor == 3 && patch < 1))
-      {
-        return false;
-      }
-
-      try
-      {
-        RestApiGet(json, orthanc, "/plugins/web-viewer");       
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        // The Web viewer is not installed, this is OK
-        LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled";
-        return true;
-      }
-
-      if (!ParseVersion(version, major, minor, patch, json))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version;
-
-      return (major >= 3 ||
-              (major == 2 && minor >= 2));
-    }
-
-
-    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                        const std::string& instance,
-                                        unsigned int frame,
-                                        Orthanc::PixelFormat targetFormat)
-    {
-      std::string uri = ("instances/" + instance + "/frames/" + 
-                         boost::lexical_cast<std::string>(frame));
-
-      std::string compressed;
-
-      switch (targetFormat)
-      {
-        case Orthanc::PixelFormat_RGB24:
-          orthanc.RestApiGet(compressed, uri + "/preview");
-          break;
-
-        case Orthanc::PixelFormat_Grayscale16:
-          orthanc.RestApiGet(compressed, uri + "/image-uint16");
-          break;
-
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          orthanc.RestApiGet(compressed, uri + "/image-int16");
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-      
-      std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader);
-      result->ReadFromMemory(compressed);
-
-      if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16)
-      {
-        if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-        {
-          result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-      }
-
-      return result.release();
-    }
-
-
-    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                            const std::string& instance,
-                                            unsigned int frame,
-                                            unsigned int quality,
-                                            Orthanc::PixelFormat targetFormat)
-    {
-      if (quality <= 0 || 
-          quality > 100)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      // This requires the official Web viewer plugin to be installed!
-      std::string uri = ("web-viewer/instances/jpeg" + 
-                         boost::lexical_cast<std::string>(quality) + 
-                         "-" + instance + "_" + 
-                         boost::lexical_cast<std::string>(frame));
-
-      Json::Value encoded;
-      RestApiGet(encoded, orthanc, uri);
-
-      if (encoded.type() != Json::objectValue ||
-          !encoded.isMember("Orthanc") ||
-          encoded["Orthanc"].type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      Json::Value& info = encoded["Orthanc"];
-      if (!info.isMember("PixelData") ||
-          !info.isMember("Stretched") ||
-          !info.isMember("Compression") ||
-          info["Compression"].type() != Json::stringValue ||
-          info["PixelData"].type() != Json::stringValue ||
-          info["Stretched"].type() != Json::booleanValue ||
-          info["Compression"].asString() != "Jpeg")
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }          
-
-      bool isSigned = false;
-      bool isStretched = info["Stretched"].asBool();
-
-      if (info.isMember("IsSigned"))
-      {
-        if (info["IsSigned"].type() != Json::booleanValue)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }          
-        else
-        {
-          isSigned = info["IsSigned"].asBool();
-        }
-      }
-
-      std::string jpeg;
-      Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
-
-      std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
-      reader->ReadFromMemory(jpeg);
-
-      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
-      {
-        if (targetFormat != Orthanc::PixelFormat_RGB24)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-
-        if (isSigned || isStretched)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-        else
-        {
-          return reader.release();
-        }
-      }
-
-      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      if (!isStretched)
-      {
-        if (targetFormat != reader->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-        }
-
-        return reader.release();
-      }
-
-      int32_t stretchLow = 0;
-      int32_t stretchHigh = 0;
-
-      if (!info.isMember("StretchLow") ||
-          !info.isMember("StretchHigh") ||
-          info["StretchLow"].type() != Json::intValue ||
-          info["StretchHigh"].type() != Json::intValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      stretchLow = info["StretchLow"].asInt();
-      stretchHigh = info["StretchHigh"].asInt();
-
-      if (stretchLow < -32768 ||
-          stretchHigh > 65535 ||
-          (stretchLow < 0 && stretchHigh > 32767))
-      {
-        // This range cannot be represented with a uint16_t or an int16_t
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);          
-      }
-
-      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
-      std::auto_ptr<Orthanc::ImageAccessor> image
-        (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false));
-
-      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
-      float offset = static_cast<float>(stretchLow) / scaling;
-      
-      Orthanc::ImageProcessing::Convert(*image, *reader);
-      Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
-
-#if 0
-      /*info.removeMember("PixelData");
-        std::cout << info.toStyledString();*/
-      
-      int64_t a, b;
-      Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image);
-      std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl;
-#endif
-
-      return image.release();
-    }
-
-
-    static void AddTag(Orthanc::DicomMap& target,
-                       const OrthancPlugins::IDicomDataset& source,
-                       const Orthanc::DicomTag& tag)
-    {
-      OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement());
-      
-      std::string value;
-      if (source.GetStringValue(value, key))
-      {
-        target.SetValue(tag, value, false);
-      }
-    }
-
-    
-    void ConvertDataset(Orthanc::DicomMap& target,
-                        const OrthancPlugins::IDicomDataset& source)
-    {
-      target.Clear();
-
-      AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED);
-      AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED);
-      AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS);
-      AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING);
-      AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER);
-      AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR);
-      AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT);
-      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
-      AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT);
-      AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES);
-      AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION);
-      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION);
-      AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING);
-      AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION);
-      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
-      AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE);
-      AddTag(target, source, Orthanc::DICOM_TAG_ROWS);
-      AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL);
-      AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
-      AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS);
-      AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID);
-      AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID);
-      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER);
-      AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH);
-    }
-  }
-}
--- a/Framework/Toolbox/MessagingToolbox.h	Tue May 28 11:37:50 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../StoneEnumerations.h"
-
-#include <Core/DicomFormat/DicomMap.h>
-#include <Core/Images/ImageAccessor.h>
-#include <Plugins/Samples/Common/IDicomDataset.h>
-#include <Plugins/Samples/Common/IOrthancConnection.h>
-
-#include <json/value.h>
-
-namespace OrthancStone
-{
-  namespace MessagingToolbox
-  {
-    bool ParseJson(Json::Value& target,
-                   const void* content,
-                   size_t size);
-
-    void JsonToString(std::string& target,
-                      const Json::Value& source);
-
-
-    void RestApiGet(Json::Value& target,
-                    OrthancPlugins::IOrthancConnection& orthanc,
-                    const std::string& uri);
-
-    void RestApiPost(Json::Value& target,
-                     OrthancPlugins::IOrthancConnection& orthanc,
-                     const std::string& uri,
-                     const std::string& body);
-
-    bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc);
-
-    bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc);
-
-    // This downloads the image from Orthanc and keeps its pixel
-    // format unchanged (will be either Grayscale8, Grayscale16,
-    // SignedGrayscale16, or RGB24)
-    Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                        const std::string& instance,
-                                        unsigned int frame,
-                                        Orthanc::PixelFormat targetFormat);
-
-    Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc,
-                                            const std::string& instance,
-                                            unsigned int frame,
-                                            unsigned int quality,
-                                            Orthanc::PixelFormat targetFormat);
-
-    void ConvertDataset(Orthanc::DicomMap& target,
-                        const OrthancPlugins::IDicomDataset& source);
-  }
-}
--- a/Framework/Volumes/VolumeImageGeometry.h	Tue May 28 11:37:50 2019 +0200
+++ b/Framework/Volumes/VolumeImageGeometry.h	Tue May 28 14:15:03 2019 +0200
@@ -115,6 +115,15 @@
     bool DetectProjection(VolumeProjection& projection,
                           const Vector& planeNormal) const;
 
+    /**
+    Being given a cutting plane, this method will determine if it is an
+    axial, sagittal or coronal cut and returns 
+    the slice number corresponding to this cut.
+
+    If the cutting plane is not parallel to the tree x = 0, y = 0 or z = 0
+    planes, it is considered as arbitrary and the method returns false. 
+    Otherwise, it returns true.
+    */
     bool DetectSlice(VolumeProjection& projection,
                      unsigned int& slice,
                      const CoordinateSystem3D& plane) const;
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Tue May 28 14:15:03 2019 +0200
@@ -1,6 +1,5 @@
 #include "WasmPlatformApplicationAdapter.h"
 
-#include "Framework/Toolbox/MessagingToolbox.h"
 #include "Framework/StoneException.h"
 #include <stdio.h>
 #include "Platforms/Wasm/Defaults.h"
@@ -57,4 +56,4 @@
     }
   }
 
-}
\ No newline at end of file
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue May 28 11:37:50 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue May 28 14:15:03 2019 +0200
@@ -330,6 +330,7 @@
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DownloadStack.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IWebService.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.cpp
@@ -478,7 +479,6 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
--- a/Samples/Sdl/Loader.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Tue May 28 14:15:03 2019 +0200
@@ -193,10 +193,24 @@
     }
   };
 
-
+  /**
+  This interface is implemented by objects representing 3D volume data and 
+  that are able to return an object that represent a slice of their data 
+  and are able to create the corresponding visual representation.
+  */
   class IVolumeSlicer : public boost::noncopyable
   {
   public:
+    /**
+    This interface is implemented by objects representing a slice of 
+    volume data and that are able to create a 2D layer to display a this 
+    slice.
+
+    The CreateSceneLayer factory method is called with an optional
+    configurator that possibly impacts the ISceneLayer subclass that is 
+    created (for instance, if a LUT must be applied on the texture when
+    displaying it)
+    */
     class IExtractedSlice : public boost::noncopyable
     {
     public:
@@ -204,9 +218,18 @@
       {
       }
 
+      /**
+      Invalid slices are created when the data is not ready yet or if the
+      cut is outside of the available geometry.
+      */
       virtual bool IsValid() = 0;
 
-      // Must be a cheap call
+      /**
+      This retrieves the *revision* that gets incremented every time the 
+      underlying object undergoes a mutable operation (that it, changes its 
+      state).
+      This **must** be a cheap call.
+      */
       virtual uint64_t GetRevision() = 0;
 
       // This call can take some time
@@ -214,7 +237,9 @@
                                             const CoordinateSystem3D& cuttingPlane) = 0;
     };
 
-    
+    /**
+    See IExtractedSlice.IsValid()
+    */
     class InvalidSlice : public IExtractedSlice
     {
     public:
@@ -240,13 +265,21 @@
     {
     }
 
+    /**
+    This method is implemented by the objects representing volumetric data
+    and must returns an IExtractedSlice subclass that contains all the data
+    needed to, later one, create its visual representation through
+    CreateSceneLayer.
+    Subclasses a.o.: ExtractedSlice, Slice, InvalidSlice
+    */
     virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
   };
 
-
-
-  // This class combines a 3D image buffer, a 3D volume geometry and
-  // information about the DICOM parameters of the series.
+  /**
+  This class combines a 3D image buffer, a 3D volume geometry and
+  information about the DICOM parameters of the series.
+  (MPR means MultiPlanar Reconstruction)
+  */ 
   class DicomVolumeImage : public boost::noncopyable
   {
   public:
@@ -341,125 +374,157 @@
     }
   };
 
-
-
-  class DicomVolumeImageOrthogonalSlice : public IVolumeSlicer::IExtractedSlice
+  /**
+  Implements the IVolumeSlicer on Dicom volume data when the cutting plane
+  that is supplied to the slicer is either axial, sagittal or coronal. 
+  Arbitrary planes are *not* supported
+  */
+  class DicomVolumeImageMPRSlicer : public IVolumeSlicer
   {
-  private:
-    const DicomVolumeImage&  volume_;
-    bool                 valid_;
-    VolumeProjection     projection_;
-    unsigned int         sliceIndex_;
-
-    void CheckValid() const
+  public:
+    class Slice : public IExtractedSlice
     {
-      if (!valid_)
+    private:
+      const DicomVolumeImage&  volume_;
+      bool                 valid_;
+      VolumeProjection     projection_;
+      unsigned int         sliceIndex_;
+
+      void CheckValid() const
+      {
+        if (!valid_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+      }
+
+    protected:
+      // Can be overloaded in subclasses
+      virtual uint64_t GetRevisionInternal(VolumeProjection projection,
+                                           unsigned int sliceIndex) const
       {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        return volume_.GetRevision();
+      }
+
+    public:
+      /**
+      The constructor initializes the type of projection (axial, sagittal or
+      coronal) and the corresponding slice index, from the cutting plane.
+      */
+      Slice(const DicomVolumeImage& volume,
+            const CoordinateSystem3D& cuttingPlane) :
+        volume_(volume)
+      {
+        valid_ = (volume_.HasDicomParameters() &&
+                  volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane));
       }
-    }
+
+      VolumeProjection GetProjection() const
+      {
+        CheckValid();
+        return projection_;
+      }
+
+      unsigned int GetSliceIndex() const
+      {
+        CheckValid();
+        return sliceIndex_;
+      }
+
+      virtual bool IsValid()
+      {
+        return valid_;
+      }
+
+      virtual uint64_t GetRevision()
+      {
+        CheckValid();
+        return GetRevisionInternal(projection_, sliceIndex_);
+      }
 
-  protected:
-    // Can be overloaded in subclasses
-    virtual uint64_t GetRevisionInternal(VolumeProjection projection,
-                                         unsigned int sliceIndex) const
-    {
-      return volume_.GetRevision();
-    }
+      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                            const CoordinateSystem3D& cuttingPlane)
+      {
+        CheckValid();
+
+        if (configurator == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer,
+                                          "A style configurator is mandatory for textures");
+        }
+
+        std::auto_ptr<TextureBaseSceneLayer> texture;
+        
+        {
+          const DicomInstanceParameters& parameters = volume_.GetDicomParameters();
+          ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_);
+          texture.reset(dynamic_cast<TextureBaseSceneLayer*>
+                        (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters)));
+        }
+
+        const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_);
+      
+        double x0, y0, x1, y1;
+        cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin());
+        cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX());
+        texture->SetOrigin(x0, y0);
+
+        double dx = x1 - x0;
+        double dy = y1 - y0;
+        if (!LinearAlgebra::IsCloseToZero(dx) ||
+            !LinearAlgebra::IsCloseToZero(dy))
+        {
+          texture->SetAngle(atan2(dy, dx));
+        }
+        
+        Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_);
+        texture->SetPixelSpacing(tmp[0], tmp[1]);
+
+        return texture.release();
+
+#if 0
+        double w = texture->GetTexture().GetWidth() * tmp[0];
+        double h = texture->GetTexture().GetHeight() * tmp[1];
+        printf("%.1f %.1f %.1f => %.1f %.1f => %.1f %.1f\n",
+               system.GetOrigin() [0],
+               system.GetOrigin() [1],
+               system.GetOrigin() [2],
+               x0, y0, x0 + w, y0 + h);
+
+        std::auto_ptr<PolylineSceneLayer> toto(new PolylineSceneLayer);
+
+        PolylineSceneLayer::Chain c;
+        c.push_back(ScenePoint2D(x0, y0));
+        c.push_back(ScenePoint2D(x0 + w, y0));
+        c.push_back(ScenePoint2D(x0 + w, y0 + h));
+        c.push_back(ScenePoint2D(x0, y0 + h));
+      
+        toto->AddChain(c, true);
+
+        return toto.release();
+#endif
+      }
+    };
+
+  private:
+    boost::shared_ptr<DicomVolumeImage>  volume_;
 
   public:
-    DicomVolumeImageOrthogonalSlice(const DicomVolumeImage& volume,
-                                    const CoordinateSystem3D& cuttingPlane) :
+    DicomVolumeImageMPRSlicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
       volume_(volume)
     {
-      valid_ = (volume_.HasDicomParameters() &&
-                volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane));
-    }
-
-    VolumeProjection GetProjection() const
-    {
-      CheckValid();
-      return projection_;
-    }
-
-    unsigned int GetSliceIndex() const
-    {
-      CheckValid();
-      return sliceIndex_;
-    }
-
-    virtual bool IsValid()
-    {
-      return valid_;
-    }
-
-    virtual uint64_t GetRevision()
-    {
-      CheckValid();
-      return GetRevisionInternal(projection_, sliceIndex_);
     }
 
-    virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
-                                          const CoordinateSystem3D& cuttingPlane)
+    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
     {
-      CheckValid();
-
-      if (configurator == NULL)
+      if (volume_->HasGeometry())
       {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer,
-                                        "A style configurator is mandatory for textures");
-      }
-
-      std::auto_ptr<TextureBaseSceneLayer> texture;
-        
-      {
-        const DicomInstanceParameters& parameters = volume_.GetDicomParameters();
-        ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_);
-        texture.reset(dynamic_cast<TextureBaseSceneLayer*>
-                      (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters)));
+        return new Slice(*volume_, cuttingPlane);
       }
-
-      const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_);
-      
-      double x0, y0, x1, y1;
-      cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin());
-      cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX());
-      texture->SetOrigin(x0, y0);
-
-      double dx = x1 - x0;
-      double dy = y1 - y0;
-      if (!LinearAlgebra::IsCloseToZero(dx) ||
-          !LinearAlgebra::IsCloseToZero(dy))
+      else
       {
-        texture->SetAngle(atan2(dy, dx));
+        return new IVolumeSlicer::InvalidSlice;
       }
-        
-      Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_);
-      texture->SetPixelSpacing(tmp[0], tmp[1]);
-
-      return texture.release();
-
-#if 0
-      double w = texture->GetTexture().GetWidth() * tmp[0];
-      double h = texture->GetTexture().GetHeight() * tmp[1];
-      printf("%.1f %.1f %.1f => %.1f %.1f => %.1f %.1f\n",
-             system.GetOrigin() [0],
-             system.GetOrigin() [1],
-             system.GetOrigin() [2],
-             x0, y0, x0 + w, y0 + h);
-
-      std::auto_ptr<PolylineSceneLayer> toto(new PolylineSceneLayer);
-
-      PolylineSceneLayer::Chain c;
-      c.push_back(ScenePoint2D(x0, y0));
-      c.push_back(ScenePoint2D(x0 + w, y0));
-      c.push_back(ScenePoint2D(x0 + w, y0 + h));
-      c.push_back(ScenePoint2D(x0, y0 + h));
-      
-      toto->AddChain(c, true);
-
-      return toto.release();
-#endif
     }
   };
 
@@ -583,6 +648,7 @@
       }
 
       // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
+      // (called with the slices created in LoadGeometry)
       void ComputeGeometry(SlicesSorter& slices)
       {
         Clear();
@@ -664,7 +730,7 @@
     };
 
 
-    class Slice : public DicomVolumeImageOrthogonalSlice
+    class ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice
     {
     private:
       const OrthancSeriesVolumeProgressiveLoader&  that_;
@@ -680,17 +746,25 @@
         else
         {
           // For coronal and sagittal projections, we take the global
-          // revision of the volume
+          // revision of the volume because even if a single slice changes,
+          // this means the projection will yield a different result --> 
+          // we must increase the revision as soon as any slice changes 
           return that_.volume_->GetRevision();
         }
       }
 
     public:
-      Slice(const OrthancSeriesVolumeProgressiveLoader& that,
-            const CoordinateSystem3D& plane) :
-        DicomVolumeImageOrthogonalSlice(*that.volume_, plane),
+      ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
+                     const CoordinateSystem3D& plane) :
+        DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
         that_(that)
       {
+        if (that_.strategy_.get() != NULL &&
+            IsValid() &&
+            GetProjection() == VolumeProjection_Axial)
+        {
+          that_.strategy_->SetCurrent(GetSliceIndex());
+        }
       }
     };
 
@@ -746,7 +820,9 @@
       }
     }
 
-
+    /**
+    This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
+    */
     void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
     {
       Json::Value body;
@@ -770,6 +846,7 @@
           std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
           instance->SetOrthancInstanceIdentifier(instances[i]);
 
+          // the 3D plane corresponding to the slice
           CoordinateSystem3D geometry = instance->GetGeometry();
           slices.AddSlice(geometry, instance.release());
         }
@@ -791,7 +868,7 @@
         volume_->SetDicomParameters(parameters);
         volume_->GetPixelData().Clear();
 
-        strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(slicesCount), BEST_QUALITY));
+        strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY));
         
         assert(simultaneousDownloads_ != 0);
         for (unsigned int i = 0; i < simultaneousDownloads_; i++)
@@ -933,16 +1010,7 @@
     {
       if (volume_->HasGeometry())
       {
-        std::auto_ptr<Slice> slice(new Slice(*this, cuttingPlane));
-
-        if (strategy_.get() != NULL &&
-            slice->IsValid() &&
-            slice->GetProjection() == VolumeProjection_Axial)
-        {
-          strategy_->SetCurrent(slice->GetSliceIndex());
-        }
-
-        return slice.release();
+        return new ExtractedSlice(*this, cuttingPlane);
       }
       else
       {
@@ -952,44 +1020,196 @@
   };
 
 
-
-  class OrthancMultiframeVolumeLoader :
-    public IObserver,
-    public IObservable,
-    public IVolumeSlicer
+  /**
+  This class is supplied with Oracle commands and will schedule up to 
+  simultaneousDownloads_ of them at the same time, then will schedule the 
+  rest once slots become available. It is used, a.o., by the 
+  OrtancMultiframeVolumeLoader class.
+  */
+  class LoaderStateMachine : public IObserver
   {
-  private:
+  protected:
     class State : public Orthanc::IDynamicObject
     {
     private:
-      OrthancMultiframeVolumeLoader&  that_;
-
-    protected:
-      void Schedule(OrthancRestApiCommand* command) const
-      {
-        that_.oracle_.Schedule(that_, command);
-      }
-
-      OrthancMultiframeVolumeLoader& GetTarget() const
-      {
-        return that_;
-      }
+      LoaderStateMachine&  that_;
 
     public:
-      State(OrthancMultiframeVolumeLoader& that) :
+      State(LoaderStateMachine& that) :
         that_(that)
       {
       }
+
+      State(const State& currentState) :
+        that_(currentState.that_)
+      {
+      }
+
+      void Schedule(OracleCommandWithPayload* command) const
+      {
+        that_.Schedule(command);
+      }
+
+      template <typename T>
+      T& GetLoader() const
+      {
+        return dynamic_cast<T&>(that_);
+      }
       
-      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const = 0;
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+      
+      virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message) const
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+      
+      virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) const
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
     };
-    
-    void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+
+    void Schedule(OracleCommandWithPayload* command)
+    {
+      std::auto_ptr<OracleCommandWithPayload> protection(command);
+
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      
+      if (!command->HasPayload())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "The payload must contain the next state");
+      }
+
+      pendingCommands_.push_back(protection.release());
+      Step();
+    }
+
+    void Start()
+    {
+      if (active_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      active_ = true;
+
+      for (size_t i = 0; i < simultaneousDownloads_; i++)
+      {
+        Step();
+      }
+    }
+
+  private:
+    void Step()
+    {
+      if (!pendingCommands_.empty() &&
+          activeCommands_ < simultaneousDownloads_)
+      {
+        oracle_.Schedule(*this, pendingCommands_.front());
+        pendingCommands_.pop_front();
+
+        activeCommands_++;
+      }
+    }
+
+    void Clear()
+    {
+      for (PendingCommands::iterator it = pendingCommands_.begin();
+           it != pendingCommands_.end(); ++it)
+      {
+        delete *it;
+      }
+    }
+
+    void HandleException(const OracleCommandExceptionMessage& message)
+    {
+      LOG(ERROR) << "Error in the state machine, stopping all processing";
+      Clear();
+    }
+
+    template <typename T>
+    void Handle(const T& message)
     {
       dynamic_cast<const State&>(message.GetOrigin().GetPayload()).Handle(message);
+      activeCommands_--;
+      Step();
     }
 
+    typedef std::list<IOracleCommand*>  PendingCommands;
 
+    IOracle&         oracle_;
+    bool             active_;
+    unsigned int     simultaneousDownloads_;
+    PendingCommands  pendingCommands_;
+    unsigned int     activeCommands_;
+
+  public:
+    LoaderStateMachine(IOracle& oracle,
+                       IObservable& oracleObservable) :
+      IObserver(oracleObservable.GetBroker()),
+      oracle_(oracle),
+      active_(false),
+      simultaneousDownloads_(4),
+      activeCommands_(0)
+    {
+      oracleObservable.RegisterObserverCallback(
+        new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage>
+        (*this, &LoaderStateMachine::Handle<OrthancRestApiCommand::SuccessMessage>));
+
+      oracleObservable.RegisterObserverCallback(
+        new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage>
+        (*this, &LoaderStateMachine::Handle<GetOrthancImageCommand::SuccessMessage>));
+
+      oracleObservable.RegisterObserverCallback(
+        new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage>
+        (*this, &LoaderStateMachine::Handle<GetOrthancWebViewerJpegCommand::SuccessMessage>));
+
+      oracleObservable.RegisterObserverCallback(
+        new Callable<LoaderStateMachine, OracleCommandExceptionMessage>
+        (*this, &LoaderStateMachine::HandleException));
+    }
+
+    virtual ~LoaderStateMachine()
+    {
+      Clear();
+    }
+
+    bool IsActive() const
+    {
+      return active_;
+    }
+
+    void SetSimultaneousDownloads(unsigned int count)
+    {
+      if (active_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else if (count == 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
+      }
+      else
+      {
+        simultaneousDownloads_ = count;
+      }
+    }
+  };
+
+
+
+  class OrthancMultiframeVolumeLoader :
+    public LoaderStateMachine,
+    public IObservable
+  {
+  private:
     class LoadRTDoseGeometry : public State
     {
     private:
@@ -1005,6 +1225,7 @@
         {
           throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
         }
+
       }
 
       virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
@@ -1013,7 +1234,7 @@
         std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
         dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
 
-        GetTarget().SetGeometry(*dicom_);
+        GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
       }      
     };
 
@@ -1043,6 +1264,8 @@
       
       virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
+        OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
+        
         Json::Value body;
         message.ParseJsonBody(body);
         
@@ -1060,15 +1283,15 @@
           // mandatory for RT-DOSE, but is too long to be returned by default
           
           std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-          command->SetUri("/instances/" + GetTarget().GetInstanceId() + "/content/" +
+          command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
                           Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
-          command->SetPayload(new LoadRTDoseGeometry(GetTarget(), dicom.release()));
+          command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release()));
 
           Schedule(command.release());
         }
         else
         {
-          GetTarget().SetGeometry(*dicom);
+          loader.SetGeometry(*dicom);
         }
       }
     };
@@ -1085,7 +1308,7 @@
       
       virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
-        GetTarget().SetTransferSyntax(message.GetAnswer());
+        GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
       }
     };
    
@@ -1100,22 +1323,20 @@
       
       virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
-        GetTarget().SetUncompressedPixelData(message.GetAnswer());
+        GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
       }
     };
    
     
 
     boost::shared_ptr<DicomVolumeImage>   volume_;
-    IOracle&     oracle_;
-    bool         active_;
     std::string  instanceId_;
     std::string  transferSyntaxUid_;
 
 
     const std::string& GetInstanceId() const
     {
-      if (active_)
+      if (IsActive())
       {
         return instanceId_;
       }
@@ -1133,7 +1354,13 @@
       {
         return;
       }
-      
+      /*
+      1.2.840.10008.1.2	Implicit VR Endian: Default Transfer Syntax for DICOM
+      1.2.840.10008.1.2.1	Explicit VR Little Endian
+      1.2.840.10008.1.2.2	Explicit VR Big Endian
+
+      See https://www.dicomlibrary.com/dicom/transfer-syntax/
+      */
       if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
           transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
           transferSyntaxUid_ == "1.2.840.10008.1.2.2")
@@ -1143,7 +1370,7 @@
         command->SetUri("/instances/" + instanceId_ + "/content/" +
                         Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
         command->SetPayload(new LoadUncompressedPixelData(*this));
-        oracle_.Schedule(*this, command.release());
+        Schedule(command.release());
       }
       else
       {
@@ -1220,7 +1447,6 @@
     {
       ImageBuffer3D& target = volume_->GetPixelData();
       
-      const Orthanc::PixelFormat format = target.GetFormat();
       const unsigned int bpp = target.GetBytesPerPixel();
       const unsigned int width = target.GetWidth();
       const unsigned int height = target.GetHeight();
@@ -1248,7 +1474,7 @@
 
         for (unsigned int y = 0; y < height; y++)
         {
-          assert(sizeof(T) == Orthanc::GetBytesPerPixel(format));
+          assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
 
           T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
 
@@ -1286,61 +1512,36 @@
     OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
                                   IOracle& oracle,
                                   IObservable& oracleObservable) :
-      IObserver(oracleObservable.GetBroker()),
+      LoaderStateMachine(oracle, oracleObservable),
       IObservable(oracleObservable.GetBroker()),
-      volume_(volume),
-      oracle_(oracle),
-      active_(false)
+      volume_(volume)
     {
       if (volume.get() == NULL)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
       }
-      
-      oracleObservable.RegisterObserverCallback(
-        new Callable<OrthancMultiframeVolumeLoader, OrthancRestApiCommand::SuccessMessage>
-        (*this, &OrthancMultiframeVolumeLoader::Handle));
     }
 
 
     void LoadInstance(const std::string& instanceId)
     {
-      if (active_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        active_ = true;
-        instanceId_ = instanceId;
+      Start();
 
-        {
-          std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-          command->SetHttpHeader("Accept-Encoding", "gzip");
-          command->SetUri("/instances/" + instanceId + "/tags");
-          command->SetPayload(new LoadGeometry(*this));
-          oracle_.Schedule(*this, command.release());
-        }
+      instanceId_ = instanceId;
 
-        {
-          std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-          command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
-          command->SetPayload(new LoadTransferSyntax(*this));
-          oracle_.Schedule(*this, command.release());
-        }
+      {
+        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+        command->SetHttpHeader("Accept-Encoding", "gzip");
+        command->SetUri("/instances/" + instanceId + "/tags");
+        command->SetPayload(new LoadGeometry(*this));
+        Schedule(command.release());
       }
-    }
-
 
-    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
-    {
-      if (volume_->HasGeometry())
       {
-        return new DicomVolumeImageOrthogonalSlice(*volume_, cuttingPlane);
-      }
-      else
-      {
-        return new IVolumeSlicer::InvalidSlice;
+        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+        command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
+        command->SetPayload(new LoadTransferSyntax(*this));
+        Schedule(command.release());
       }
     }
   };
@@ -1469,46 +1670,108 @@
     public IVolumeSlicer
   {
   private:
-    enum State
-    {
-      State_Setup,
-      State_Loading,
-      State_Ready
-    };    
-    
-    
     std::auto_ptr<DicomStructureSet>  content_;
     IOracle&                          oracle_;
-    State                             state_;
+    bool                              active_;
+    uint64_t                          revision_;
     std::string                       instanceId_;
 
     void Handle(const OrthancRestApiCommand::SuccessMessage& message)
     {
-      if (state_ != State_Loading)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      const boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
+      assert(active_);
 
       {
         OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
         content_.reset(new DicomStructureSet(dicom));
       }
 
-      const boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      std::set<std::string> instances;
+      content_->GetReferencedInstances(instances);
+
+      for (std::set<std::string>::const_iterator
+             it = instances.begin(); it != instances.end(); ++it)
+      {
+        printf("[%s]\n", it->c_str());
+      }
+    }
+
+    
+    class Slice : public IExtractedSlice
+    {
+    private:
+      const DicomStructureSet&  content_;
+      uint64_t                  revision_;
+      bool                      isValid_;
+      
+    public:
+      Slice(const DicomStructureSet& content,
+            uint64_t revision,
+            const CoordinateSystem3D& cuttingPlane) :
+        content_(content),
+        revision_(revision)
+      {
+        bool opposite;
+
+        const Vector normal = content.GetNormal();
+        isValid_ = (
+          GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
+          GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
+          GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
+      }
+      
+      virtual bool IsValid()
+      {
+        return isValid_;
+      }
 
-      printf("LOADED: %d\n", (end - start).total_milliseconds());
-      state_ = State_Ready;
-    }
-      
+      virtual uint64_t GetRevision()
+      {
+        return revision_;
+      }
+
+      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
+                                            const CoordinateSystem3D& cuttingPlane)
+      {
+        assert(isValid_);
+
+        std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
+
+        for (size_t i = 0; i < content_.GetStructuresCount(); i++)
+        {
+          std::vector< std::vector<DicomStructureSet::PolygonPoint> > polygons;
+          
+          if (content_.ProjectStructure(polygons, i, cuttingPlane))
+          {
+            printf(">> %d\n", static_cast<int>(polygons.size()));
+            
+            for (size_t j = 0; j < polygons.size(); j++)
+            {
+              PolylineSceneLayer::Chain chain;
+              chain.resize(polygons[j].size());
+            
+              for (size_t k = 0; k < polygons[i].size(); k++)
+              {
+                chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second);
+              }
+
+              layer->AddChain(chain, true /* closed */);
+            }
+          }
+        }
+
+        printf("OK\n");
+
+        return layer.release();
+      }
+    };
     
   public:
     DicomStructureSetLoader(IOracle& oracle,
                             IObservable& oracleObservable) :
       IObserver(oracleObservable.GetBroker()),
       oracle_(oracle),
-      state_(State_Setup)
+      active_(false),
+      revision_(0)
     {
       oracleObservable.RegisterObserverCallback(
         new Callable<DicomStructureSetLoader, OrthancRestApiCommand::SuccessMessage>
@@ -1518,13 +1781,13 @@
     
     void LoadInstance(const std::string& instanceId)
     {
-      if (state_ != State_Setup)
+      if (active_)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
       }
       else
       {
-        state_ = State_Loading;
+        active_ = true;
         instanceId_ = instanceId;
 
         {
@@ -1538,7 +1801,15 @@
 
     virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
     {
-      return NULL;
+      if (content_.get() == NULL)
+      {
+        // Geometry is not available yet
+        return new IVolumeSlicer::InvalidSlice;
+      }
+      else
+      {
+        return new Slice(*content_, revision_, cuttingPlane);
+      }
     }
   };
 
@@ -1759,7 +2030,7 @@
   OrthancStone::CoordinateSystem3D  plane_;
   OrthancStone::IOracle&            oracle_;
   OrthancStone::Scene2D             scene_;
-  std::auto_ptr<OrthancStone::VolumeSceneLayerSource>  source1_, source2_;
+  std::auto_ptr<OrthancStone::VolumeSceneLayerSource>  source1_, source2_, source3_;
 
 
   void Refresh()
@@ -1774,6 +2045,11 @@
       source2_->Update(plane_);
     }
 
+    if (source3_.get() != NULL)
+    {
+      source3_->Update(plane_);
+    }
+
     scene_.FitContent(1024, 768);
       
     {
@@ -1923,6 +2199,13 @@
       source2_->SetConfigurator(style);
     }
   }
+
+  void SetStructureSet(int depth,
+                       const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
+  {
+    source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
+  }
+                       
 };
 
 
@@ -1947,7 +2230,8 @@
   }
 
 
-  toto->SetReferenceLoader(*ctLoader);
+  //toto->SetReferenceLoader(*ctLoader);
+  toto->SetReferenceLoader(*doseLoader);
 
 
 #if 1
@@ -1963,10 +2247,14 @@
   {
     std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator);
     config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
-    toto->SetVolume2(1, doseLoader, config.release());
+
+    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose));
+    toto->SetVolume2(1, tmp, config.release());
   }
 
-  //oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100));
+  toto->SetStructureSet(2, rtstructLoader);
+  
+  oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100));
 
   if (0)
   {
@@ -2045,10 +2333,16 @@
 
   
   // 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
-
+  doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+  //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6");  // RT-STRUCT
+#endif
   // 2015-01-28-Multiframe
   //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279");  // Multiframe CT
   
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue May 28 11:37:50 2019 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue May 28 14:15:03 2019 +0200
@@ -23,10 +23,10 @@
 
 #include "../Framework/Deprecated/Layers/FrameRenderer.h"
 #include "../Framework/Deprecated/Toolbox/DownloadStack.h"
+#include "../Framework/Deprecated/Toolbox/MessagingToolbox.h"
 #include "../Framework/Deprecated/Toolbox/OrthancSlicesLoader.h"
 #include "../Framework/Toolbox/FiniteProjectiveCamera.h"
 #include "../Framework/Toolbox/GeometryToolbox.h"
-#include "../Framework/Toolbox/MessagingToolbox.h"
 #include "../Framework/Volumes/ImageBuffer3D.h"
 #include "../Platforms/Generic/OracleWebService.h"
 
@@ -636,7 +636,7 @@
 {
   Json::Value response;
   std::string source = "{\"command\":\"panel:takeDarkImage\",\"commandType\":\"simple\",\"args\":{}}";
-  ASSERT_TRUE(OrthancStone::MessagingToolbox::ParseJson(response, source.c_str(), source.size()));
+  ASSERT_TRUE(Deprecated::MessagingToolbox::ParseJson(response, source.c_str(), source.size()));
 }
 
 TEST(VolumeImageGeometry, Basic)