changeset 1225:16738485e457 broker

deprecating DicomStructureSetLoader, OrthancMultiframeVolumeLoader and OrthancSeriesVolumeProgressiveLoader
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 08 Dec 2019 11:45:09 +0100
parents 37bc7f115f81
children 05d05cba0f4f
files Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp Framework/Deprecated/Loaders/DicomStructureSetLoader.h Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp Framework/Deprecated/Loaders/DicomStructureSetLoader2.h Framework/Deprecated/Loaders/LoaderCache.cpp Framework/Deprecated/Loaders/LoaderCache.h Framework/Deprecated/Loaders/LoaderStateMachine.cpp Framework/Deprecated/Loaders/LoaderStateMachine.h Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.h Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.h Framework/Loaders/DicomStructureSetLoader.cpp Framework/Loaders/DicomStructureSetLoader.h Framework/Loaders/DicomStructureSetLoader2.cpp Framework/Loaders/DicomStructureSetLoader2.h Framework/Loaders/LoaderCache.cpp Framework/Loaders/LoaderCache.h Framework/Loaders/LoaderStateMachine.cpp Framework/Loaders/LoaderStateMachine.h Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Framework/Loaders/OrthancMultiframeVolumeLoader.h Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 25 files changed, 2711 insertions(+), 2717 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,417 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomStructureSetLoader.h"
+
+#include "../../Scene2D/PolylineSceneLayer.h"
+#include "../../StoneException.h"
+#include "../../Toolbox/GeometryToolbox.h"
+
+#include <Core/Toolbox.h>
+
+#include <algorithm>
+
+#if 0
+bool logbgo233 = false;
+bool logbgo115 = false;
+#endif
+
+namespace Deprecated
+{
+
+#if 0
+  void DumpDicomMap(std::ostream& o, const Orthanc::DicomMap& dicomMap)
+  {
+    using namespace std;
+    //ios_base::fmtflags state = o.flags();
+    //o.flags(ios::right | ios::hex);
+    //o << "(" << setfill('0') << setw(4) << tag.GetGroup()
+    //  << "," << setw(4) << tag.GetElement() << ")";
+    //o.flags(state);
+    Json::Value val;
+    dicomMap.Serialize(val);
+    o << val;
+    //return o;
+  }
+#endif
+
+
+  class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State
+  {
+  private:
+    std::string instanceId_;
+      
+  public:
+    AddReferencedInstance(DicomStructureSetLoader& that,
+                          const std::string& instanceId) :
+      State(that),
+      instanceId_(instanceId)
+    {
+    }
+
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+      Json::Value tags;
+      message.ParseJsonBody(tags);
+        
+      Orthanc::DicomMap dicom;
+      dicom.FromDicomAsJson(tags);
+
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+
+      loader.content_->AddReferencedSlice(dicom);
+
+      loader.countProcessedInstances_ ++;
+      assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);
+
+      if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
+      {
+        // All the referenced instances have been loaded, finalize the RT-STRUCT
+        loader.content_->CheckReferencedSlices();
+        loader.revision_++;
+        loader.SetStructuresReady();
+      }
+    }
+  };
+
+
+  // State that converts a "SOP Instance UID" to an Orthanc identifier
+  class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State
+  {
+  private:
+    std::string  sopInstanceUid_;
+      
+  public:
+    LookupInstance(DicomStructureSetLoader& that,
+                   const std::string& sopInstanceUid) :
+      State(that),
+      sopInstanceUid_(sopInstanceUid)
+    {
+    }
+
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+#if 0
+      LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)";
+#endif
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+
+      Json::Value lookup;
+      message.ParseJsonBody(lookup);
+
+      if (lookup.type() != Json::arrayValue ||
+          lookup.size() != 1 ||
+          !lookup[0].isMember("Type") ||
+          !lookup[0].isMember("Path") ||
+          lookup[0]["Type"].type() != Json::stringValue ||
+          lookup[0]["ID"].type() != Json::stringValue ||
+          lookup[0]["Type"].asString() != "Instance")
+      {
+        std::stringstream msg;
+        msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = ";
+        for (OrthancStone::OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin();
+             it != message.GetAnswerHeaders().end(); ++it)
+        {
+          msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n";
+        }
+        const std::string msgStr = msg.str();
+        LOG(ERROR) << msgStr;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
+      }
+
+      const std::string instanceId = lookup[0]["ID"].asString();
+
+      {
+        std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+        command->SetHttpHeader("Accept-Encoding", "gzip");
+        std::string uri = "/instances/" + instanceId + "/tags";
+        command->SetUri(uri);
+        command->AcquirePayload(new AddReferencedInstance(loader, instanceId));
+        Schedule(command.release());
+      }
+    }
+  };
+
+
+  class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State
+  {
+  public:
+    LoadStructure(DicomStructureSetLoader& that) :
+    State(that)
+    {
+    }
+    
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+#if 0
+      if (logbgo115)
+        LOG(TRACE) << "DicomStructureSetLoader::LoadStructure::Handle() (SUCCESS)";
+#endif
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+        
+      {
+        OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
+        loader.content_.reset(new OrthancStone::DicomStructureSet(dicom));
+        size_t structureCount = loader.content_->GetStructuresCount();
+        loader.structureVisibility_.resize(structureCount);
+        bool everythingVisible = false;
+        if ((loader.initiallyVisibleStructures_.size() == 1)
+          && (loader.initiallyVisibleStructures_[0].size() == 1)
+          && (loader.initiallyVisibleStructures_[0][0] == '*'))
+        {
+          everythingVisible = true;
+        }
+
+        for (size_t i = 0; i < structureCount; ++i)
+        {
+          // if a single "*" string is supplied, this means we want everything 
+          // to be visible...
+          if(everythingVisible)
+          {
+            loader.structureVisibility_.at(i) = true;
+          }
+          else
+          {
+            // otherwise, we only enable visibility for those structures whose 
+            // names are mentioned in the initiallyVisibleStructures_ array
+            const std::string& structureName = loader.content_->GetStructureName(i);
+
+            std::vector<std::string>::iterator foundIt =
+              std::find(
+                loader.initiallyVisibleStructures_.begin(),
+                loader.initiallyVisibleStructures_.end(),
+                structureName);
+            std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end();
+            if (foundIt != endIt)
+              loader.structureVisibility_.at(i) = true;
+            else
+              loader.structureVisibility_.at(i) = false;
+          }
+        }
+      }
+
+      // Some (admittedly invalid) Dicom files have empty values in the 
+      // 0008,1155 tag. We try our best to cope with this.
+      std::set<std::string> instances;
+      std::set<std::string> nonEmptyInstances;
+      loader.content_->GetReferencedInstances(instances);
+      for (std::set<std::string>::const_iterator
+        it = instances.begin(); it != instances.end(); ++it)
+      {
+        std::string instance = Orthanc::Toolbox::StripSpaces(*it);
+        if(instance != "")
+          nonEmptyInstances.insert(instance);
+      }
+
+      loader.countReferencedInstances_ = 
+        static_cast<unsigned int>(nonEmptyInstances.size());
+
+      for (std::set<std::string>::const_iterator
+        it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it)
+      {
+        std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+        command->SetUri("/tools/lookup");
+        command->SetMethod(Orthanc::HttpMethod_Post);
+        command->SetBody(*it);
+        command->AcquirePayload(new LookupInstance(loader, *it));
+        Schedule(command.release());
+      }
+    }
+  };
+    
+
+  class DicomStructureSetLoader::Slice : public IExtractedSlice
+  {
+  private:
+    const OrthancStone::DicomStructureSet&  content_;
+    uint64_t                  revision_;
+    bool                      isValid_;
+    std::vector<bool>         visibility_;
+      
+  public:
+    /**
+    The visibility vector must either:
+    - be empty
+    or
+    - contain the same number of items as the number of structures in the 
+      structure set.
+    In the first case (empty vector), all the structures are displayed.
+    In the second case, the visibility of each structure is defined by the 
+    content of the vector at the corresponding index.
+    */
+    Slice(const OrthancStone::DicomStructureSet& content,
+          uint64_t revision,
+          const OrthancStone::CoordinateSystem3D& cuttingPlane,
+          std::vector<bool> visibility = std::vector<bool>()) 
+      : content_(content)
+      , revision_(revision)
+      , visibility_(visibility)
+    {
+      ORTHANC_ASSERT((visibility_.size() == content_.GetStructuresCount())
+        || (visibility_.size() == 0u));
+
+      bool opposite;
+
+      const OrthancStone::Vector normal = content.GetNormal();
+      isValid_ = (
+        OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
+        OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
+        OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
+    }
+      
+    virtual bool IsValid()
+    {
+      return isValid_;
+    }
+
+    virtual uint64_t GetRevision()
+    {
+      return revision_;
+    }
+
+    virtual OrthancStone::ISceneLayer* CreateSceneLayer(
+      const OrthancStone::ILayerStyleConfigurator* configurator,
+      const OrthancStone::CoordinateSystem3D& cuttingPlane)
+    {
+      assert(isValid_);
+
+      std::auto_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer);
+      layer->SetThickness(2);
+
+      for (size_t i = 0; i < content_.GetStructuresCount(); i++)
+      {
+        if ((visibility_.size() == 0) || visibility_.at(i))
+        {
+          const OrthancStone::Color& color = content_.GetStructureColor(i);
+
+#ifdef USE_BOOST_UNION_FOR_POLYGONS 
+          std::vector< std::vector<OrthancStone::Point2D> > polygons;
+
+          if (content_.ProjectStructure(polygons, i, cuttingPlane))
+          {
+            for (size_t j = 0; j < polygons.size(); j++)
+            {
+              PolylineSceneLayer::Chain chain;
+              chain.resize(polygons[j].size());
+
+              for (size_t k = 0; k < polygons[j].size(); k++)
+              {
+                chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y);
+    }
+
+              layer->AddChain(chain, true /* closed */, color);
+  }
+        }
+#else
+          std::vector< std::pair<OrthancStone::Point2D, OrthancStone::Point2D> > segments;
+
+          if (content_.ProjectStructure(segments, i, cuttingPlane))
+          {
+            for (size_t j = 0; j < segments.size(); j++)
+            {
+              OrthancStone::PolylineSceneLayer::Chain chain;
+              chain.resize(2);
+
+              chain[0] = OrthancStone::ScenePoint2D(segments[j].first.x, segments[j].first.y);
+              chain[1] = OrthancStone::ScenePoint2D(segments[j].second.x, segments[j].second.y);
+
+              layer->AddChain(chain, false /* NOT closed */, color);
+            }
+          }
+#endif        
+        }
+      }
+
+      return layer.release();
+    }
+  };
+    
+
+  DicomStructureSetLoader::DicomStructureSetLoader(OrthancStone::IOracle& oracle,
+                                                   OrthancStone::IObservable& oracleObservable) :
+    LoaderStateMachine(oracle, oracleObservable),
+    revision_(0),
+    countProcessedInstances_(0),
+    countReferencedInstances_(0),
+    structuresReady_(false)
+  {
+  }
+    
+    
+  void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
+  {
+    structureVisibility_.at(structureIndex) = display;
+    revision_++;
+  }
+
+  DicomStructureSetLoader::~DicomStructureSetLoader()
+  {
+    LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()";
+  }
+
+  void DicomStructureSetLoader::LoadInstance(
+    const std::string& instanceId, 
+    const std::vector<std::string>& initiallyVisibleStructures)
+  {
+    Start();
+      
+    instanceId_ = instanceId;
+    initiallyVisibleStructures_ = initiallyVisibleStructures;
+
+    {
+      std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+
+      std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
+
+      command->SetUri(uri);
+      command->AcquirePayload(new LoadStructure(*this));
+      Schedule(command.release());
+    }
+  }
+
+
+  OrthancStone::IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane)
+  {
+    if (content_.get() == NULL)
+    {
+      // Geometry is not available yet
+      return new OrthancStone::IVolumeSlicer::InvalidSlice;
+    }
+    else
+    {
+      return new Slice(*content_, revision_, cuttingPlane, structureVisibility_);
+    }
+  }
+
+  void DicomStructureSetLoader::SetStructuresReady()
+  {
+    ORTHANC_ASSERT(!structuresReady_);
+    structuresReady_ = true;
+    BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this));
+  }
+
+  bool DicomStructureSetLoader::AreStructuresReady() const
+  {
+    return structuresReady_;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader.h	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,100 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Toolbox/DicomStructureSet.h"
+#include "../../Volumes/IVolumeSlicer.h"
+#include "LoaderStateMachine.h"
+
+#include <vector>
+
+namespace Deprecated
+{
+  class DicomStructureSetLoader :
+    public LoaderStateMachine,
+    public OrthancStone::IVolumeSlicer,
+    public OrthancStone::IObservable
+  {
+  private:
+    class Slice;
+
+    // States of LoaderStateMachine
+    class AddReferencedInstance;   // 3rd state
+    class LookupInstance;          // 2nd state
+    class LoadStructure;           // 1st state
+    
+    std::auto_ptr<OrthancStone::DicomStructureSet>  content_;
+    uint64_t                          revision_;
+    std::string                       instanceId_;
+    unsigned int                      countProcessedInstances_;
+    unsigned int                      countReferencedInstances_;  
+
+    // will be set to true once the loading is finished
+    bool                              structuresReady_;
+
+    /**
+    At load time, these strings are used to initialize the structureVisibility_ 
+    vector.
+
+    As a special case, if initiallyVisibleStructures_ contains a single string
+    that is '*', ALL structures will be made visible.
+    */
+    std::vector<std::string> initiallyVisibleStructures_;
+
+    /**
+    Contains the "Should this structure be displayed?" flag for all structures.
+    Only filled when structures are loaded.
+
+    Changing this value directly affects the rendering
+    */
+    std::vector<bool>                  structureVisibility_;
+
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader);
+
+    DicomStructureSetLoader(OrthancStone::IOracle& oracle,
+                            OrthancStone::IObservable& oracleObservable);    
+    
+    OrthancStone::DicomStructureSet* GetContent()
+    {
+      return content_.get();
+    }
+
+    void SetStructureDisplayState(size_t structureIndex, bool display);
+    
+    bool GetStructureDisplayState(size_t structureIndex) const
+    {
+      return structureVisibility_.at(structureIndex);
+    }
+
+    ~DicomStructureSetLoader();
+    
+    void LoadInstance(const std::string& instanceId, 
+                      const std::vector<std::string>& initiallyVisibleStructures = std::vector<std::string>());
+
+    virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
+
+    void SetStructuresReady();
+
+    bool AreStructuresReady() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,125 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "DicomStructureSetLoader2.h"
+
+#include "../Messages/IObservable.h"
+#include "../Oracle/IOracle.h"
+#include "../Oracle/OracleCommandExceptionMessage.h"
+
+namespace Deprecated
+{
+
+  DicomStructureSetLoader2::DicomStructureSetLoader2(
+    DicomStructureSet2& structureSet
+    , IOracle& oracle
+    , IObservable& oracleObservable)
+    : IObserver(oracleObservable.GetBroker())
+    , IObservable(oracleObservable.GetBroker())
+    , structureSet_(structureSet)
+    , oracle_(oracle)
+    , oracleObservable_(oracleObservable)
+    , structuresReady_(false)
+  {
+    LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()";
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<DicomStructureSetLoader2, OrthancRestApiCommand::SuccessMessage>
+      (*this, &DicomStructureSetLoader2::HandleSuccessMessage));
+
+    oracleObservable.RegisterObserverCallback(
+      new Callable<DicomStructureSetLoader2, OracleCommandExceptionMessage>
+      (*this, &DicomStructureSetLoader2::HandleExceptionMessage));
+  }
+
+  DicomStructureSetLoader2::~DicomStructureSetLoader2()
+  {
+    LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()";
+    oracleObservable_.Unregister(this);
+  }
+
+  void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body)
+  {
+    OrthancPlugins::FullOrthancDataset dicom(body);
+    //loader.content_.reset(new DicomStructureSet(dicom));
+    structureSet_.Clear();
+    structureSet_.SetContents(dicom);
+    SetStructuresReady();
+  }
+
+  void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message)
+  {
+    const std::string& body = message.GetAnswer();
+    LoadInstanceFromString(body);
+  }
+
+  void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
+  {
+    LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. "
+      << "Error: " << message.GetException().What() << " Details: "
+      << message.GetException().GetDetails();
+  }
+
+  void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId)
+  {
+    std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
+    command->SetHttpHeader("Accept-Encoding", "gzip");
+
+    std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
+
+    command->SetUri(uri);
+    oracle_.Schedule(*this, command.release());
+  }
+
+  void DicomStructureSetLoader2::SetStructuresReady()
+  {
+    structuresReady_ = true;
+  }
+
+  bool DicomStructureSetLoader2::AreStructuresReady() const
+  {
+    return structuresReady_;
+  }
+
+  /*
+
+    void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
+    {
+      LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing";
+      LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " <<
+        message.GetException().GetDetails();
+        Clear();
+    }
+
+    LoaderStateMachine::~LoaderStateMachine()
+    {
+      Clear();
+    }
+
+
+  */
+
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader2.h	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,87 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You 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
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "../Toolbox/DicomStructureSet2.h"
+#include "../Messages/IMessage.h"
+#include "../Messages/IObserver.h"
+#include "../Messages/IObservable.h"
+#include "../Oracle/OrthancRestApiCommand.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Deprecated
+{
+  class IOracle;
+  class IObservable;
+  class OrthancRestApiCommand;
+  class OracleCommandExceptionMessage;
+
+  class DicomStructureSetLoader2 : public IObserver, public IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2);
+
+    /**
+    Warning: the structureSet, oracle and oracleObservable objects must live
+    at least as long as this object (TODO: shared_ptr?)
+    */
+    DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable);
+
+    ~DicomStructureSetLoader2();
+
+    void LoadInstance(const std::string& instanceId);
+
+    /** Internal use */
+    void LoadInstanceFromString(const std::string& body);
+
+    void SetStructuresReady();
+    bool AreStructuresReady() const;
+  
+  private:
+    /**
+    Called back by the oracle when data is ready!
+    */
+    void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message);
+
+    /**
+    Called back by the oracle when shit hits the fan
+    */
+    void HandleExceptionMessage(const OracleCommandExceptionMessage& message);
+
+    /**
+    The structure set that will be (cleared and) filled with data from the 
+    loader
+    */
+    DicomStructureSet2& structureSet_;
+
+    IOracle&            oracle_;
+    IObservable&        oracleObservable_;
+    bool                structuresReady_;
+  };
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/LoaderCache.cpp	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,412 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You 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 "LoaderCache.h"
+
+#include "../../StoneException.h"
+#include "OrthancSeriesVolumeProgressiveLoader.h"
+#include "OrthancMultiframeVolumeLoader.h"
+#include "DicomStructureSetLoader.h"
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+#include "DicomStructureSetLoader2.h"
+#endif 
+ //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
+#if ORTHANC_ENABLE_WASM == 1
+# include <unistd.h>
+# include "../../Oracle/WebAssemblyOracle.h"
+#else
+# include "../../Oracle/ThreadedOracle.h"
+#endif
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+#include "../../Toolbox/DicomStructureSet2.h"
+#endif 
+//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "../../Volumes/DicomVolumeImage.h"
+#include "../../Volumes/DicomVolumeImageMPRSlicer.h"
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+#include "../../Volumes/DicomStructureSetSlicer2.h"
+#endif 
+//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace Deprecated
+{
+#if ORTHANC_ENABLE_WASM == 1
+  LoaderCache::LoaderCache(OrthancStone::WebAssemblyOracle& oracle)
+    : oracle_(oracle)
+  {
+
+  }
+#else
+  LoaderCache::LoaderCache(OrthancStone::ThreadedOracle& oracle,
+                           OrthancStone::Deprecated::LockingEmitter& lockingEmitter)
+    : oracle_(oracle)
+    , lockingEmitter_(lockingEmitter)
+  {
+  }
+#endif
+
+  boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> 
+    LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid)
+  {
+    try
+    {
+      
+      // normalize keys a little
+      seriesUuid = Orthanc::Toolbox::StripSpaces(seriesUuid);
+      Orthanc::Toolbox::ToLowerCase(seriesUuid);
+
+      // find in cache
+      if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end())
+      {
+//        LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : CACHEMISS --> need to load seriesUUid = " << seriesUuid;
+#if ORTHANC_ENABLE_WASM == 1
+//        LOG(TRACE) << "Performing request for series " << seriesUuid << " sbrk(0) = " << sbrk(0);
+#else
+//        LOG(TRACE) << "Performing request for series " << seriesUuid;
+#endif
+        boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage);
+        boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader;
+//        LOG(TRACE) << "volumeImage = " << volumeImage.get();
+        {
+#if ORTHANC_ENABLE_WASM == 1
+          loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_));
+#else
+          OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
+          loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable()));
+#endif
+//          LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get();
+          loader->LoadSeries(seriesUuid);
+//          LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader->LoadSeries successful";
+        }
+        seriesVolumeProgressiveLoaders_[seriesUuid] = loader;
+      }
+      else
+      {
+//        LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid;
+      }
+      return seriesVolumeProgressiveLoaders_[seriesUuid];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in LoaderCache";
+      throw;
+    }
+  }
+
+  boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid)
+  {
+    // if the loader is not available, let's trigger its creation
+    if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end())
+    {
+      GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid);
+    }
+    ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end());
+
+    return multiframeVolumeLoaders_[instanceUuid];
+  }
+
+  boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid)
+  {
+    try
+    {
+      // normalize keys a little
+      instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid);
+      Orthanc::Toolbox::ToLowerCase(instanceUuid);
+
+      // find in cache
+      if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end())
+      {
+        boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage);
+        boost::shared_ptr<OrthancMultiframeVolumeLoader> loader;
+
+        {
+#if ORTHANC_ENABLE_WASM == 1
+          loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, oracle_));
+#else
+          OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
+          loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, lock.GetOracleObservable()));
+#endif
+          loader->LoadInstance(instanceUuid);
+        }
+        multiframeVolumeLoaders_[instanceUuid] = loader;
+        boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage));
+        dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer;
+      }
+      return dicomVolumeImageMPRSlicers_[instanceUuid];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in LoaderCache";
+      throw;
+    }
+  }
+  
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+  boost::shared_ptr<DicomStructureSetSlicer2> LoaderCache::GetDicomStructureSetSlicer2(std::string instanceUuid)
+  {
+    // if the loader is not available, let's trigger its creation
+    if (dicomStructureSetSlicers2_.find(instanceUuid) == dicomStructureSetSlicers2_.end())
+    {
+      GetDicomStructureSetLoader2(instanceUuid);
+    }
+    ORTHANC_ASSERT(dicomStructureSetSlicers2_.find(instanceUuid) != dicomStructureSetSlicers2_.end());
+
+    return dicomStructureSetSlicers2_[instanceUuid];
+  }
+#endif
+//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
+  /**
+  This method allows to convert a list of string into a string by 
+  sorting the strings then joining them
+  */
+  static std::string SortAndJoin(const std::vector<std::string>& stringList)
+  {
+    if (stringList.size() == 0)
+    {
+      return "";
+    } 
+    else
+    {
+      std::vector<std::string> sortedStringList = stringList;
+      std::sort(sortedStringList.begin(), sortedStringList.end());
+      std::stringstream s;
+      s << sortedStringList[0];
+      for (size_t i = 1; i < sortedStringList.size(); ++i)
+      {
+        s << "-" << sortedStringList[i];
+      }
+      return s.str();
+    }
+  }
+  
+  boost::shared_ptr<DicomStructureSetLoader> 
+    LoaderCache::GetDicomStructureSetLoader(
+      std::string inInstanceUuid, 
+      const std::vector<std::string>& initiallyVisibleStructures)
+  {
+    try
+    {
+      // normalize keys a little
+      inInstanceUuid = Orthanc::Toolbox::StripSpaces(inInstanceUuid);
+      Orthanc::Toolbox::ToLowerCase(inInstanceUuid);
+
+      std::string initiallyVisibleStructuresKey = 
+        SortAndJoin(initiallyVisibleStructures);
+
+      std::string entryKey = inInstanceUuid + "_" + initiallyVisibleStructuresKey;
+
+      // find in cache
+      if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end())
+      {
+        boost::shared_ptr<DicomStructureSetLoader> loader;
+
+        {
+#if ORTHANC_ENABLE_WASM == 1
+          loader.reset(new DicomStructureSetLoader(oracle_, oracle_));
+#else
+          OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
+          loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable()));
+#endif
+          loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures);
+        }
+        dicomStructureSetLoaders_[entryKey] = loader;
+      }
+      return dicomStructureSetLoaders_[entryKey];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in LoaderCache";
+      throw;
+    }
+  }
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+  boost::shared_ptr<DicomStructureSetLoader2> LoaderCache::GetDicomStructureSetLoader2(std::string instanceUuid)
+  {
+    try
+    {
+      // normalize keys a little
+      instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid);
+      Orthanc::Toolbox::ToLowerCase(instanceUuid);
+
+      // find in cache
+      if (dicomStructureSetLoaders2_.find(instanceUuid) == dicomStructureSetLoaders2_.end())
+      {
+        boost::shared_ptr<DicomStructureSetLoader2> loader;
+        boost::shared_ptr<DicomStructureSet2> structureSet(new DicomStructureSet2());
+        boost::shared_ptr<DicomStructureSetSlicer2> rtSlicer(new DicomStructureSetSlicer2(structureSet));
+        dicomStructureSetSlicers2_[instanceUuid] = rtSlicer;
+        dicomStructureSets2_[instanceUuid] = structureSet; // to prevent it from being deleted
+        {
+#if ORTHANC_ENABLE_WASM == 1
+          loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, oracle_));
+#else
+          LockingEmitter::WriterLock lock(lockingEmitter_);
+          // TODO: clarify lifetimes... this is DANGEROUS!
+          loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, lock.GetOracleObservable()));
+#endif
+          loader->LoadInstance(instanceUuid);
+        }
+        dicomStructureSetLoaders2_[instanceUuid] = loader;
+      }
+      return dicomStructureSetLoaders2_[instanceUuid];
+    }
+    catch (const Orthanc::OrthancException& e)
+    {
+      if (e.HasDetails())
+      {
+        LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What() << " Details: " << e.GetDetails();
+      }
+      else
+      {
+        LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What();
+      }
+      throw;
+    }
+    catch (const std::exception& e)
+    {
+      LOG(ERROR) << "std::exception in GetDicomStructureSetLoader2: " << e.what();
+      throw;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Unknown exception in GetDicomStructureSetLoader2";
+      throw;
+    }
+  }
+
+#endif
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+
+  void LoaderCache::ClearCache()
+  {
+#if ORTHANC_ENABLE_WASM != 1
+    OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
+#endif
+    
+//#ifndef NDEBUG
+    // ISO way of checking for debug builds
+    DebugDisplayObjRefCounts();
+//#endif
+    seriesVolumeProgressiveLoaders_.clear();
+    multiframeVolumeLoaders_.clear();
+    dicomVolumeImageMPRSlicers_.clear();
+    dicomStructureSetLoaders_.clear();
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+    // order is important!
+    dicomStructureSetLoaders2_.clear();
+    dicomStructureSetSlicers2_.clear();
+    dicomStructureSets2_.clear();
+#endif
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+  }
+
+  template<typename T> void DebugDisplayObjRefCountsInMap(
+    const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap)
+  {
+    LOG(TRACE) << "Map \"" << name << "\" ref counts:";
+    size_t i = 0;
+    for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator 
+           it = myMap.begin(); it != myMap.end(); ++it)
+    {
+      LOG(TRACE) << "  element #" << i << ": ref count = " << it->second.use_count();
+      i++;
+    }
+  }
+
+  void LoaderCache::DebugDisplayObjRefCounts()
+  {
+    DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_);
+    DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_);
+    DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_);
+    DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_);
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+    DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders2_", dicomStructureSetLoaders2_);
+    DebugDisplayObjRefCountsInMap("dicomStructureSetSlicers2_", dicomStructureSetSlicers2_);
+#endif
+//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/LoaderCache.h	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,109 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../../Messages/LockingEmitter.h"
+#include "../../Volumes/DicomVolumeImageMPRSlicer.h"
+#include "OrthancSeriesVolumeProgressiveLoader.h"
+#include "OrthancMultiframeVolumeLoader.h"
+#include "DicomStructureSetLoader.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace OrthancStone
+{
+#if ORTHANC_ENABLE_WASM == 1
+  class WebAssemblyOracle;
+#else
+  class ThreadedOracle;
+#endif
+}
+
+namespace Deprecated
+{
+  class LoaderCache
+  {
+  public:
+#if ORTHANC_ENABLE_WASM == 1
+    LoaderCache(OrthancStone::WebAssemblyOracle& oracle);
+#else
+    LoaderCache(OrthancStone::ThreadedOracle& oracle, OrthancStone::Deprecated::LockingEmitter& lockingEmitter);
+#endif
+
+    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader>
+      GetSeriesVolumeProgressiveLoader      (std::string seriesUuid);
+    
+    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer>
+      GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid);
+
+    boost::shared_ptr<OrthancMultiframeVolumeLoader>
+      GetMultiframeVolumeLoader(std::string instanceUuid);
+
+    boost::shared_ptr<DicomStructureSetLoader>
+      GetDicomStructureSetLoader(
+        std::string instanceUuid,
+        const std::vector<std::string>& initiallyVisibleStructures);
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+    boost::shared_ptr<DicomStructureSetLoader2>
+      GetDicomStructureSetLoader2(std::string instanceUuid);
+
+    boost::shared_ptr<DicomStructureSetSlicer2>
+      GetDicomStructureSetSlicer2(std::string instanceUuid);
+#endif 
+    //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+    void ClearCache();
+
+  private:
+    
+    void DebugDisplayObjRefCounts();
+#if ORTHANC_ENABLE_WASM == 1
+    OrthancStone::WebAssemblyOracle& oracle_;
+#else
+    OrthancStone::ThreadedOracle& oracle_;
+    OrthancStone::Deprecated::LockingEmitter& lockingEmitter_;
+#endif
+
+    std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> >
+      seriesVolumeProgressiveLoaders_;
+    std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> >
+      multiframeVolumeLoaders_;
+    std::map<std::string, boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> >
+      dicomVolumeImageMPRSlicers_;
+    std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> >
+      dicomStructureSetLoaders_;
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+    std::map<std::string, boost::shared_ptr<DicomStructureSetLoader2> >
+      dicomStructureSetLoaders2_;
+    std::map<std::string, boost::shared_ptr<DicomStructureSet2> >
+      dicomStructureSets2_;
+    std::map<std::string, boost::shared_ptr<DicomStructureSetSlicer2> >
+      dicomStructureSetSlicers2_;
+#endif 
+    //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/LoaderStateMachine.cpp	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,198 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LoaderStateMachine.h"
+
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  void LoaderStateMachine::State::Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+      
+
+  void LoaderStateMachine::State::Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+      
+  void LoaderStateMachine::State::Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+
+  void LoaderStateMachine::Schedule(OrthancStone::OracleCommandBase* command)
+  {
+    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()";
+
+    std::auto_ptr<OrthancStone::OracleCommandBase> protection(command);
+
+    if (command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+      
+    if (!command->HasPayload())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "The payload must contain the next state");
+    }
+    pendingCommands_.push_back(protection.release());
+
+    Step();
+  }
+
+
+  void LoaderStateMachine::Start()
+  {
+    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Start()";
+
+    if (active_)
+    {
+      LOG(TRACE) << "LoaderStateMachine::Start() called while active_ is true";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    active_ = true;
+
+    for (size_t i = 0; i < simultaneousDownloads_; i++)
+    {
+      Step();
+    }
+  }
+
+
+  void LoaderStateMachine::Step()
+  {
+    if (!pendingCommands_.empty() &&
+        activeCommands_ < simultaneousDownloads_)
+    {
+
+      OrthancStone::IOracleCommand* nextCommand = pendingCommands_.front();
+
+      LOG(TRACE) << "    LoaderStateMachine(" << std::hex << this << std::dec << 
+        ")::Step(): activeCommands_ (" << activeCommands_ << 
+        ") < simultaneousDownloads_ (" << simultaneousDownloads_ << 
+        ") --> will Schedule command addr " << std::hex << nextCommand << std::dec;
+
+      boost::shared_ptr<IObserver> observer(GetSharedObserver());
+      oracle_.Schedule(observer, nextCommand);
+      pendingCommands_.pop_front();
+
+      activeCommands_++;
+    }
+    else
+    {
+      LOG(TRACE) << "    LoaderStateMachine(" << std::hex << this << std::dec << 
+        ")::Step(): activeCommands_ (" << activeCommands_ << 
+        ") >= simultaneousDownloads_ (" << simultaneousDownloads_ << 
+        ") --> will NOT Schedule command";
+    }
+  }
+
+
+  void LoaderStateMachine::Clear()
+  {
+    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Clear()";
+    for (PendingCommands::iterator it = pendingCommands_.begin();
+         it != pendingCommands_.end(); ++it)
+    {
+      delete *it;
+    }
+
+    pendingCommands_.clear();
+  }
+  
+
+  void LoaderStateMachine::HandleExceptionMessage(const OrthancStone::OracleCommandExceptionMessage& message)
+  {
+    LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing";
+    LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " <<
+      message.GetException().GetDetails();
+      Clear();
+  }
+
+  template <typename T>
+  void LoaderStateMachine::HandleSuccessMessage(const T& message)
+  {
+    if (activeCommands_ <= 0) {
+      LOG(ERROR) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage : activeCommands_ should be > 0 but is: " << activeCommands_;
+    }
+    else {
+      activeCommands_--;
+      try
+      {
+        dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message);
+        Step();
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Error in the state machine, stopping all processing: " <<
+          e.What() << " Details: " << e.GetDetails();
+        Clear();
+      }
+    }
+  }
+
+
+  LoaderStateMachine::LoaderStateMachine(OrthancStone::IOracle& oracle,
+                                         OrthancStone::IObservable& oracleObservable) :
+    oracle_(oracle),
+    active_(false),
+    simultaneousDownloads_(4),
+    activeCommands_(0)
+  {
+    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()";
+
+    // TODO => Move this out of constructor
+    Register<OrthancStone::OrthancRestApiCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
+    Register<OrthancStone::GetOrthancImageCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
+    Register<OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
+    Register<OrthancStone::OracleCommandExceptionMessage>(oracleObservable, &LoaderStateMachine::HandleExceptionMessage);
+  }
+
+  LoaderStateMachine::~LoaderStateMachine()
+  {
+    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()";
+    Clear();
+  }
+
+  void LoaderStateMachine::SetSimultaneousDownloads(unsigned int count)
+  {
+    if (active_)
+    {
+      LOG(ERROR) << "LoaderStateMachine::SetSimultaneousDownloads called while active_ is true";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (count == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
+    }
+    else
+    {
+      simultaneousDownloads_ = count;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/LoaderStateMachine.h	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,116 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Messages/IObservable.h"
+#include "../../Messages/ObserverBase.h"
+#include "../../Oracle/GetOrthancImageCommand.h"
+#include "../../Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Oracle/IOracle.h"
+#include "../../Oracle/OracleCommandExceptionMessage.h"
+#include "../../Oracle/OrthancRestApiCommand.h"
+
+#include <Core/IDynamicObject.h>
+
+#include <list>
+
+namespace Deprecated
+{
+  /**
+     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 OrthancStone::ObserverBase<LoaderStateMachine>
+  {
+  protected:
+    class State : public Orthanc::IDynamicObject
+    {
+    private:
+      LoaderStateMachine&  that_;
+
+    public:
+      State(LoaderStateMachine& that) :
+      that_(that)
+      {
+      }
+
+      State(const State& currentState) :
+      that_(currentState.that_)
+      {
+      }
+
+      void Schedule(OrthancStone::OracleCommandBase* command) const
+      {
+        that_.Schedule(command);
+      }
+
+      template <typename T>
+      T& GetLoader() const
+      {
+        return dynamic_cast<T&>(that_);
+      }
+      
+      virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message);
+      
+      virtual void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message);
+      
+      virtual void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message);
+    };
+
+    void Schedule(OrthancStone::OracleCommandBase* command);
+
+    void Start();
+
+  private:
+    void Step();
+
+    void Clear();
+
+    void HandleExceptionMessage(const OrthancStone::OracleCommandExceptionMessage& message);
+
+    template <typename T>
+    void HandleSuccessMessage(const T& message);
+
+    typedef std::list<OrthancStone::IOracleCommand*>  PendingCommands;
+
+    OrthancStone::IOracle&         oracle_;
+    bool             active_;
+    unsigned int     simultaneousDownloads_;
+    PendingCommands  pendingCommands_;
+    unsigned int     activeCommands_;
+
+  public:
+    LoaderStateMachine(OrthancStone::IOracle& oracle,
+                       OrthancStone::IObservable& oracleObservable);
+
+    virtual ~LoaderStateMachine();
+
+    bool IsActive() const
+    {
+      return active_;
+    }
+
+    void SetSimultaneousDownloads(unsigned int count);  
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,383 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancMultiframeVolumeLoader.h"
+
+#include <Core/Endianness.h>
+#include <Core/Toolbox.h>
+
+namespace Deprecated
+{
+  class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State
+  {
+  private:
+    std::auto_ptr<Orthanc::DicomMap>  dicom_;
+
+  public:
+    LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
+                       Orthanc::DicomMap* dicom) :
+      State(that),
+      dicom_(dicom)
+    {
+      if (dicom == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+    }
+
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+      // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
+      std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
+      dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
+
+      GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
+    }      
+  };
+
+
+  static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
+  {
+    std::string s;
+    if (!dicom.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "DICOM file without SOP class UID");
+    }
+    else
+    {
+      return s;
+    }
+  }
+    
+
+  class OrthancMultiframeVolumeLoader::LoadGeometry : public State
+  {
+  public:
+    LoadGeometry(OrthancMultiframeVolumeLoader& that) :
+    State(that)
+    {
+    }
+      
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+      OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
+        
+      Json::Value body;
+      message.ParseJsonBody(body);
+        
+      if (body.type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
+      dicom->FromDicomAsJson(body);
+
+      if (OrthancStone::StringToSopClassUid(GetSopClassUid(*dicom)) == OrthancStone::SopClassUid_RTDose)
+      {
+        // Download the "Grid Frame Offset Vector" DICOM tag, that is
+        // mandatory for RT-DOSE, but is too long to be returned by default
+          
+        std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+        command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
+                        Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
+        command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release()));
+
+        Schedule(command.release());
+      }
+      else
+      {
+        loader.SetGeometry(*dicom);
+      }
+    }
+  };
+
+  class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State
+  {
+  public:
+    LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
+    State(that)
+    {
+    }
+      
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+      GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
+    }
+  };
+
+  class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State
+  {
+  public:
+    LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
+    State(that)
+    {
+    }
+      
+    virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
+    {
+      GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
+    }
+  };
+
+  const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const
+  {
+    if (IsActive())
+    {
+      return instanceId_;
+    }
+    else
+    {
+      LOG(ERROR) << "OrthancMultiframeVolumeLoader::GetInstanceId(): (!IsActive())";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads()
+  {
+    if (transferSyntaxUid_.empty() ||
+        !volume_->HasGeometry())
+    {
+      return;
+    }
+    /*
+      1.2.840.10008.1.2	Implicit VR Endian: Default Transfer Syntax for DICOM
+      1.2.840.10008.1.2.1	Explicit VR Little Endian
+      1.2.840.10008.1.2.2	Explicit VR Big Endian
+
+      See https://www.dicomlibrary.com/dicom/transfer-syntax/
+    */
+    if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
+        transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
+        transferSyntaxUid_ == "1.2.840.10008.1.2.2")
+    {
+      std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetUri("/instances/" + instanceId_ + "/content/" +
+                      Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
+      command->AcquirePayload(new LoadUncompressedPixelData(*this));
+      Schedule(command.release());
+    }
+    else
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_NotImplemented,
+        "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
+    }
+  }
+
+  void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax)
+  {
+    transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
+    ScheduleFrameDownloads();
+  }
+
+  void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom)
+  {
+    OrthancStone::DicomInstanceParameters parameters(dicom);
+    volume_->SetDicomParameters(parameters);
+      
+    Orthanc::PixelFormat format;
+    if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    double spacingZ;
+    switch (parameters.GetSopClassUid())
+    {
+      case OrthancStone::SopClassUid_RTDose:
+        spacingZ = parameters.GetThickness();
+        break;
+
+      default:
+        throw Orthanc::OrthancException(
+          Orthanc::ErrorCode_NotImplemented,
+          "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
+    }
+
+    const unsigned int width = parameters.GetImageInformation().GetWidth();
+    const unsigned int height = parameters.GetImageInformation().GetHeight();
+    const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
+
+    {
+      OrthancStone::VolumeImageGeometry geometry;
+      geometry.SetSizeInVoxels(width, height, depth);
+      geometry.SetAxialGeometry(parameters.GetGeometry());
+      geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
+                                  parameters.GetPixelSpacingY(), spacingZ);
+      volume_->Initialize(geometry, format, true /* Do compute range */);
+    }
+
+    volume_->GetPixelData().Clear();
+
+    ScheduleFrameDownloads();
+
+
+
+    BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_));
+  }
+
+
+  ORTHANC_FORCE_INLINE
+  static void CopyPixel(uint32_t& target, const void* source)
+  {
+    // TODO - check alignement?
+    target = le32toh(*reinterpret_cast<const uint32_t*>(source));
+  }
+
+  ORTHANC_FORCE_INLINE
+    static void CopyPixel(uint16_t& target, const void* source)
+  {
+    // TODO - check alignement?
+    target = le16toh(*reinterpret_cast<const uint16_t*>(source));
+  }
+
+  ORTHANC_FORCE_INLINE
+    static void CopyPixel(int16_t& target, const void* source)
+  {
+    // byte swapping is the same for unsigned and signed integers
+    // (the sign bit is always stored with the MSByte)
+    uint16_t* targetUp = reinterpret_cast<uint16_t*>(&target);
+    CopyPixel(*targetUp, source);
+  }
+
+  template <typename T>
+  void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData)
+  {
+    OrthancStone::ImageBuffer3D& target = volume_->GetPixelData();
+      
+    const unsigned int bpp = target.GetBytesPerPixel();
+    const unsigned int width = target.GetWidth();
+    const unsigned int height = target.GetHeight();
+    const unsigned int depth = target.GetDepth();
+
+    if (pixelData.size() != bpp * width * height * depth)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "The pixel data has not the proper size");
+    }
+
+    if (pixelData.empty())
+    {
+      return;
+    }
+
+    const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
+
+    for (unsigned int z = 0; z < depth; z++)
+    {
+      OrthancStone::ImageBuffer3D::SliceWriter writer(target, OrthancStone::VolumeProjection_Axial, z);
+
+      assert (writer.GetAccessor().GetWidth() == width &&
+              writer.GetAccessor().GetHeight() == height);
+
+      for (unsigned int y = 0; y < height; y++)
+      {
+        assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
+
+        T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
+
+        for (unsigned int x = 0; x < width; x++)
+        {
+          CopyPixel(*target, source);
+          target ++;
+          source += bpp;
+        }
+      }
+    }
+  }
+
+  void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData)
+  {
+    switch (volume_->GetPixelData().GetFormat())
+    {
+      case Orthanc::PixelFormat_Grayscale32:
+        CopyPixelData<uint32_t>(pixelData);
+        break;
+      case Orthanc::PixelFormat_Grayscale16:
+        CopyPixelData<uint16_t>(pixelData);
+        break;
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        CopyPixelData<int16_t>(pixelData);
+        break;
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    volume_->IncrementRevision();
+
+    pixelDataLoaded_ = true;
+    BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_));
+  }
+  
+  bool OrthancMultiframeVolumeLoader::HasGeometry() const
+  {
+    return volume_->HasGeometry();
+  }
+
+  const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const
+  {
+    return volume_->GetGeometry();
+  }
+
+  OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
+                                                               OrthancStone::IOracle& oracle,
+                                                               OrthancStone::IObservable& oracleObservable) :
+    LoaderStateMachine(oracle, oracleObservable),
+    volume_(volume),
+    pixelDataLoaded_(false)
+  {
+    if (volume.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+  OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()
+  {
+    LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()";
+  }
+
+  void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId)
+  {
+    Start();
+
+    instanceId_ = instanceId;
+
+    {
+      std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+      command->SetHttpHeader("Accept-Encoding", "gzip");
+      command->SetUri("/instances/" + instanceId + "/tags");
+      command->AcquirePayload(new LoadGeometry(*this));
+      Schedule(command.release());
+    }
+
+    {
+      std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+      command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
+      command->AcquirePayload(new LoadTransferSyntax(*this));
+      Schedule(command.release());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.h	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,76 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "LoaderStateMachine.h"
+#include "../../Volumes/DicomVolumeImage.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Deprecated
+{
+  class OrthancMultiframeVolumeLoader :
+    public LoaderStateMachine,
+    public OrthancStone::IObservable
+  {
+  private:
+    class LoadRTDoseGeometry;
+    class LoadGeometry;
+    class LoadTransferSyntax;    
+    class LoadUncompressedPixelData;
+
+    boost::shared_ptr<OrthancStone::DicomVolumeImage>  volume_;
+    std::string                          instanceId_;
+    std::string                          transferSyntaxUid_;
+    bool                                 pixelDataLoaded_;
+
+    const std::string& GetInstanceId() const;
+
+    void ScheduleFrameDownloads();
+
+    void SetTransferSyntax(const std::string& transferSyntax);
+
+    void SetGeometry(const Orthanc::DicomMap& dicom);
+
+    template <typename T>
+    void CopyPixelData(const std::string& pixelData);
+
+    void SetUncompressedPixelData(const std::string& pixelData);
+
+    bool HasGeometry() const;
+    const OrthancStone::VolumeImageGeometry& GetImageGeometry() const;
+
+  public:
+    OrthancMultiframeVolumeLoader(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
+                                  OrthancStone::IOracle& oracle,
+                                  OrthancStone::IObservable& oracleObservable);
+    
+    virtual ~OrthancMultiframeVolumeLoader();
+
+    bool IsPixelDataLoaded() const
+    {
+      return pixelDataLoaded_;
+    }
+
+    void LoadInstance(const std::string& instanceId);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,513 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancSeriesVolumeProgressiveLoader.h"
+
+#include "../../Loaders/BasicFetchingItemsSorter.h"
+#include "../../Loaders/BasicFetchingStrategy.h"
+#include "../../Toolbox/GeometryToolbox.h"
+#include "../../Volumes/DicomVolumeImageMPRSlicer.h"
+
+#include <Core/Images/ImageProcessing.h>
+#include <Core/OrthancException.h>
+
+namespace Deprecated
+{
+  class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public OrthancStone::DicomVolumeImageMPRSlicer::Slice
+  {
+  private:
+    const OrthancSeriesVolumeProgressiveLoader&  that_;
+
+  public:
+    ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
+                   const OrthancStone::CoordinateSystem3D& plane) :
+      OrthancStone::DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
+      that_(that)
+    {
+      if (IsValid())
+      {
+        if (GetProjection() == OrthancStone::VolumeProjection_Axial)
+        {
+          // For coronal and sagittal projections, we take the global
+          // revision of the volume because even if a single slice changes,
+          // this means the projection will yield a different result --> 
+          // we must increase the revision as soon as any slice changes 
+          SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex()));
+        }
+      
+        if (that_.strategy_.get() != NULL &&
+            GetProjection() == OrthancStone::VolumeProjection_Axial)
+        {
+          that_.strategy_->SetCurrent(GetSliceIndex());
+        }
+      }
+    }
+  };
+
+    
+    
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index,
+                                                                        const OrthancStone::DicomInstanceParameters& reference) const
+  {
+    const OrthancStone::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 OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckVolume() const
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(slices_[i] != NULL);
+      if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "This class does not support multi-frame images");
+      }
+    }
+
+    if (slices_.size() != 0)
+    {
+      const OrthancStone::DicomInstanceParameters& reference = *slices_[0];
+
+      for (size_t i = 1; i < slices_.size(); i++)
+      {
+        CheckSlice(i, reference);
+      }
+    }
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(slices_[i] != NULL);
+      delete slices_[i];
+    }
+
+    slices_.clear();
+    slicesRevision_.clear();
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const
+  {
+    if (!HasGeometry())
+    {
+      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index): (!HasGeometry())";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (index >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(slices_.size() == GetImageGeometry().GetDepth() &&
+             slices_.size() == slicesRevision_.size());
+    }
+  }
+
+
+  // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
+  // (called with the slices created in LoadGeometry)
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(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)
+    {
+      geometry_.reset(new OrthancStone::VolumeImageGeometry);
+    }
+    else
+    {
+      slices_.reserve(slices.GetSlicesCount());
+      slicesRevision_.resize(slices.GetSlicesCount(), 0);
+
+      for (size_t i = 0; i < slices.GetSlicesCount(); i++)
+      {
+        const OrthancStone::DicomInstanceParameters& slice =
+          dynamic_cast<const OrthancStone::DicomInstanceParameters&>(slices.GetSlicePayload(i));
+        slices_.push_back(new OrthancStone::DicomInstanceParameters(slice));
+      }
+
+      CheckVolume();
+
+      double spacingZ;
+
+      if (slices.ComputeSpacingBetweenSlices(spacingZ))
+      {
+        LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
+      
+        const OrthancStone::DicomInstanceParameters& parameters = *slices_[0];
+
+        geometry_.reset(new OrthancStone::VolumeImageGeometry);
+        geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(),
+                                   parameters.GetImageInformation().GetHeight(),
+                                   static_cast<unsigned int>(slices.GetSlicesCount()));
+        geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
+        geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
+                                      parameters.GetPixelSpacingY(), spacingZ);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "The origins of the slices of a volume image are not regularly spaced");
+     }
+    }
+  }
+
+
+  const OrthancStone::VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const
+  {
+    if (!HasGeometry())
+    {
+      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry(): (!HasGeometry())";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      assert(slices_.size() == geometry_->GetDepth());
+      return *geometry_;
+    }
+  }
+
+
+  const OrthancStone::DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const
+  {
+    CheckSliceIndex(index);
+    return *slices_[index];
+  }
+
+
+  uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const
+  {
+    CheckSliceIndex(index);
+    return slicesRevision_[index];
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index)
+  {
+    CheckSliceIndex(index);
+    slicesRevision_[index] ++;
+  }
+
+
+  static unsigned int GetSliceIndexPayload(const OrthancStone::OracleCommandBase& command)
+  {
+    assert(command.HasPayload());
+    return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload()
+  {
+    assert(strategy_.get() != NULL);
+      
+    unsigned int sliceIndex, quality;
+      
+    if (strategy_->GetNext(sliceIndex, quality))
+    {
+      assert(quality <= BEST_QUALITY);
+
+      const OrthancStone::DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
+          
+      const std::string& instance = slice.GetOrthancInstanceIdentifier();
+      if (instance.empty())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      std::auto_ptr<OrthancStone::OracleCommandBase> command;
+        
+      if (quality == BEST_QUALITY)
+      {
+        std::auto_ptr<OrthancStone::GetOrthancImageCommand> tmp(new OrthancStone::GetOrthancImageCommand);
+        // TODO: review the following comment. 
+        // - Commented out by bgo on 2019-07-19 | reason: Alain has seen cases 
+        //   where gzipping the uint16 image took 11 sec to produce 5mb. 
+        //   The unzipped request was much much faster.
+        // - Re-enabled on 2019-07-30. Reason: in Web Assembly, the browser 
+        //   does not use the Accept-Encoding header and always requests
+        //   compression. Furthermore, NOT 
+        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<OrthancStone::GetOrthancWebViewerJpegCommand> tmp(new OrthancStone::GetOrthancWebViewerJpegCommand);
+        // TODO: review the following comment. Commented out by bgo on 2019-07-19
+        // (gzip for jpeg seems overkill)
+        //tmp->SetHttpHeader("Accept-Encoding", "gzip");
+        tmp->SetInstance(instance);
+        tmp->SetQuality((quality == 0 ? 50 : 90));
+        tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
+        command.reset(tmp.release());
+      }
+
+      command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
+
+      boost::shared_ptr<IObserver> observer(GetSharedObserver());
+      oracle_.Schedule(observer, command.release());
+    }
+    else
+    {
+      // loading is finished!
+      volumeImageReadyInHighQuality_ = true;
+      BroadcastMessage(OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality(*this));
+    }
+  }
+
+/**
+   This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
+*/
+  void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancStone::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<OrthancStone::DicomInstanceParameters> instance(new OrthancStone::DicomInstanceParameters(dicom));
+        instance->SetOrthancInstanceIdentifier(instances[i]);
+
+        // the 3D plane corresponding to the slice
+        OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
+        slices.AddSlice(geometry, instance.release());
+      }
+
+      seriesGeometry_.ComputeGeometry(slices);
+    }
+
+    size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
+
+    if (slicesCount == 0)
+    {
+      volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
+    }
+    else
+    {
+      const OrthancStone::DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
+        
+      volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
+      volume_->SetDicomParameters(parameters);
+      volume_->GetPixelData().Clear();
+
+      strategy_.reset(new OrthancStone::BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY));
+        
+      assert(simultaneousDownloads_ != 0);
+      for (unsigned int i = 0; i < simultaneousDownloads_; i++)
+      {
+        ScheduleNextSliceDownload();
+      }
+    }
+
+    slicesQuality_.resize(slicesCount, 0);
+
+    BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_));
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex,
+                                                             const Orthanc::ImageAccessor& image,
+                                                             unsigned int quality)
+  {
+    assert(sliceIndex < slicesQuality_.size() &&
+           slicesQuality_.size() == volume_->GetPixelData().GetDepth());
+      
+    if (quality >= slicesQuality_[sliceIndex])
+    {
+      {
+        OrthancStone::ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), OrthancStone::VolumeProjection_Axial, sliceIndex);
+        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
+      }
+
+      volume_->IncrementRevision();
+      seriesGeometry_.IncrementSliceRevision(sliceIndex);
+      slicesQuality_[sliceIndex] = quality;
+
+      BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_));
+    }
+
+    ScheduleNextSliceDownload();
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
+  {
+    SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY);
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  {
+    unsigned int quality;
+      
+    switch (dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>(message.GetOrigin()).GetQuality())
+    {
+      case 50:
+        quality = LOW_QUALITY;
+        break;
+
+      case 90:
+        quality = MIDDLE_QUALITY;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+      
+    SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
+  }
+
+
+  OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<OrthancStone::DicomVolumeImage>& volume,
+                                                                             OrthancStone::IOracle& oracle,
+                                                                             OrthancStone::IObservable& oracleObservable) :
+    oracle_(oracle),
+    active_(false),
+    simultaneousDownloads_(4),
+    volume_(volume),
+    sorter_(new OrthancStone::BasicFetchingItemsSorter::Factory),
+    volumeImageReadyInHighQuality_(false)
+  {
+    // TODO => Move this out of constructor
+    Register<OrthancStone::OrthancRestApiCommand::SuccessMessage>
+      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry);
+
+    Register<OrthancStone::GetOrthancImageCommand::SuccessMessage>
+      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent);
+
+    Register<OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>
+      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent);
+  }
+
+  OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()
+  {
+    LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()";
+  }
+
+  void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count)
+  {
+    if (active_)
+    {
+      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(): (active_)";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (count == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
+    }
+    else
+    {
+      simultaneousDownloads_ = count;
+    }
+  }
+
+
+  void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId)
+  {
+//    LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries seriesId=" << seriesId;
+    if (active_)
+    {
+//      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries NOT ACTIVE! --> ERROR";
+      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId): (active_)";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      active_ = true;
+
+      std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+      command->SetUri("/series/" + seriesId + "/instances-tags");
+
+//      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule";
+      boost::shared_ptr<IObserver> observer(GetSharedObserver());
+      oracle_.Schedule(observer, command.release());
+//      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule";
+    }
+  }
+  
+
+  OrthancStone::IVolumeSlicer::IExtractedSlice* 
+  OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane)
+  {
+    if (volume_->HasGeometry())
+    {
+      return new ExtractedSlice(*this, cuttingPlane);
+    }
+    else
+    {
+      return new IVolumeSlicer::InvalidSlice;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Sun Dec 08 11:45:09 2019 +0100
@@ -0,0 +1,163 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You 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 "../../Loaders/IFetchingItemsSorter.h"
+#include "../../Loaders/IFetchingStrategy.h"
+#include "../../Messages/IObservable.h"
+#include "../../Messages/ObserverBase.h"
+#include "../../Oracle/GetOrthancImageCommand.h"
+#include "../../Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Oracle/IOracle.h"
+#include "../../Oracle/OrthancRestApiCommand.h"
+#include "../../Toolbox/SlicesSorter.h"
+#include "../../Volumes/DicomVolumeImage.h"
+#include "../../Volumes/IVolumeSlicer.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Deprecated
+{
+  /**
+    This class is used to manage the progressive loading of a volume that
+    is stored in a Dicom series.
+  */
+  class OrthancSeriesVolumeProgressiveLoader : 
+    public OrthancStone::ObserverBase<OrthancSeriesVolumeProgressiveLoader>,
+    public OrthancStone::IObservable,
+    public OrthancStone::IVolumeSlicer
+  {
+  private:
+    static const unsigned int LOW_QUALITY = 0;
+    static const unsigned int MIDDLE_QUALITY = 1;
+    static const unsigned int BEST_QUALITY = 2;
+    
+    class ExtractedSlice;
+    
+    /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */
+    class SeriesGeometry : public boost::noncopyable
+    {
+    private:
+      void CheckSlice(size_t index,
+                      const OrthancStone::DicomInstanceParameters& reference) const;
+    
+      void CheckVolume() const;
+
+      void Clear();
+
+      void CheckSliceIndex(size_t index) const;
+
+      std::auto_ptr<OrthancStone::VolumeImageGeometry>     geometry_;
+      std::vector<OrthancStone::DicomInstanceParameters*>  slices_;
+      std::vector<uint64_t>                  slicesRevision_;
+
+    public:
+      ~SeriesGeometry()
+      {
+        Clear();
+      }
+
+      void ComputeGeometry(OrthancStone::SlicesSorter& slices);
+
+      virtual bool HasGeometry() const
+      {
+        return geometry_.get() != NULL;
+      }
+
+      virtual const OrthancStone::VolumeImageGeometry& GetImageGeometry() const;
+
+      const OrthancStone::DicomInstanceParameters& GetSliceParameters(size_t index) const;
+
+      uint64_t GetSliceRevision(size_t index) const;
+
+      void IncrementSliceRevision(size_t index);
+    };
+
+    void ScheduleNextSliceDownload();
+
+    void LoadGeometry(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message);
+
+    void SetSliceContent(unsigned int sliceIndex,
+                         const Orthanc::ImageAccessor& image,
+                         unsigned int quality);
+
+    void LoadBestQualitySliceContent(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message);
+
+    void LoadJpegSliceContent(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message);
+
+    OrthancStone::IOracle&                                      oracle_;
+    bool                                          active_;
+    unsigned int                                  simultaneousDownloads_;
+    SeriesGeometry                                seriesGeometry_;
+    boost::shared_ptr<OrthancStone::DicomVolumeImage>           volume_;
+    std::auto_ptr<OrthancStone::IFetchingItemsSorter::IFactory> sorter_;
+    std::auto_ptr<OrthancStone::IFetchingStrategy>              strategy_;
+    std::vector<unsigned int>                     slicesQuality_;
+    bool                                          volumeImageReadyInHighQuality_;
+
+
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeImageReadyInHighQuality, OrthancSeriesVolumeProgressiveLoader);
+
+
+    OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<OrthancStone::DicomVolumeImage>& volume,
+                                         OrthancStone::IOracle& oracle,
+                                         OrthancStone::IObservable& oracleObservable);
+
+    virtual ~OrthancSeriesVolumeProgressiveLoader();
+
+    void SetSimultaneousDownloads(unsigned int count);
+
+    bool IsVolumeImageReadyInHighQuality() const
+    {
+      return volumeImageReadyInHighQuality_;
+    }
+
+    void LoadSeries(const std::string& seriesId);
+
+    /**
+    This getter is used by clients that do not receive the geometry through
+    subscribing, for instance if they are created or listening only AFTER the
+    "geometry loaded" message is broadcast 
+    */
+    bool HasGeometry() const
+    {
+      return seriesGeometry_.HasGeometry();
+    }
+
+    /**
+    Same remark as HasGeometry
+    */
+    const OrthancStone::VolumeImageGeometry& GetImageGeometry() const
+    {
+      return seriesGeometry_.GetImageGeometry();
+    }
+
+    /**
+    When a slice is requested, the strategy algorithm (that defines the 
+    sequence of resources to be loaded from the server) is modified to 
+    take into account this request (this is done in the ExtractedSlice ctor)
+    */
+    virtual IExtractedSlice*
+      ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
+  };
+}
--- a/Framework/Loaders/DicomStructureSetLoader.cpp	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,416 +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 "DicomStructureSetLoader.h"
-
-#include "../Scene2D/PolylineSceneLayer.h"
-#include "../StoneException.h"
-#include "../Toolbox/GeometryToolbox.h"
-
-#include <Core/Toolbox.h>
-
-#include <algorithm>
-
-#if 0
-bool logbgo233 = false;
-bool logbgo115 = false;
-#endif
-
-namespace OrthancStone
-{
-
-#if 0
-  void DumpDicomMap(std::ostream& o, const Orthanc::DicomMap& dicomMap)
-  {
-    using namespace std;
-    //ios_base::fmtflags state = o.flags();
-    //o.flags(ios::right | ios::hex);
-    //o << "(" << setfill('0') << setw(4) << tag.GetGroup()
-    //  << "," << setw(4) << tag.GetElement() << ")";
-    //o.flags(state);
-    Json::Value val;
-    dicomMap.Serialize(val);
-    o << val;
-    //return o;
-  }
-#endif
-
-
-  class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State
-  {
-  private:
-    std::string instanceId_;
-      
-  public:
-    AddReferencedInstance(DicomStructureSetLoader& that,
-                          const std::string& instanceId) :
-      State(that),
-      instanceId_(instanceId)
-    {
-    }
-
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      Json::Value tags;
-      message.ParseJsonBody(tags);
-        
-      Orthanc::DicomMap dicom;
-      dicom.FromDicomAsJson(tags);
-
-      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
-
-      loader.content_->AddReferencedSlice(dicom);
-
-      loader.countProcessedInstances_ ++;
-      assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);
-
-      if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
-      {
-        // All the referenced instances have been loaded, finalize the RT-STRUCT
-        loader.content_->CheckReferencedSlices();
-        loader.revision_++;
-        loader.SetStructuresReady();
-      }
-    }
-  };
-
-
-  // State that converts a "SOP Instance UID" to an Orthanc identifier
-  class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State
-  {
-  private:
-    std::string  sopInstanceUid_;
-      
-  public:
-    LookupInstance(DicomStructureSetLoader& that,
-                   const std::string& sopInstanceUid) :
-      State(that),
-      sopInstanceUid_(sopInstanceUid)
-    {
-    }
-
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-#if 0
-      LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)";
-#endif
-      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
-
-      Json::Value lookup;
-      message.ParseJsonBody(lookup);
-
-      if (lookup.type() != Json::arrayValue ||
-          lookup.size() != 1 ||
-          !lookup[0].isMember("Type") ||
-          !lookup[0].isMember("Path") ||
-          lookup[0]["Type"].type() != Json::stringValue ||
-          lookup[0]["ID"].type() != Json::stringValue ||
-          lookup[0]["Type"].asString() != "Instance")
-      {
-        std::stringstream msg;
-        msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = ";
-        for (OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin();
-             it != message.GetAnswerHeaders().end(); ++it)
-        {
-          msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n";
-        }
-        const std::string msgStr = msg.str();
-        LOG(ERROR) << msgStr;
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
-      }
-
-      const std::string instanceId = lookup[0]["ID"].asString();
-
-      {
-        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-        command->SetHttpHeader("Accept-Encoding", "gzip");
-        std::string uri = "/instances/" + instanceId + "/tags";
-        command->SetUri(uri);
-        command->AcquirePayload(new AddReferencedInstance(loader, instanceId));
-        Schedule(command.release());
-      }
-    }
-  };
-
-
-  class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State
-  {
-  public:
-    LoadStructure(DicomStructureSetLoader& that) :
-    State(that)
-    {
-    }
-    
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-#if 0
-      if (logbgo115)
-        LOG(TRACE) << "DicomStructureSetLoader::LoadStructure::Handle() (SUCCESS)";
-#endif
-      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
-        
-      {
-        OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
-        loader.content_.reset(new DicomStructureSet(dicom));
-        size_t structureCount = loader.content_->GetStructuresCount();
-        loader.structureVisibility_.resize(structureCount);
-        bool everythingVisible = false;
-        if ((loader.initiallyVisibleStructures_.size() == 1)
-          && (loader.initiallyVisibleStructures_[0].size() == 1)
-          && (loader.initiallyVisibleStructures_[0][0] == '*'))
-        {
-          everythingVisible = true;
-        }
-
-        for (size_t i = 0; i < structureCount; ++i)
-        {
-          // if a single "*" string is supplied, this means we want everything 
-          // to be visible...
-          if(everythingVisible)
-          {
-            loader.structureVisibility_.at(i) = true;
-          }
-          else
-          {
-            // otherwise, we only enable visibility for those structures whose 
-            // names are mentioned in the initiallyVisibleStructures_ array
-            const std::string& structureName = loader.content_->GetStructureName(i);
-
-            std::vector<std::string>::iterator foundIt =
-              std::find(
-                loader.initiallyVisibleStructures_.begin(),
-                loader.initiallyVisibleStructures_.end(),
-                structureName);
-            std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end();
-            if (foundIt != endIt)
-              loader.structureVisibility_.at(i) = true;
-            else
-              loader.structureVisibility_.at(i) = false;
-          }
-        }
-      }
-
-      // Some (admittedly invalid) Dicom files have empty values in the 
-      // 0008,1155 tag. We try our best to cope with this.
-      std::set<std::string> instances;
-      std::set<std::string> nonEmptyInstances;
-      loader.content_->GetReferencedInstances(instances);
-      for (std::set<std::string>::const_iterator
-        it = instances.begin(); it != instances.end(); ++it)
-      {
-        std::string instance = Orthanc::Toolbox::StripSpaces(*it);
-        if(instance != "")
-          nonEmptyInstances.insert(instance);
-      }
-
-      loader.countReferencedInstances_ = 
-        static_cast<unsigned int>(nonEmptyInstances.size());
-
-      for (std::set<std::string>::const_iterator
-        it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it)
-      {
-        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-        command->SetUri("/tools/lookup");
-        command->SetMethod(Orthanc::HttpMethod_Post);
-        command->SetBody(*it);
-        command->AcquirePayload(new LookupInstance(loader, *it));
-        Schedule(command.release());
-      }
-    }
-  };
-    
-
-  class DicomStructureSetLoader::Slice : public IExtractedSlice
-  {
-  private:
-    const DicomStructureSet&  content_;
-    uint64_t                  revision_;
-    bool                      isValid_;
-    std::vector<bool>         visibility_;
-      
-  public:
-    /**
-    The visibility vector must either:
-    - be empty
-    or
-    - contain the same number of items as the number of structures in the 
-      structure set.
-    In the first case (empty vector), all the structures are displayed.
-    In the second case, the visibility of each structure is defined by the 
-    content of the vector at the corresponding index.
-    */
-    Slice(const DicomStructureSet& content,
-          uint64_t revision,
-          const CoordinateSystem3D& cuttingPlane,
-          std::vector<bool> visibility = std::vector<bool>()) 
-      : content_(content)
-      , revision_(revision)
-      , visibility_(visibility)
-    {
-      ORTHANC_ASSERT((visibility_.size() == content_.GetStructuresCount())
-        || (visibility_.size() == 0u));
-
-      bool opposite;
-
-      const Vector normal = content.GetNormal();
-      isValid_ = (
-        GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
-        GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
-        GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
-    }
-      
-    virtual bool IsValid()
-    {
-      return isValid_;
-    }
-
-    virtual uint64_t GetRevision()
-    {
-      return revision_;
-    }
-
-    virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
-                                          const CoordinateSystem3D& cuttingPlane)
-    {
-      assert(isValid_);
-
-      std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
-      layer->SetThickness(2);
-
-      for (size_t i = 0; i < content_.GetStructuresCount(); i++)
-      {
-        if ((visibility_.size() == 0) || visibility_.at(i))
-        {
-          const Color& color = content_.GetStructureColor(i);
-
-#ifdef USE_BOOST_UNION_FOR_POLYGONS 
-          std::vector< std::vector<Point2D> > polygons;
-
-          if (content_.ProjectStructure(polygons, i, cuttingPlane))
-          {
-            for (size_t j = 0; j < polygons.size(); j++)
-            {
-              PolylineSceneLayer::Chain chain;
-              chain.resize(polygons[j].size());
-
-              for (size_t k = 0; k < polygons[j].size(); k++)
-              {
-                chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y);
-    }
-
-              layer->AddChain(chain, true /* closed */, color);
-  }
-        }
-#else
-          std::vector< std::pair<Point2D, Point2D> > segments;
-
-          if (content_.ProjectStructure(segments, i, cuttingPlane))
-          {
-            for (size_t j = 0; j < segments.size(); j++)
-            {
-              PolylineSceneLayer::Chain chain;
-              chain.resize(2);
-
-              chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y);
-              chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y);
-
-              layer->AddChain(chain, false /* NOT closed */, color);
-            }
-          }
-#endif        
-        }
-      }
-
-      return layer.release();
-    }
-  };
-    
-
-  DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle,
-                                                   IObservable& oracleObservable) :
-    LoaderStateMachine(oracle, oracleObservable),
-    revision_(0),
-    countProcessedInstances_(0),
-    countReferencedInstances_(0),
-    structuresReady_(false)
-  {
-  }
-    
-    
-  void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
-  {
-    structureVisibility_.at(structureIndex) = display;
-    revision_++;
-  }
-
-  DicomStructureSetLoader::~DicomStructureSetLoader()
-  {
-    LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()";
-  }
-
-  void DicomStructureSetLoader::LoadInstance(
-    const std::string& instanceId, 
-    const std::vector<std::string>& initiallyVisibleStructures)
-  {
-    Start();
-      
-    instanceId_ = instanceId;
-    initiallyVisibleStructures_ = initiallyVisibleStructures;
-
-    {
-      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-      command->SetHttpHeader("Accept-Encoding", "gzip");
-
-      std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
-
-      command->SetUri(uri);
-      command->AcquirePayload(new LoadStructure(*this));
-      Schedule(command.release());
-    }
-  }
-
-
-  IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
-  {
-    if (content_.get() == NULL)
-    {
-      // Geometry is not available yet
-      return new IVolumeSlicer::InvalidSlice;
-    }
-    else
-    {
-      return new Slice(*content_, revision_, cuttingPlane, structureVisibility_);
-    }
-  }
-
-  void DicomStructureSetLoader::SetStructuresReady()
-  {
-    ORTHANC_ASSERT(!structuresReady_);
-    structuresReady_ = true;
-    BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this));
-  }
-
-  bool DicomStructureSetLoader::AreStructuresReady() const
-  {
-    return structuresReady_;
-  }
-
-}
--- a/Framework/Loaders/DicomStructureSetLoader.h	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Toolbox/DicomStructureSet.h"
-#include "../Volumes/IVolumeSlicer.h"
-#include "LoaderStateMachine.h"
-
-#include <vector>
-
-namespace OrthancStone
-{
-  class DicomStructureSetLoader :
-    public LoaderStateMachine,
-    public IVolumeSlicer,
-    public IObservable
-  {
-  private:
-    class Slice;
-
-    // States of LoaderStateMachine
-    class AddReferencedInstance;   // 3rd state
-    class LookupInstance;          // 2nd state
-    class LoadStructure;           // 1st state
-    
-    std::auto_ptr<DicomStructureSet>  content_;
-    uint64_t                          revision_;
-    std::string                       instanceId_;
-    unsigned int                      countProcessedInstances_;
-    unsigned int                      countReferencedInstances_;  
-
-    // will be set to true once the loading is finished
-    bool                              structuresReady_;
-
-    /**
-    At load time, these strings are used to initialize the structureVisibility_ 
-    vector.
-
-    As a special case, if initiallyVisibleStructures_ contains a single string
-    that is '*', ALL structures will be made visible.
-    */
-    std::vector<std::string> initiallyVisibleStructures_;
-
-    /**
-    Contains the "Should this structure be displayed?" flag for all structures.
-    Only filled when structures are loaded.
-
-    Changing this value directly affects the rendering
-    */
-    std::vector<bool>                  structureVisibility_;
-
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader);
-
-    DicomStructureSetLoader(IOracle& oracle,
-                            IObservable& oracleObservable);    
-    
-    DicomStructureSet* GetContent()
-    {
-      return content_.get();
-    }
-
-    void SetStructureDisplayState(size_t structureIndex, bool display);
-    
-    bool GetStructureDisplayState(size_t structureIndex) const
-    {
-      return structureVisibility_.at(structureIndex);
-    }
-
-    ~DicomStructureSetLoader();
-    
-    void LoadInstance(const std::string& instanceId, 
-                      const std::vector<std::string>& initiallyVisibleStructures = std::vector<std::string>());
-
-    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
-
-    void SetStructuresReady();
-
-    bool AreStructuresReady() const;
-  };
-}
--- a/Framework/Loaders/DicomStructureSetLoader2.cpp	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +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/>.
- **/
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "DicomStructureSetLoader2.h"
-
-#include "../Messages/IObservable.h"
-#include "../Oracle/IOracle.h"
-#include "../Oracle/OracleCommandExceptionMessage.h"
-
-namespace OrthancStone
-{
-
-  DicomStructureSetLoader2::DicomStructureSetLoader2(
-    DicomStructureSet2& structureSet
-    , IOracle& oracle
-    , IObservable& oracleObservable)
-    : IObserver(oracleObservable.GetBroker())
-    , IObservable(oracleObservable.GetBroker())
-    , structureSet_(structureSet)
-    , oracle_(oracle)
-    , oracleObservable_(oracleObservable)
-    , structuresReady_(false)
-  {
-    LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()";
-
-    oracleObservable.RegisterObserverCallback(
-      new Callable<DicomStructureSetLoader2, OrthancRestApiCommand::SuccessMessage>
-      (*this, &DicomStructureSetLoader2::HandleSuccessMessage));
-
-    oracleObservable.RegisterObserverCallback(
-      new Callable<DicomStructureSetLoader2, OracleCommandExceptionMessage>
-      (*this, &DicomStructureSetLoader2::HandleExceptionMessage));
-  }
-
-  DicomStructureSetLoader2::~DicomStructureSetLoader2()
-  {
-    LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()";
-    oracleObservable_.Unregister(this);
-  }
-
-  void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body)
-  {
-    OrthancPlugins::FullOrthancDataset dicom(body);
-    //loader.content_.reset(new DicomStructureSet(dicom));
-    structureSet_.Clear();
-    structureSet_.SetContents(dicom);
-    SetStructuresReady();
-  }
-
-  void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message)
-  {
-    const std::string& body = message.GetAnswer();
-    LoadInstanceFromString(body);
-  }
-
-  void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
-  {
-    LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. "
-      << "Error: " << message.GetException().What() << " Details: "
-      << message.GetException().GetDetails();
-  }
-
-  void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId)
-  {
-    std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-    command->SetHttpHeader("Accept-Encoding", "gzip");
-
-    std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050";
-
-    command->SetUri(uri);
-    oracle_.Schedule(*this, command.release());
-  }
-
-  void DicomStructureSetLoader2::SetStructuresReady()
-  {
-    structuresReady_ = true;
-  }
-
-  bool DicomStructureSetLoader2::AreStructuresReady() const
-  {
-    return structuresReady_;
-  }
-
-  /*
-
-    void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
-    {
-      LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing";
-      LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " <<
-        message.GetException().GetDetails();
-        Clear();
-    }
-
-    LoaderStateMachine::~LoaderStateMachine()
-    {
-      Clear();
-    }
-
-
-  */
-
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
--- a/Framework/Loaders/DicomStructureSetLoader2.h	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +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
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "../Toolbox/DicomStructureSet2.h"
-#include "../Messages/IMessage.h"
-#include "../Messages/IObserver.h"
-#include "../Messages/IObservable.h"
-#include "../Oracle/OrthancRestApiCommand.h"
-
-#include <boost/noncopyable.hpp>
-
-namespace OrthancStone
-{
-  class IOracle;
-  class IObservable;
-  class OrthancRestApiCommand;
-  class OracleCommandExceptionMessage;
-
-  class DicomStructureSetLoader2 : public IObserver, public IObservable
-  {
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2);
-
-    /**
-    Warning: the structureSet, oracle and oracleObservable objects must live
-    at least as long as this object (TODO: shared_ptr?)
-    */
-    DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable);
-
-    ~DicomStructureSetLoader2();
-
-    void LoadInstance(const std::string& instanceId);
-
-    /** Internal use */
-    void LoadInstanceFromString(const std::string& body);
-
-    void SetStructuresReady();
-    bool AreStructuresReady() const;
-  
-  private:
-    /**
-    Called back by the oracle when data is ready!
-    */
-    void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message);
-
-    /**
-    Called back by the oracle when shit hits the fan
-    */
-    void HandleExceptionMessage(const OracleCommandExceptionMessage& message);
-
-    /**
-    The structure set that will be (cleared and) filled with data from the 
-    loader
-    */
-    DicomStructureSet2& structureSet_;
-
-    IOracle&            oracle_;
-    IObservable&        oracleObservable_;
-    bool                structuresReady_;
-  };
-}
-
-#endif 
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
--- a/Framework/Loaders/LoaderCache.cpp	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,413 +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 "LoaderCache.h"
-
-#include "../StoneException.h"
-#include "OrthancSeriesVolumeProgressiveLoader.h"
-#include "OrthancMultiframeVolumeLoader.h"
-#include "DicomStructureSetLoader.h"
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-#include "DicomStructureSetLoader2.h"
-#endif 
- //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
-#if ORTHANC_ENABLE_WASM == 1
-# include <unistd.h>
-# include "../Oracle/WebAssemblyOracle.h"
-#else
-# include "../Oracle/ThreadedOracle.h"
-#endif
-
-#include "../Messages/LockingEmitter.h"
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-#include "../Toolbox/DicomStructureSet2.h"
-#endif 
-//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include "../Volumes/DicomVolumeImage.h"
-#include "../Volumes/DicomVolumeImageMPRSlicer.h"
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-#include "../Volumes/DicomStructureSetSlicer2.h"
-#endif 
-//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-#include <Core/OrthancException.h>
-#include <Core/Toolbox.h>
-
-namespace OrthancStone
-{
-#if ORTHANC_ENABLE_WASM == 1
-  LoaderCache::LoaderCache(WebAssemblyOracle& oracle)
-    : oracle_(oracle)
-  {
-
-  }
-#else
-  LoaderCache::LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter)
-    : oracle_(oracle)
-    , lockingEmitter_(lockingEmitter)
-  {
-  }
-#endif
-
-  boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> 
-    LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid)
-  {
-    try
-    {
-      
-      // normalize keys a little
-      seriesUuid = Orthanc::Toolbox::StripSpaces(seriesUuid);
-      Orthanc::Toolbox::ToLowerCase(seriesUuid);
-
-      // find in cache
-      if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end())
-      {
-//        LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : CACHEMISS --> need to load seriesUUid = " << seriesUuid;
-#if ORTHANC_ENABLE_WASM == 1
-//        LOG(TRACE) << "Performing request for series " << seriesUuid << " sbrk(0) = " << sbrk(0);
-#else
-//        LOG(TRACE) << "Performing request for series " << seriesUuid;
-#endif
-        boost::shared_ptr<DicomVolumeImage> volumeImage(new DicomVolumeImage);
-        boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader;
-//        LOG(TRACE) << "volumeImage = " << volumeImage.get();
-        {
-#if ORTHANC_ENABLE_WASM == 1
-          loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_));
-#else
-          Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
-          loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable()));
-#endif
-//          LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get();
-          loader->LoadSeries(seriesUuid);
-//          LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader->LoadSeries successful";
-        }
-        seriesVolumeProgressiveLoaders_[seriesUuid] = loader;
-      }
-      else
-      {
-//        LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid;
-      }
-      return seriesVolumeProgressiveLoaders_[seriesUuid];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in LoaderCache";
-      throw;
-    }
-  }
-
-  boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid)
-  {
-    // if the loader is not available, let's trigger its creation
-    if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end())
-    {
-      GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid);
-    }
-    ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end());
-
-    return multiframeVolumeLoaders_[instanceUuid];
-  }
-
-  boost::shared_ptr<DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid)
-  {
-    try
-    {
-      // normalize keys a little
-      instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid);
-      Orthanc::Toolbox::ToLowerCase(instanceUuid);
-
-      // find in cache
-      if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end())
-      {
-        boost::shared_ptr<DicomVolumeImage> volumeImage(new DicomVolumeImage);
-        boost::shared_ptr<OrthancMultiframeVolumeLoader> loader;
-
-        {
-#if ORTHANC_ENABLE_WASM == 1
-          loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, oracle_));
-#else
-          Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
-          loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, lock.GetOracleObservable()));
-#endif
-          loader->LoadInstance(instanceUuid);
-        }
-        multiframeVolumeLoaders_[instanceUuid] = loader;
-        boost::shared_ptr<DicomVolumeImageMPRSlicer> mprSlicer(new DicomVolumeImageMPRSlicer(volumeImage));
-        dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer;
-      }
-      return dicomVolumeImageMPRSlicers_[instanceUuid];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in LoaderCache";
-      throw;
-    }
-  }
-  
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-  boost::shared_ptr<DicomStructureSetSlicer2> LoaderCache::GetDicomStructureSetSlicer2(std::string instanceUuid)
-  {
-    // if the loader is not available, let's trigger its creation
-    if (dicomStructureSetSlicers2_.find(instanceUuid) == dicomStructureSetSlicers2_.end())
-    {
-      GetDicomStructureSetLoader2(instanceUuid);
-    }
-    ORTHANC_ASSERT(dicomStructureSetSlicers2_.find(instanceUuid) != dicomStructureSetSlicers2_.end());
-
-    return dicomStructureSetSlicers2_[instanceUuid];
-  }
-#endif
-//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
-  /**
-  This method allows to convert a list of string into a string by 
-  sorting the strings then joining them
-  */
-  static std::string SortAndJoin(const std::vector<std::string>& stringList)
-  {
-    if (stringList.size() == 0)
-    {
-      return "";
-    } 
-    else
-    {
-      std::vector<std::string> sortedStringList = stringList;
-      std::sort(sortedStringList.begin(), sortedStringList.end());
-      std::stringstream s;
-      s << sortedStringList[0];
-      for (size_t i = 1; i < sortedStringList.size(); ++i)
-      {
-        s << "-" << sortedStringList[i];
-      }
-      return s.str();
-    }
-  }
-  
-  boost::shared_ptr<DicomStructureSetLoader> 
-    LoaderCache::GetDicomStructureSetLoader(
-      std::string inInstanceUuid, 
-      const std::vector<std::string>& initiallyVisibleStructures)
-  {
-    try
-    {
-      // normalize keys a little
-      inInstanceUuid = Orthanc::Toolbox::StripSpaces(inInstanceUuid);
-      Orthanc::Toolbox::ToLowerCase(inInstanceUuid);
-
-      std::string initiallyVisibleStructuresKey = 
-        SortAndJoin(initiallyVisibleStructures);
-
-      std::string entryKey = inInstanceUuid + "_" + initiallyVisibleStructuresKey;
-
-      // find in cache
-      if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end())
-      {
-        boost::shared_ptr<DicomStructureSetLoader> loader;
-
-        {
-#if ORTHANC_ENABLE_WASM == 1
-          loader.reset(new DicomStructureSetLoader(oracle_, oracle_));
-#else
-          Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
-          loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable()));
-#endif
-          loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures);
-        }
-        dicomStructureSetLoaders_[entryKey] = loader;
-      }
-      return dicomStructureSetLoaders_[entryKey];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in LoaderCache: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in LoaderCache: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in LoaderCache";
-      throw;
-    }
-  }
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-  boost::shared_ptr<DicomStructureSetLoader2> LoaderCache::GetDicomStructureSetLoader2(std::string instanceUuid)
-  {
-    try
-    {
-      // normalize keys a little
-      instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid);
-      Orthanc::Toolbox::ToLowerCase(instanceUuid);
-
-      // find in cache
-      if (dicomStructureSetLoaders2_.find(instanceUuid) == dicomStructureSetLoaders2_.end())
-      {
-        boost::shared_ptr<DicomStructureSetLoader2> loader;
-        boost::shared_ptr<DicomStructureSet2> structureSet(new DicomStructureSet2());
-        boost::shared_ptr<DicomStructureSetSlicer2> rtSlicer(new DicomStructureSetSlicer2(structureSet));
-        dicomStructureSetSlicers2_[instanceUuid] = rtSlicer;
-        dicomStructureSets2_[instanceUuid] = structureSet; // to prevent it from being deleted
-        {
-#if ORTHANC_ENABLE_WASM == 1
-          loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, oracle_));
-#else
-          LockingEmitter::WriterLock lock(lockingEmitter_);
-          // TODO: clarify lifetimes... this is DANGEROUS!
-          loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, lock.GetOracleObservable()));
-#endif
-          loader->LoadInstance(instanceUuid);
-        }
-        dicomStructureSetLoaders2_[instanceUuid] = loader;
-      }
-      return dicomStructureSetLoaders2_[instanceUuid];
-    }
-    catch (const Orthanc::OrthancException& e)
-    {
-      if (e.HasDetails())
-      {
-        LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What() << " Details: " << e.GetDetails();
-      }
-      else
-      {
-        LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What();
-      }
-      throw;
-    }
-    catch (const std::exception& e)
-    {
-      LOG(ERROR) << "std::exception in GetDicomStructureSetLoader2: " << e.what();
-      throw;
-    }
-    catch (...)
-    {
-      LOG(ERROR) << "Unknown exception in GetDicomStructureSetLoader2";
-      throw;
-    }
-  }
-
-#endif
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-
-  void LoaderCache::ClearCache()
-  {
-#if ORTHANC_ENABLE_WASM != 1
-    Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_);
-#endif
-    
-//#ifndef NDEBUG
-    // ISO way of checking for debug builds
-    DebugDisplayObjRefCounts();
-//#endif
-    seriesVolumeProgressiveLoaders_.clear();
-    multiframeVolumeLoaders_.clear();
-    dicomVolumeImageMPRSlicers_.clear();
-    dicomStructureSetLoaders_.clear();
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-    // order is important!
-    dicomStructureSetLoaders2_.clear();
-    dicomStructureSetSlicers2_.clear();
-    dicomStructureSets2_.clear();
-#endif
-// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-  }
-
-  template<typename T> void DebugDisplayObjRefCountsInMap(
-    const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap)
-  {
-    LOG(TRACE) << "Map \"" << name << "\" ref counts:";
-    size_t i = 0;
-    for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator 
-           it = myMap.begin(); it != myMap.end(); ++it)
-    {
-      LOG(TRACE) << "  element #" << i << ": ref count = " << it->second.use_count();
-      i++;
-    }
-  }
-
-  void LoaderCache::DebugDisplayObjRefCounts()
-  {
-    DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_);
-    DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_);
-    DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_);
-    DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_);
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-    DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders2_", dicomStructureSetLoaders2_);
-    DebugDisplayObjRefCountsInMap("dicomStructureSetSlicers2_", dicomStructureSetSlicers2_);
-#endif
-//BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-  }
-}
--- a/Framework/Loaders/LoaderCache.h	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#pragma once
-
-#include <boost/shared_ptr.hpp>
-
-#include <map>
-#include <string>
-#include <vector>
-
-namespace OrthancStone
-{
-  class OrthancSeriesVolumeProgressiveLoader;
-  class DicomVolumeImageMPRSlicer;
-  class DicomStructureSetLoader;
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-  class DicomStructureSetLoader2;
-  class DicomStructureSetSlicer2;
-  class DicomStructureSet2;
-#endif 
-  //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-  class OrthancMultiframeVolumeLoader;
-
-#if ORTHANC_ENABLE_WASM == 1
-  class WebAssemblyOracle;
-#else
-  class ThreadedOracle;
-  namespace Deprecated
-  {
-    class LockingEmitter;
-  }
-#endif
-
-  class LoaderCache
-  {
-  public:
-#if ORTHANC_ENABLE_WASM == 1
-    LoaderCache(WebAssemblyOracle& oracle);
-#else
-    LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter);
-#endif
-
-    boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader>
-      GetSeriesVolumeProgressiveLoader      (std::string seriesUuid);
-    
-    boost::shared_ptr<DicomVolumeImageMPRSlicer>
-      GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid);
-
-    boost::shared_ptr<OrthancMultiframeVolumeLoader>
-      GetMultiframeVolumeLoader(std::string instanceUuid);
-
-    boost::shared_ptr<DicomStructureSetLoader>
-      GetDicomStructureSetLoader(
-        std::string instanceUuid,
-        const std::vector<std::string>& initiallyVisibleStructures);
-
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-    boost::shared_ptr<DicomStructureSetLoader2>
-      GetDicomStructureSetLoader2(std::string instanceUuid);
-
-    boost::shared_ptr<DicomStructureSetSlicer2>
-      GetDicomStructureSetSlicer2(std::string instanceUuid);
-#endif 
-    //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-
-    void ClearCache();
-
-  private:
-    
-    void DebugDisplayObjRefCounts();
-#if ORTHANC_ENABLE_WASM == 1
-    WebAssemblyOracle& oracle_;
-#else
-    ThreadedOracle& oracle_;
-    Deprecated::LockingEmitter& lockingEmitter_;
-#endif
-
-    std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> >
-      seriesVolumeProgressiveLoaders_;
-    std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> >
-      multiframeVolumeLoaders_;
-    std::map<std::string, boost::shared_ptr<DicomVolumeImageMPRSlicer> >
-      dicomVolumeImageMPRSlicers_;
-    std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> >
-      dicomStructureSetLoaders_;
-#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-    std::map<std::string, boost::shared_ptr<DicomStructureSetLoader2> >
-      dicomStructureSetLoaders2_;
-    std::map<std::string, boost::shared_ptr<DicomStructureSet2> >
-      dicomStructureSets2_;
-    std::map<std::string, boost::shared_ptr<DicomStructureSetSlicer2> >
-      dicomStructureSetSlicers2_;
-#endif 
-    //BGO_ENABLE_DICOMSTRUCTURESETLOADER2
-  };
-}
-
--- a/Framework/Loaders/LoaderStateMachine.cpp	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +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 "LoaderStateMachine.h"
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  void LoaderStateMachine::State::Handle(const OrthancRestApiCommand::SuccessMessage& message)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-      
-
-  void LoaderStateMachine::State::Handle(const GetOrthancImageCommand::SuccessMessage& message)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-
-      
-  void LoaderStateMachine::State::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-
-
-  void LoaderStateMachine::Schedule(OracleCommandBase* command)
-  {
-    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()";
-
-    std::auto_ptr<OracleCommandBase> protection(command);
-
-    if (command == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-      
-    if (!command->HasPayload())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                      "The payload must contain the next state");
-    }
-    pendingCommands_.push_back(protection.release());
-
-    Step();
-  }
-
-
-  void LoaderStateMachine::Start()
-  {
-    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Start()";
-
-    if (active_)
-    {
-      LOG(TRACE) << "LoaderStateMachine::Start() called while active_ is true";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-
-    active_ = true;
-
-    for (size_t i = 0; i < simultaneousDownloads_; i++)
-    {
-      Step();
-    }
-  }
-
-
-  void LoaderStateMachine::Step()
-  {
-    if (!pendingCommands_.empty() &&
-        activeCommands_ < simultaneousDownloads_)
-    {
-
-      IOracleCommand* nextCommand = pendingCommands_.front();
-
-      LOG(TRACE) << "    LoaderStateMachine(" << std::hex << this << std::dec << 
-        ")::Step(): activeCommands_ (" << activeCommands_ << 
-        ") < simultaneousDownloads_ (" << simultaneousDownloads_ << 
-        ") --> will Schedule command addr " << std::hex << nextCommand << std::dec;
-
-      boost::shared_ptr<IObserver> observer(GetSharedObserver());
-      oracle_.Schedule(observer, nextCommand);
-      pendingCommands_.pop_front();
-
-      activeCommands_++;
-    }
-    else
-    {
-      LOG(TRACE) << "    LoaderStateMachine(" << std::hex << this << std::dec << 
-        ")::Step(): activeCommands_ (" << activeCommands_ << 
-        ") >= simultaneousDownloads_ (" << simultaneousDownloads_ << 
-        ") --> will NOT Schedule command";
-    }
-  }
-
-
-  void LoaderStateMachine::Clear()
-  {
-    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Clear()";
-    for (PendingCommands::iterator it = pendingCommands_.begin();
-         it != pendingCommands_.end(); ++it)
-    {
-      delete *it;
-    }
-
-    pendingCommands_.clear();
-  }
-
-
-  void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message)
-  {
-    LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing";
-    LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " <<
-      message.GetException().GetDetails();
-      Clear();
-  }
-
-  template <typename T>
-  void LoaderStateMachine::HandleSuccessMessage(const T& message)
-  {
-    if (activeCommands_ <= 0) {
-      LOG(ERROR) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage : activeCommands_ should be > 0 but is: " << activeCommands_;
-    }
-    else {
-      activeCommands_--;
-      try
-      {
-        dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message);
-        Step();
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        LOG(ERROR) << "Error in the state machine, stopping all processing: " <<
-          e.What() << " Details: " << e.GetDetails();
-        Clear();
-      }
-    }
-  }
-
-
-  LoaderStateMachine::LoaderStateMachine(IOracle& oracle,
-                                         IObservable& oracleObservable) :
-    oracle_(oracle),
-    active_(false),
-    simultaneousDownloads_(4),
-    activeCommands_(0)
-  {
-    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()";
-
-    // TODO => Move this out of constructor
-    Register<OrthancRestApiCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
-    Register<GetOrthancImageCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
-    Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage);
-    Register<OracleCommandExceptionMessage>(oracleObservable, &LoaderStateMachine::HandleExceptionMessage);
-  }
-
-  LoaderStateMachine::~LoaderStateMachine()
-  {
-    LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()";
-    Clear();
-  }
-
-  void LoaderStateMachine::SetSimultaneousDownloads(unsigned int count)
-  {
-    if (active_)
-    {
-      LOG(ERROR) << "LoaderStateMachine::SetSimultaneousDownloads called while active_ is true";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else if (count == 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
-    }
-    else
-    {
-      simultaneousDownloads_ = count;
-    }
-  }
-}
--- a/Framework/Loaders/LoaderStateMachine.h	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Messages/IObservable.h"
-#include "../Messages/ObserverBase.h"
-#include "../Oracle/GetOrthancImageCommand.h"
-#include "../Oracle/GetOrthancWebViewerJpegCommand.h"
-#include "../Oracle/IOracle.h"
-#include "../Oracle/OracleCommandExceptionMessage.h"
-#include "../Oracle/OrthancRestApiCommand.h"
-
-#include <Core/IDynamicObject.h>
-
-#include <list>
-
-namespace OrthancStone
-{
-  /**
-     This class is supplied with Oracle commands and will schedule up to 
-     simultaneousDownloads_ of them at the same time, then will schedule the 
-     rest once slots become available. It is used, a.o., by the 
-     OrtancMultiframeVolumeLoader class.
-  */
-  class LoaderStateMachine : public ObserverBase<LoaderStateMachine>
-  {
-  protected:
-    class State : public Orthanc::IDynamicObject
-    {
-    private:
-      LoaderStateMachine&  that_;
-
-    public:
-      State(LoaderStateMachine& that) :
-      that_(that)
-      {
-      }
-
-      State(const State& currentState) :
-      that_(currentState.that_)
-      {
-      }
-
-      void Schedule(OracleCommandBase* command) const
-      {
-        that_.Schedule(command);
-      }
-
-      template <typename T>
-      T& GetLoader() const
-      {
-        return dynamic_cast<T&>(that_);
-      }
-      
-      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message);
-      
-      virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message);
-      
-      virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message);
-    };
-
-    void Schedule(OracleCommandBase* command);
-
-    void Start();
-
-  private:
-    void Step();
-
-    void Clear();
-
-    void HandleExceptionMessage(const OracleCommandExceptionMessage& message);
-
-    template <typename T>
-    void HandleSuccessMessage(const T& message);
-
-    typedef std::list<IOracleCommand*>  PendingCommands;
-
-    IOracle&         oracle_;
-    bool             active_;
-    unsigned int     simultaneousDownloads_;
-    PendingCommands  pendingCommands_;
-    unsigned int     activeCommands_;
-
-  public:
-    LoaderStateMachine(IOracle& oracle,
-                       IObservable& oracleObservable);
-
-    virtual ~LoaderStateMachine();
-
-    bool IsActive() const
-    {
-      return active_;
-    }
-
-    void SetSimultaneousDownloads(unsigned int count);  
-  };
-}
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,383 +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 "OrthancMultiframeVolumeLoader.h"
-
-#include <Core/Endianness.h>
-#include <Core/Toolbox.h>
-
-namespace OrthancStone
-{
-  class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State
-  {
-  private:
-    std::auto_ptr<Orthanc::DicomMap>  dicom_;
-
-  public:
-    LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
-                       Orthanc::DicomMap* dicom) :
-      State(that),
-      dicom_(dicom)
-    {
-      if (dicom == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-
-    }
-
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
-      std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
-      dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
-
-      GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
-    }      
-  };
-
-
-  static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
-  {
-    std::string s;
-    if (!dicom.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                      "DICOM file without SOP class UID");
-    }
-    else
-    {
-      return s;
-    }
-  }
-    
-
-  class OrthancMultiframeVolumeLoader::LoadGeometry : public State
-  {
-  public:
-    LoadGeometry(OrthancMultiframeVolumeLoader& that) :
-    State(that)
-    {
-    }
-      
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
-        
-      Json::Value body;
-      message.ParseJsonBody(body);
-        
-      if (body.type() != Json::objectValue)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-
-      std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
-      dicom->FromDicomAsJson(body);
-
-      if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose)
-      {
-        // Download the "Grid Frame Offset Vector" DICOM tag, that is
-        // mandatory for RT-DOSE, but is too long to be returned by default
-          
-        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-        command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
-                        Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
-        command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release()));
-
-        Schedule(command.release());
-      }
-      else
-      {
-        loader.SetGeometry(*dicom);
-      }
-    }
-  };
-
-  class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State
-  {
-  public:
-    LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
-    State(that)
-    {
-    }
-      
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
-    }
-  };
-
-  class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State
-  {
-  public:
-    LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
-    State(that)
-    {
-    }
-      
-    virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
-    {
-      GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
-    }
-  };
-
-  const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const
-  {
-    if (IsActive())
-    {
-      return instanceId_;
-    }
-    else
-    {
-      LOG(ERROR) << "OrthancMultiframeVolumeLoader::GetInstanceId(): (!IsActive())";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-  void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads()
-  {
-    if (transferSyntaxUid_.empty() ||
-        !volume_->HasGeometry())
-    {
-      return;
-    }
-    /*
-      1.2.840.10008.1.2	Implicit VR Endian: Default Transfer Syntax for DICOM
-      1.2.840.10008.1.2.1	Explicit VR Little Endian
-      1.2.840.10008.1.2.2	Explicit VR Big Endian
-
-      See https://www.dicomlibrary.com/dicom/transfer-syntax/
-    */
-    if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
-        transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
-        transferSyntaxUid_ == "1.2.840.10008.1.2.2")
-    {
-      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-      command->SetHttpHeader("Accept-Encoding", "gzip");
-      command->SetUri("/instances/" + instanceId_ + "/content/" +
-                      Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
-      command->AcquirePayload(new LoadUncompressedPixelData(*this));
-      Schedule(command.release());
-    }
-    else
-    {
-      throw Orthanc::OrthancException(
-        Orthanc::ErrorCode_NotImplemented,
-        "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
-    }
-  }
-
-  void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax)
-  {
-    transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
-    ScheduleFrameDownloads();
-  }
-
-  void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom)
-  {
-    DicomInstanceParameters parameters(dicom);
-    volume_->SetDicomParameters(parameters);
-      
-    Orthanc::PixelFormat format;
-    if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    double spacingZ;
-    switch (parameters.GetSopClassUid())
-    {
-      case SopClassUid_RTDose:
-        spacingZ = parameters.GetThickness();
-        break;
-
-      default:
-        throw Orthanc::OrthancException(
-          Orthanc::ErrorCode_NotImplemented,
-          "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
-    }
-
-    const unsigned int width = parameters.GetImageInformation().GetWidth();
-    const unsigned int height = parameters.GetImageInformation().GetHeight();
-    const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
-
-    {
-      VolumeImageGeometry geometry;
-      geometry.SetSizeInVoxels(width, height, depth);
-      geometry.SetAxialGeometry(parameters.GetGeometry());
-      geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
-                                  parameters.GetPixelSpacingY(), spacingZ);
-      volume_->Initialize(geometry, format, true /* Do compute range */);
-    }
-
-    volume_->GetPixelData().Clear();
-
-    ScheduleFrameDownloads();
-
-
-
-    BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
-  }
-
-
-  ORTHANC_FORCE_INLINE
-  static void CopyPixel(uint32_t& target, const void* source)
-  {
-    // TODO - check alignement?
-    target = le32toh(*reinterpret_cast<const uint32_t*>(source));
-  }
-
-  ORTHANC_FORCE_INLINE
-    static void CopyPixel(uint16_t& target, const void* source)
-  {
-    // TODO - check alignement?
-    target = le16toh(*reinterpret_cast<const uint16_t*>(source));
-  }
-
-  ORTHANC_FORCE_INLINE
-    static void CopyPixel(int16_t& target, const void* source)
-  {
-    // byte swapping is the same for unsigned and signed integers
-    // (the sign bit is always stored with the MSByte)
-    uint16_t* targetUp = reinterpret_cast<uint16_t*>(&target);
-    CopyPixel(*targetUp, source);
-  }
-
-  template <typename T>
-  void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData)
-  {
-    ImageBuffer3D& target = volume_->GetPixelData();
-      
-    const unsigned int bpp = target.GetBytesPerPixel();
-    const unsigned int width = target.GetWidth();
-    const unsigned int height = target.GetHeight();
-    const unsigned int depth = target.GetDepth();
-
-    if (pixelData.size() != bpp * width * height * depth)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                      "The pixel data has not the proper size");
-    }
-
-    if (pixelData.empty())
-    {
-      return;
-    }
-
-    const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
-
-    for (unsigned int z = 0; z < depth; z++)
-    {
-      ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z);
-
-      assert (writer.GetAccessor().GetWidth() == width &&
-              writer.GetAccessor().GetHeight() == height);
-
-      for (unsigned int y = 0; y < height; y++)
-      {
-        assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
-
-        T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
-
-        for (unsigned int x = 0; x < width; x++)
-        {
-          CopyPixel(*target, source);
-          target ++;
-          source += bpp;
-        }
-      }
-    }
-  }
-
-  void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData)
-  {
-    switch (volume_->GetPixelData().GetFormat())
-    {
-      case Orthanc::PixelFormat_Grayscale32:
-        CopyPixelData<uint32_t>(pixelData);
-        break;
-      case Orthanc::PixelFormat_Grayscale16:
-        CopyPixelData<uint16_t>(pixelData);
-        break;
-      case Orthanc::PixelFormat_SignedGrayscale16:
-        CopyPixelData<int16_t>(pixelData);
-        break;
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
-    volume_->IncrementRevision();
-
-    pixelDataLoaded_ = true;
-    BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
-  }
-  
-  bool OrthancMultiframeVolumeLoader::HasGeometry() const
-  {
-    return volume_->HasGeometry();
-  }
-
-  const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const
-  {
-    return volume_->GetGeometry();
-  }
-
-  OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume,
-                                                               IOracle& oracle,
-                                                               IObservable& oracleObservable) :
-    LoaderStateMachine(oracle, oracleObservable),
-    volume_(volume),
-    pixelDataLoaded_(false)
-  {
-    if (volume.get() == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-  }
-
-  OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()
-  {
-    LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()";
-  }
-
-  void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId)
-  {
-    Start();
-
-    instanceId_ = instanceId;
-
-    {
-      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-      command->SetHttpHeader("Accept-Encoding", "gzip");
-      command->SetUri("/instances/" + instanceId + "/tags");
-      command->AcquirePayload(new LoadGeometry(*this));
-      Schedule(command.release());
-    }
-
-    {
-      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-      command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
-      command->AcquirePayload(new LoadTransferSyntax(*this));
-      Schedule(command.release());
-    }
-  }
-}
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "LoaderStateMachine.h"
-#include "../Volumes/DicomVolumeImage.h"
-
-#include <boost/shared_ptr.hpp>
-
-namespace OrthancStone
-{
-  class OrthancMultiframeVolumeLoader :
-    public LoaderStateMachine,
-    public IObservable
-  {
-  private:
-    class LoadRTDoseGeometry;
-    class LoadGeometry;
-    class LoadTransferSyntax;    
-    class LoadUncompressedPixelData;
-
-    boost::shared_ptr<DicomVolumeImage>  volume_;
-    std::string                          instanceId_;
-    std::string                          transferSyntaxUid_;
-    bool                                 pixelDataLoaded_;
-
-    const std::string& GetInstanceId() const;
-
-    void ScheduleFrameDownloads();
-
-    void SetTransferSyntax(const std::string& transferSyntax);
-
-    void SetGeometry(const Orthanc::DicomMap& dicom);
-
-    template <typename T>
-    void CopyPixelData(const std::string& pixelData);
-
-    void SetUncompressedPixelData(const std::string& pixelData);
-
-    bool HasGeometry() const;
-    const VolumeImageGeometry& GetImageGeometry() const;
-
-  public:
-    OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume,
-                                  IOracle& oracle,
-                                  IObservable& oracleObservable);
-    
-    virtual ~OrthancMultiframeVolumeLoader();
-
-    bool IsPixelDataLoaded() const
-    {
-      return pixelDataLoaded_;
-    }
-
-    void LoadInstance(const std::string& instanceId);
-  };
-}
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,513 +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 "OrthancSeriesVolumeProgressiveLoader.h"
-
-#include "../Toolbox/GeometryToolbox.h"
-#include "../Volumes/DicomVolumeImageMPRSlicer.h"
-#include "BasicFetchingItemsSorter.h"
-#include "BasicFetchingStrategy.h"
-
-#include <Core/Images/ImageProcessing.h>
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice
-  {
-  private:
-    const OrthancSeriesVolumeProgressiveLoader&  that_;
-
-  public:
-    ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
-                   const CoordinateSystem3D& plane) :
-      DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
-      that_(that)
-    {
-      if (IsValid())
-      {
-        if (GetProjection() == VolumeProjection_Axial)
-        {
-          // For coronal and sagittal projections, we take the global
-          // revision of the volume because even if a single slice changes,
-          // this means the projection will yield a different result --> 
-          // we must increase the revision as soon as any slice changes 
-          SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex()));
-        }
-      
-        if (that_.strategy_.get() != NULL &&
-            GetProjection() == VolumeProjection_Axial)
-        {
-          that_.strategy_->SetCurrent(GetSliceIndex());
-        }
-      }
-    }
-  };
-
-    
-    
-  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index,
-                                                                        const DicomInstanceParameters& reference) const
-  {
-    const DicomInstanceParameters& slice = *slices_[index];
-      
-    if (!GeometryToolbox::IsParallel(
-          reference.GetGeometry().GetNormal(),
-          slice.GetGeometry().GetNormal()))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                      "A slice in the volume image is not parallel to the others");
-    }
-
-    if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
-                                      "The pixel format changes across the slices of the volume image");
-    }
-
-    if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
-        reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
-                                      "The width/height of slices are not constant in the volume image");
-    }
-
-    if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
-        !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                      "The pixel spacing of the slices change across the volume image");
-    }
-  }
-
-    
-  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckVolume() const
-  {
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      assert(slices_[i] != NULL);
-      if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "This class does not support multi-frame images");
-      }
-    }
-
-    if (slices_.size() != 0)
-    {
-      const DicomInstanceParameters& reference = *slices_[0];
-
-      for (size_t i = 1; i < slices_.size(); i++)
-      {
-        CheckSlice(i, reference);
-      }
-    }
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear()
-  {
-    for (size_t i = 0; i < slices_.size(); i++)
-    {
-      assert(slices_[i] != NULL);
-      delete slices_[i];
-    }
-
-    slices_.clear();
-    slicesRevision_.clear();
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const
-  {
-    if (!HasGeometry())
-    {
-      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index): (!HasGeometry())";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else if (index >= slices_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(slices_.size() == GetImageGeometry().GetDepth() &&
-             slices_.size() == slicesRevision_.size());
-    }
-  }
-
-
-  // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
-  // (called with the slices created in LoadGeometry)
-  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(SlicesSorter& slices)
-  {
-    Clear();
-      
-    if (!slices.Sort())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                      "Cannot sort the 3D slices of a DICOM series");          
-    }
-
-    if (slices.GetSlicesCount() == 0)
-    {
-      geometry_.reset(new VolumeImageGeometry);
-    }
-    else
-    {
-      slices_.reserve(slices.GetSlicesCount());
-      slicesRevision_.resize(slices.GetSlicesCount(), 0);
-
-      for (size_t i = 0; i < slices.GetSlicesCount(); i++)
-      {
-        const DicomInstanceParameters& slice =
-          dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
-        slices_.push_back(new DicomInstanceParameters(slice));
-      }
-
-      CheckVolume();
-
-      double spacingZ;
-
-      if (slices.ComputeSpacingBetweenSlices(spacingZ))
-      {
-        LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
-      
-        const DicomInstanceParameters& parameters = *slices_[0];
-
-        geometry_.reset(new VolumeImageGeometry);
-        geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(),
-                                   parameters.GetImageInformation().GetHeight(),
-                                   static_cast<unsigned int>(slices.GetSlicesCount()));
-        geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
-        geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
-                                      parameters.GetPixelSpacingY(), spacingZ);
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
-                                        "The origins of the slices of a volume image are not regularly spaced");
-     }
-    }
-  }
-
-
-  const VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const
-  {
-    if (!HasGeometry())
-    {
-      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry(): (!HasGeometry())";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      assert(slices_.size() == geometry_->GetDepth());
-      return *geometry_;
-    }
-  }
-
-
-  const DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const
-  {
-    CheckSliceIndex(index);
-    return *slices_[index];
-  }
-
-
-  uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const
-  {
-    CheckSliceIndex(index);
-    return slicesRevision_[index];
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index)
-  {
-    CheckSliceIndex(index);
-    slicesRevision_[index] ++;
-  }
-
-
-  static unsigned int GetSliceIndexPayload(const OracleCommandBase& command)
-  {
-    assert(command.HasPayload());
-    return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload()
-  {
-    assert(strategy_.get() != NULL);
-      
-    unsigned int sliceIndex, quality;
-      
-    if (strategy_->GetNext(sliceIndex, quality))
-    {
-      assert(quality <= BEST_QUALITY);
-
-      const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
-          
-      const std::string& instance = slice.GetOrthancInstanceIdentifier();
-      if (instance.empty())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-      std::auto_ptr<OracleCommandBase> command;
-        
-      if (quality == BEST_QUALITY)
-      {
-        std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand);
-        // TODO: review the following comment. 
-        // - Commented out by bgo on 2019-07-19 | reason: Alain has seen cases 
-        //   where gzipping the uint16 image took 11 sec to produce 5mb. 
-        //   The unzipped request was much much faster.
-        // - Re-enabled on 2019-07-30. Reason: in Web Assembly, the browser 
-        //   does not use the Accept-Encoding header and always requests
-        //   compression. Furthermore, NOT 
-        tmp->SetHttpHeader("Accept-Encoding", "gzip");
-        tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-        tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());
-        tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-        command.reset(tmp.release());
-      }
-      else
-      {
-        std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand);
-        // TODO: review the following comment. Commented out by bgo on 2019-07-19
-        // (gzip for jpeg seems overkill)
-        //tmp->SetHttpHeader("Accept-Encoding", "gzip");
-        tmp->SetInstance(instance);
-        tmp->SetQuality((quality == 0 ? 50 : 90));
-        tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-        command.reset(tmp.release());
-      }
-
-      command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
-
-      boost::shared_ptr<IObserver> observer(GetSharedObserver());
-      oracle_.Schedule(observer, command.release());
-    }
-    else
-    {
-      // loading is finished!
-      volumeImageReadyInHighQuality_ = true;
-      BroadcastMessage(OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality(*this));
-    }
-  }
-
-/**
-   This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
-*/
-  void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
-  {
-    Json::Value body;
-    message.ParseJsonBody(body);
-      
-    if (body.type() != Json::objectValue)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-
-    {
-      Json::Value::Members instances = body.getMemberNames();
-
-      SlicesSorter slices;
-        
-      for (size_t i = 0; i < instances.size(); i++)
-      {
-        Orthanc::DicomMap dicom;
-        dicom.FromDicomAsJson(body[instances[i]]);
-
-        std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
-        instance->SetOrthancInstanceIdentifier(instances[i]);
-
-        // the 3D plane corresponding to the slice
-        CoordinateSystem3D geometry = instance->GetGeometry();
-        slices.AddSlice(geometry, instance.release());
-      }
-
-      seriesGeometry_.ComputeGeometry(slices);
-    }
-
-    size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
-
-    if (slicesCount == 0)
-    {
-      volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
-    }
-    else
-    {
-      const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
-        
-      volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
-      volume_->SetDicomParameters(parameters);
-      volume_->GetPixelData().Clear();
-
-      strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY));
-        
-      assert(simultaneousDownloads_ != 0);
-      for (unsigned int i = 0; i < simultaneousDownloads_; i++)
-      {
-        ScheduleNextSliceDownload();
-      }
-    }
-
-    slicesQuality_.resize(slicesCount, 0);
-
-    BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex,
-                                                             const Orthanc::ImageAccessor& image,
-                                                             unsigned int quality)
-  {
-    assert(sliceIndex < slicesQuality_.size() &&
-           slicesQuality_.size() == volume_->GetPixelData().GetDepth());
-      
-    if (quality >= slicesQuality_[sliceIndex])
-    {
-      {
-        ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex);
-        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
-      }
-
-      volume_->IncrementRevision();
-      seriesGeometry_.IncrementSliceRevision(sliceIndex);
-      slicesQuality_[sliceIndex] = quality;
-
-      BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
-    }
-
-    ScheduleNextSliceDownload();
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message)
-  {
-    SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY);
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
-  {
-    unsigned int quality;
-      
-    switch (dynamic_cast<const GetOrthancWebViewerJpegCommand&>(message.GetOrigin()).GetQuality())
-    {
-      case 50:
-        quality = LOW_QUALITY;
-        break;
-
-      case 90:
-        quality = MIDDLE_QUALITY;
-        break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-      
-    SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
-  }
-
-
-  OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
-                                                                             IOracle& oracle,
-                                                                             IObservable& oracleObservable) :
-    oracle_(oracle),
-    active_(false),
-    simultaneousDownloads_(4),
-    volume_(volume),
-    sorter_(new BasicFetchingItemsSorter::Factory),
-    volumeImageReadyInHighQuality_(false)
-  {
-    // TODO => Move this out of constructor
-    Register<OrthancRestApiCommand::SuccessMessage>
-      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry);
-
-    Register<GetOrthancImageCommand::SuccessMessage>
-      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent);
-
-    Register<GetOrthancWebViewerJpegCommand::SuccessMessage>
-      (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent);
-  }
-
-  OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()
-  {
-    LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()";
-  }
-
-  void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count)
-  {
-    if (active_)
-    {
-      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(): (active_)";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else if (count == 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
-    }
-    else
-    {
-      simultaneousDownloads_ = count;
-    }
-  }
-
-
-  void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId)
-  {
-//    LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries seriesId=" << seriesId;
-    if (active_)
-    {
-//      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries NOT ACTIVE! --> ERROR";
-      LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId): (active_)";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      active_ = true;
-
-      std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
-      command->SetUri("/series/" + seriesId + "/instances-tags");
-
-//      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule";
-      boost::shared_ptr<IObserver> observer(GetSharedObserver());
-      oracle_.Schedule(observer, command.release());
-//      LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule";
-    }
-  }
-  
-
-  IVolumeSlicer::IExtractedSlice* 
-  OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane)
-  {
-    if (volume_->HasGeometry())
-    {
-      return new ExtractedSlice(*this, cuttingPlane);
-    }
-    else
-    {
-      return new IVolumeSlicer::InvalidSlice;
-    }
-  }
-}
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Sat Dec 07 18:45:37 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Messages/IObservable.h"
-#include "../Messages/ObserverBase.h"
-#include "../Oracle/GetOrthancImageCommand.h"
-#include "../Oracle/GetOrthancWebViewerJpegCommand.h"
-#include "../Oracle/IOracle.h"
-#include "../Oracle/OrthancRestApiCommand.h"
-#include "../Toolbox/SlicesSorter.h"
-#include "../Volumes/DicomVolumeImage.h"
-#include "../Volumes/IVolumeSlicer.h"
-#include "IFetchingItemsSorter.h"
-#include "IFetchingStrategy.h"
-
-#include <boost/shared_ptr.hpp>
-
-namespace OrthancStone
-{
-  /**
-    This class is used to manage the progressive loading of a volume that
-    is stored in a Dicom series.
-  */
-  class OrthancSeriesVolumeProgressiveLoader : 
-    public ObserverBase<OrthancSeriesVolumeProgressiveLoader>,
-    public IObservable,
-    public IVolumeSlicer
-  {
-  private:
-    static const unsigned int LOW_QUALITY = 0;
-    static const unsigned int MIDDLE_QUALITY = 1;
-    static const unsigned int BEST_QUALITY = 2;
-    
-    class ExtractedSlice;
-    
-    /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */
-    class SeriesGeometry : public boost::noncopyable
-    {
-    private:
-      void CheckSlice(size_t index,
-                      const DicomInstanceParameters& reference) const;
-    
-      void CheckVolume() const;
-
-      void Clear();
-
-      void CheckSliceIndex(size_t index) const;
-
-      std::auto_ptr<VolumeImageGeometry>     geometry_;
-      std::vector<DicomInstanceParameters*>  slices_;
-      std::vector<uint64_t>                  slicesRevision_;
-
-    public:
-      ~SeriesGeometry()
-      {
-        Clear();
-      }
-
-      void ComputeGeometry(SlicesSorter& slices);
-
-      virtual bool HasGeometry() const
-      {
-        return geometry_.get() != NULL;
-      }
-
-      virtual const VolumeImageGeometry& GetImageGeometry() const;
-
-      const DicomInstanceParameters& GetSliceParameters(size_t index) const;
-
-      uint64_t GetSliceRevision(size_t index) const;
-
-      void IncrementSliceRevision(size_t index);
-    };
-
-    void ScheduleNextSliceDownload();
-
-    void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message);
-
-    void SetSliceContent(unsigned int sliceIndex,
-                         const Orthanc::ImageAccessor& image,
-                         unsigned int quality);
-
-    void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message);
-
-    void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message);
-
-    IOracle&                                      oracle_;
-    bool                                          active_;
-    unsigned int                                  simultaneousDownloads_;
-    SeriesGeometry                                seriesGeometry_;
-    boost::shared_ptr<DicomVolumeImage>           volume_;
-    std::auto_ptr<IFetchingItemsSorter::IFactory> sorter_;
-    std::auto_ptr<IFetchingStrategy>              strategy_;
-    std::vector<unsigned int>                     slicesQuality_;
-    bool                                          volumeImageReadyInHighQuality_;
-
-
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeImageReadyInHighQuality, OrthancSeriesVolumeProgressiveLoader);
-
-
-    OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
-                                         IOracle& oracle,
-                                         IObservable& oracleObservable);
-
-    virtual ~OrthancSeriesVolumeProgressiveLoader();
-
-    void SetSimultaneousDownloads(unsigned int count);
-
-    bool IsVolumeImageReadyInHighQuality() const
-    {
-      return volumeImageReadyInHighQuality_;
-    }
-
-    void LoadSeries(const std::string& seriesId);
-
-    /**
-    This getter is used by clients that do not receive the geometry through
-    subscribing, for instance if they are created or listening only AFTER the
-    "geometry loaded" message is broadcast 
-    */
-    bool HasGeometry() const
-    {
-      return seriesGeometry_.HasGeometry();
-    }
-
-    /**
-    Same remark as HasGeometry
-    */
-    const VolumeImageGeometry& GetImageGeometry() const
-    {
-      return seriesGeometry_.GetImageGeometry();
-    }
-
-    /**
-    When a slice is requested, the strategy algorithm (that defines the 
-    sequence of resources to be loaded from the server) is modified to 
-    take into account this request (this is done in the ExtractedSlice ctor)
-    */
-    virtual IExtractedSlice*
-      ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
-  };
-}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Sat Dec 07 18:45:37 2019 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Sun Dec 08 11:45:09 2019 +0100
@@ -353,6 +353,18 @@
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineMeasureTracker.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/RenderStyle.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader2.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderCache.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderCache.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderStateMachine.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderStateMachine.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.h
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.h
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/BaseWebService.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp
@@ -452,20 +464,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingItemsSorter.h
   ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.h
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.h
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader2.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader2.h
   ${ORTHANC_STONE_ROOT}/Framework/Loaders/IFetchingItemsSorter.h
   ${ORTHANC_STONE_ROOT}/Framework/Loaders/IFetchingStrategy.h
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderCache.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderCache.h
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.h
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.h
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h
   
   ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h