changeset 723:562cdd083b9e

Merge from default
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 21 May 2019 10:28:43 +0200
parents 28b9e3a54200 (current diff) d2c0e347ddc2 (diff)
children 5b16242cdc93
files Applications/Samples/SimpleViewer/Messages.h
diffstat 37 files changed, 974 insertions(+), 617 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewer/Messages.h	Tue May 21 10:27:54 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-#pragma once
-
-namespace SimpleViewer
-{
-  enum SimpleViewerMessageType
-  {
-    SimpleViewerMessageType_First = OrthancStone::MessageType_CustomMessage,
-    SimpleViewerMessageType_AppStatusUpdated
-  };
-}
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Tue May 21 10:27:54 2019 +0200
+++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.h	Tue May 21 10:28:43 2019 +0200
@@ -51,7 +51,6 @@
 #include "ThumbnailInteractor.h"
 #include "MainWidgetInteractor.h"
 #include "AppStatus.h"
-#include "Messages.h"
 
 using namespace OrthancStone;
 
@@ -67,13 +66,14 @@
   {
   public:
 
-    struct StatusUpdatedMessage : public BaseMessage<SimpleViewerMessageType_AppStatusUpdated>
+    struct StatusUpdatedMessage : public IMessage
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
       const AppStatus& status_;
 
       StatusUpdatedMessage(const AppStatus& status)
-        : BaseMessage(),
-          status_(status)
+        : status_(status)
       {
       }
     };
--- a/Framework/Layers/DicomSeriesVolumeSlicer.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/DicomSeriesVolumeSlicer.h	Tue May 21 10:28:43 2019 +0200
@@ -45,13 +45,13 @@
     private:
       const Orthanc::ImageAccessor&  frame_;
       SliceImageQuality              imageQuality_;
-      const Slice&                   slice_;
+      const Deprecated::Slice&                   slice_;
 
     public:
       FrameReadyMessage(DicomSeriesVolumeSlicer& origin,
                         const Orthanc::ImageAccessor& frame,
                         SliceImageQuality imageQuality,
-                        const Slice& slice) :
+                        const Deprecated::Slice& slice) :
         OriginMessage(origin),
         frame_(frame),
         imageQuality_(imageQuality),
@@ -69,7 +69,7 @@
         return imageQuality_;
       }
 
-      const Slice& GetSlice() const
+      const Deprecated::Slice& GetSlice() const
       {
         return slice_;
       }
@@ -107,7 +107,7 @@
       return loader_.GetSlicesCount();
     }
 
-    const Slice& GetSlice(size_t slice) const 
+    const Deprecated::Slice& GetSlice(size_t slice) const 
     {
       return loader_.GetSlice(slice);
     }
--- a/Framework/Layers/FrameRenderer.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/FrameRenderer.cpp	Tue May 21 10:28:43 2019 +0200
@@ -118,7 +118,7 @@
 
 
   ILayerRenderer* FrameRenderer::CreateRenderer(const Orthanc::ImageAccessor& frame,
-                                                const Slice& framePlane,
+                                                const Deprecated::Slice& framePlane,
                                                 bool isFullQuality)
   {
     if (frame.GetFormat() == Orthanc::PixelFormat_RGB24)
--- a/Framework/Layers/FrameRenderer.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/FrameRenderer.h	Tue May 21 10:28:43 2019 +0200
@@ -63,7 +63,7 @@
 
     // TODO: Avoid cloning the "frame"
     static ILayerRenderer* CreateRenderer(const Orthanc::ImageAccessor& frame,
-                                          const Slice& framePlane,
+                                          const Deprecated::Slice& framePlane,
                                           bool isFullQuality);
   };
 }
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/GrayscaleFrameRenderer.cpp	Tue May 21 10:28:43 2019 +0200
@@ -114,7 +114,7 @@
 
 
   GrayscaleFrameRenderer::GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame,
-                                                 const DicomFrameConverter& converter,
+                                                 const Deprecated::DicomFrameConverter& converter,
                                                  const CoordinateSystem3D& framePlane,
                                                  double pixelSpacingX,
                                                  double pixelSpacingY,
--- a/Framework/Layers/GrayscaleFrameRenderer.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/GrayscaleFrameRenderer.h	Tue May 21 10:28:43 2019 +0200
@@ -39,7 +39,7 @@
 
   public:
     GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame,
-                           const DicomFrameConverter& converter,
+                           const Deprecated::DicomFrameConverter& converter,
                            const CoordinateSystem3D& framePlane,
                            double pixelSpacingX,
                            double pixelSpacingY,
--- a/Framework/Layers/IVolumeSlicer.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/IVolumeSlicer.h	Tue May 21 10:28:43 2019 +0200
@@ -42,17 +42,17 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      const Slice& slice_;
+      const Deprecated::Slice& slice_;
 
     public:
       SliceContentChangedMessage(IVolumeSlicer& origin,
-                                 const Slice& slice) :
+                                 const Deprecated::Slice& slice) :
         OriginMessage(origin),
         slice_(slice)
       {
       }
 
-      const Slice& GetSlice() const
+      const Deprecated::Slice& GetSlice() const
       {
         return slice_;
       }
--- a/Framework/Layers/SliceOutlineRenderer.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Layers/SliceOutlineRenderer.h	Tue May 21 10:28:43 2019 +0200
@@ -37,7 +37,7 @@
     RenderStyle         style_;
 
   public:
-    SliceOutlineRenderer(const Slice& slice) :
+    SliceOutlineRenderer(const Deprecated::Slice& slice) :
       geometry_(slice.GetGeometry()),
       pixelSpacingX_(slice.GetPixelSpacingX()),
       pixelSpacingY_(slice.GetPixelSpacingY()),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/BasicFetchingItemsSorter.cpp	Tue May 21 10:28:43 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * 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 "BasicFetchingItemsSorter.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  BasicFetchingItemsSorter::BasicFetchingItemsSorter(unsigned int itemsCount) :
+    itemsCount_(itemsCount)
+  {
+    if (itemsCount == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void BasicFetchingItemsSorter::Sort(std::vector<unsigned int>& target,
+                                      unsigned int current)
+  {
+    if (current >= itemsCount_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    target.clear();
+    target.reserve(itemsCount_);
+    target.push_back(current);
+
+    const unsigned int countBelow = current;
+    const unsigned int countAbove = (itemsCount_ - 1) - current;
+    const unsigned int n = std::min(countBelow, countAbove);
+
+    for (unsigned int i = 1; i <= n; i++)
+    {
+      assert(current + i < itemsCount_ &&
+             current >= i);
+      target.push_back(current + i);
+      target.push_back(current - i);
+    }
+
+    for (unsigned int i = current - n; i > 0; i--)
+    {
+      target.push_back(i - 1);
+    }
+
+    for (unsigned int i = current + n + 1; i < itemsCount_; i++)
+    {
+      target.push_back(i);
+    }
+
+    assert(target.size() == itemsCount_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/BasicFetchingItemsSorter.h	Tue May 21 10:28:43 2019 +0200
@@ -0,0 +1,44 @@
+/**
+ * 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 "IFetchingItemsSorter.h"
+
+namespace OrthancStone
+{
+  class BasicFetchingItemsSorter : public IFetchingItemsSorter
+  {
+  private:
+    unsigned int  itemsCount_;
+
+  public:
+    BasicFetchingItemsSorter(unsigned int itemsCount);
+
+    virtual unsigned int GetItemsCount() const
+    {
+      return itemsCount_;
+    }
+
+    virtual void Sort(std::vector<unsigned int>& target,
+                      unsigned int current);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/BasicFetchingStrategy.cpp	Tue May 21 10:28:43 2019 +0200
@@ -0,0 +1,145 @@
+/**
+ * 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 "BasicFetchingStrategy.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  void BasicFetchingStrategy::Schedule(unsigned int item,
+                                       unsigned int quality)
+  {
+    assert(item < GetItemsCount() &&
+           quality <= maxQuality_);
+      
+    if (nextQuality_[item] <= quality)
+    {
+      content_.push_back(ContentItem(item, quality));
+    }
+  }
+    
+
+  BasicFetchingStrategy::BasicFetchingStrategy(IFetchingItemsSorter* sorter,   // Takes ownership
+                                               unsigned int maxQuality) :
+    sorter_(sorter),
+    maxQuality_(maxQuality),
+    position_(0),
+    blockSize_(2)
+  {
+    if (sorter == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    nextQuality_.resize(sorter_->GetItemsCount(), 0);   // Does not change along calls to "SetCurrent()"
+      
+    SetCurrent(0);
+  }
+
+
+  void BasicFetchingStrategy::SetBlockSize(unsigned int size)
+  {
+    if (size <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    blockSize_ = size;
+  }
+
+  
+  bool BasicFetchingStrategy::GetNext(unsigned int& item,
+                                      unsigned int& quality)
+  {
+    if (position_ >= content_.size())
+    {
+      return false;
+    }
+    else
+    {
+      item = content_[position_].GetItem();       
+      quality = content_[position_].GetQuality();
+
+      assert(nextQuality_[item] <= quality);
+      nextQuality_[item] = quality + 1;
+
+      position_ ++;
+      return true;
+    }
+  }
+
+  
+  void BasicFetchingStrategy::SetCurrent(unsigned int item)
+  {
+    // TODO - This function is O(N) complexity where "N" is the
+    // number of items times the max quality. Could use a LRU index.
+
+    position_ = 0;
+      
+    std::vector<unsigned int> v;
+    sorter_->Sort(v, item);
+
+    assert(v.size() == GetItemsCount());
+
+    if (v.size() == 0)
+    {
+      return;
+    }
+      
+    content_.clear();
+    content_.reserve(v.size() * maxQuality_);
+
+    Schedule(v.front(), maxQuality_);
+
+    for (unsigned int q = 0; q <= maxQuality_; q++)
+    {
+      unsigned int start = 1 + q * blockSize_;
+      unsigned int end = start + blockSize_;
+
+      if (q == maxQuality_ ||
+          end > v.size())
+      {
+        end = v.size();
+      }
+
+      unsigned int a = 0;
+      if (maxQuality_ >= q + 1)
+      {
+        a = maxQuality_ - q - 1;
+      }
+        
+      for (unsigned int j = a; j <= maxQuality_; j++)
+      {
+        for (unsigned int i = start; i < end; i++)
+        {
+          Schedule(v[i], j);
+        }
+      }
+    }
+  }
+
+  
+  void BasicFetchingStrategy::RecycleFurthest(unsigned int& item)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/BasicFetchingStrategy.h	Tue May 21 10:28:43 2019 +0200
@@ -0,0 +1,94 @@
+/**
+ * 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 "IFetchingItemsSorter.h"
+#include "IFetchingStrategy.h"
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class BasicFetchingStrategy : public IFetchingStrategy
+  {
+  private:
+    class ContentItem
+    {
+    private:
+      unsigned int  item_;
+      unsigned int  quality_;
+
+    public:
+      ContentItem(unsigned int item,
+           unsigned int quality) :
+        item_(item),
+        quality_(quality)
+      {
+      }
+
+      unsigned int GetItem() const
+      {
+        return item_;
+      }
+
+      unsigned int GetQuality() const
+      {
+        return quality_;
+      }
+    };
+
+    std::auto_ptr<IFetchingItemsSorter>  sorter_;
+    std::vector<unsigned int>            nextQuality_;
+    unsigned int                         maxQuality_;
+    std::vector<ContentItem>             content_;
+    size_t                               position_;
+    unsigned int                         blockSize_;
+
+    void Schedule(unsigned int item,
+                  unsigned int quality);
+    
+  public:
+    BasicFetchingStrategy(IFetchingItemsSorter* sorter,   // Takes ownership
+                          unsigned int maxQuality);
+
+    virtual unsigned int GetItemsCount() const
+    {
+      return sorter_->GetItemsCount();
+    }
+
+    virtual unsigned int GetMaxQuality() const
+    {
+      return maxQuality_;
+    }
+
+    // WARNING - This parameters is only considered during the next
+    // call to SetCurrent().
+    void SetBlockSize(unsigned int size);
+
+    virtual bool GetNext(unsigned int& item,
+                         unsigned int& quality);
+    
+    virtual void SetCurrent(unsigned int item);
+
+    virtual void RecycleFurthest(unsigned int& item);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/IFetchingItemsSorter.h	Tue May 21 10:28:43 2019 +0200
@@ -0,0 +1,42 @@
+/**
+ * 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/noncopyable.hpp>
+#include <vector>
+
+namespace OrthancStone
+{
+  class IFetchingItemsSorter : public boost::noncopyable
+  {
+  public:
+    virtual ~IFetchingItemsSorter()
+    {
+    }
+
+    virtual unsigned int GetItemsCount() const = 0;
+
+    // Sort a set of items given the current item
+    virtual void Sort(std::vector<unsigned int>& target,
+                      unsigned int current) = 0;
+  };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/IFetchingStrategy.h	Tue May 21 10:28:43 2019 +0200
@@ -0,0 +1,49 @@
+/**
+ * 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/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  class IFetchingStrategy : public boost::noncopyable
+  {
+  public:
+    virtual ~IFetchingStrategy()
+    {
+    }
+
+    virtual unsigned int GetItemsCount() const = 0;
+
+    virtual unsigned int GetMaxQuality() const = 0;
+
+    virtual bool GetNext(unsigned int& item,
+                         unsigned int& quality) = 0;
+
+    virtual void SetCurrent(unsigned int item) = 0;
+
+    // Ask the strategy to re-schedule the item with the lowest
+    // priority in the fetching order. This allows to know which item
+    // should be dropped from a cache.
+    virtual void RecycleFurthest(unsigned int& item) = 0;
+  };
+};
--- a/Framework/Radiography/RadiographyDicomLayer.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Radiography/RadiographyDicomLayer.cpp	Tue May 21 10:28:43 2019 +0200
@@ -54,7 +54,7 @@
 
   void RadiographyDicomLayer::SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
   {
-    converter_.reset(new DicomFrameConverter);
+    converter_.reset(new Deprecated::DicomFrameConverter);
     converter_->ReadParameters(dataset);
     ApplyConverter();
 
@@ -112,7 +112,7 @@
   }
 
 
-  void RadiographyDicomLayer::SetDicomFrameConverter(DicomFrameConverter* converter)
+  void RadiographyDicomLayer::SetDicomFrameConverter(Deprecated::DicomFrameConverter* converter)
   {
     converter_.reset(converter);
   }
--- a/Framework/Radiography/RadiographyDicomLayer.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Radiography/RadiographyDicomLayer.h	Tue May 21 10:28:43 2019 +0200
@@ -21,19 +21,20 @@
 
 #pragma once
 
+#include "../Toolbox/DicomFrameConverter.h"
 #include "RadiographyLayer.h"
+
 #include <Plugins/Samples/Common/FullOrthancDataset.h>
 
 namespace OrthancStone
 {
   class RadiographyScene;
-  class DicomFrameConverter;
 
   class RadiographyDicomLayer : public RadiographyLayer
   {
   private:
     std::auto_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
-    std::auto_ptr<DicomFrameConverter>     converter_;
+    std::auto_ptr<Deprecated::DicomFrameConverter>     converter_;
     std::auto_ptr<Orthanc::ImageAccessor>  converted_;  // Float32
     std::string                            instanceId_;
     unsigned int                           frame_;
@@ -65,10 +66,10 @@
 
     const Orthanc::ImageAccessor* GetSourceImage() const {return source_.get();}  // currently need this access to serialize scene in plain old data to send to a WASM worker
 
-    const DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker
+    const Deprecated::DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker
     
      // Takes ownership
-    void SetDicomFrameConverter(DicomFrameConverter* converter);
+    void SetDicomFrameConverter(Deprecated::DicomFrameConverter* converter);
 
     virtual void Render(Orthanc::ImageAccessor& buffer,
                         const AffineTransform2D& viewTransform,
--- a/Framework/Radiography/RadiographyScene.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.cpp	Tue May 21 10:28:43 2019 +0200
@@ -344,7 +344,7 @@
   RadiographyLayer& RadiographyScene::LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
                                                      const std::string& instance,
                                                      unsigned int frame,
-                                                     DicomFrameConverter* converter,  // takes ownership
+                                                     Deprecated::DicomFrameConverter* converter,  // takes ownership
                                                      PhotometricDisplayMode preferredPhotometricDisplayMode,
                                                      RadiographyLayer::Geometry* geometry)
   {
--- a/Framework/Radiography/RadiographyScene.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Radiography/RadiographyScene.h	Tue May 21 10:28:43 2019 +0200
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "RadiographyLayer.h"
+#include "../Toolbox/DicomFrameConverter.h"
 #include "../Toolbox/OrthancApiClient.h"
 #include "Framework/StoneEnumerations.h"
 #include "Core/Images/Image.h"
@@ -30,7 +31,6 @@
 namespace OrthancStone
 {
   class RadiographyDicomLayer;
-  class DicomFrameConverter;
 
   class RadiographyScene :
       public IObserver,
@@ -193,7 +193,7 @@
     virtual RadiographyLayer& LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership
                                              const std::string& instance,
                                              unsigned int frame,
-                                             DicomFrameConverter* converter,  // takes ownership
+                                             Deprecated::DicomFrameConverter* converter,  // takes ownership
                                              PhotometricDisplayMode preferredPhotometricDisplayMode,
                                              RadiographyLayer::Geometry* geometry);
 
--- a/Framework/Radiography/RadiographySceneReader.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Radiography/RadiographySceneReader.cpp	Tue May 21 10:28:43 2019 +0200
@@ -32,7 +32,7 @@
 {
 
   void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */,
-                                     DicomFrameConverter* dicomFrameConverter  /* takes ownership */,
+                                     Deprecated::DicomFrameConverter* dicomFrameConverter  /* takes ownership */,
                                      PhotometricDisplayMode preferredPhotometricDisplayMode
                                      )
   {
--- a/Framework/Radiography/RadiographySceneReader.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Radiography/RadiographySceneReader.h	Tue May 21 10:28:43 2019 +0200
@@ -42,7 +42,7 @@
     RadiographyScene&                               scene_;
     const Orthanc::FontRegistry*                    fontRegistry_;
     std::auto_ptr<Orthanc::ImageAccessor>           dicomImage_;
-    std::auto_ptr<DicomFrameConverter>              dicomFrameConverter_;
+    std::auto_ptr<Deprecated::DicomFrameConverter>              dicomFrameConverter_;
     PhotometricDisplayMode                          preferredPhotometricDisplayMode_;
 
   public:
@@ -55,7 +55,7 @@
     void Read(const Json::Value& input);
     void Read(const Json::Value& input,
               Orthanc::ImageAccessor* dicomImage, // takes ownership
-              DicomFrameConverter* dicomFrameConverter, // takes ownership
+              Deprecated::DicomFrameConverter* dicomFrameConverter, // takes ownership
               PhotometricDisplayMode preferredPhotometricDisplayMode
               );
 
--- a/Framework/SmartLoader.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/SmartLoader.cpp	Tue May 21 10:28:43 2019 +0200
@@ -61,7 +61,7 @@
     };
     
     unsigned int                    sliceIndex_;
-    std::auto_ptr<Slice>            slice_;
+    std::auto_ptr<Deprecated::Slice>            slice_;
     boost::shared_ptr<Orthanc::ImageAccessor>   image_;
     SliceImageQuality               effectiveQuality_;
     CachedSliceStatus               status_;
@@ -190,7 +190,7 @@
 
     // create the slice in the cache with "empty" data
     boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker()));
-    cachedSlice->slice_.reset(new Slice(instanceId, frame));
+    cachedSlice->slice_.reset(new Deprecated::Slice(instanceId, frame));
     cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad;
     std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame);
 
@@ -228,7 +228,7 @@
       dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin());
 
     // save/replace the slice in cache
-    const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount()
+    const Deprecated::Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount()
     std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
                               boost::lexical_cast<std::string>(slice.GetFrame()));
 
@@ -249,7 +249,7 @@
   void SmartLoader::OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message)
   {
     // save/replace the slice in cache
-    const Slice& slice = message.GetSlice();
+    const Deprecated::Slice& slice = message.GetSlice();
     std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
                               boost::lexical_cast<std::string>(slice.GetFrame()));
 
@@ -273,7 +273,7 @@
     const DicomSeriesVolumeSlicer& source =
       dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin());
     
-    const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() ?
+    const Deprecated::Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() ?
     std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + 
                               boost::lexical_cast<std::string>(slice.GetFrame()));
 
--- a/Framework/Toolbox/DicomFrameConverter.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Toolbox/DicomFrameConverter.cpp	Tue May 21 10:28:43 2019 +0200
@@ -28,7 +28,7 @@
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
 
-namespace OrthancStone
+namespace Deprecated
 {
   static const Orthanc::DicomTag IMAGE_TAGS[] =
   {
@@ -61,9 +61,9 @@
   {
     SetDefaultParameters();
 
-    Vector c, w;
-    if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-        LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
+    OrthancStone::Vector c, w;
+    if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+        OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
         c.size() > 0 && 
         w.size() > 0)
     {
--- a/Framework/Toolbox/DicomFrameConverter.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Toolbox/DicomFrameConverter.h	Tue May 21 10:28:43 2019 +0200
@@ -27,7 +27,7 @@
 
 #include <memory>
 
-namespace OrthancStone
+namespace Deprecated
 {
   /**
    * This class is responsible for converting the pixel format of a
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue May 21 10:28:43 2019 +0200
@@ -76,7 +76,7 @@
     Mode               mode_;
     unsigned int       frame_;
     unsigned int       sliceIndex_;
-    const Slice*       slice_;
+    const Deprecated::Slice*       slice_;
     std::string        instanceId_;
     SliceImageQuality  quality_;
 
@@ -105,7 +105,7 @@
       return sliceIndex_;
     }
 
-    const Slice& GetSlice() const
+    const Deprecated::Slice& GetSlice() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
@@ -143,7 +143,7 @@
     }
 
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
-                                         const Slice&  slice,
+                                         const Deprecated::Slice&  slice,
                                          SliceImageQuality quality)
     {
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
@@ -154,7 +154,7 @@
     }
 
     static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
-                                            const Slice&  slice)
+                                            const Deprecated::Slice&  slice)
     {
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage));
       tmp->sliceIndex_ = sliceIndex;
@@ -163,7 +163,7 @@
       return tmp.release();
     }
 
-    static Operation* DownloadDicomFile(const Slice&  slice)
+    static Operation* DownloadDicomFile(const Deprecated::Slice&  slice)
     {
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile));
       tmp->slice_ = &slice;
@@ -241,7 +241,7 @@
       
       for (unsigned int frame = 0; frame < frames; frame++)
       {
-        std::auto_ptr<Slice> slice(new Slice);
+        std::auto_ptr<Deprecated::Slice> slice(new Deprecated::Slice);
         if (slice->ParseOrthancFrame(dicom, instances[i], frame))
         {
           CoordinateSystem3D geometry = slice->GetGeometry();
@@ -277,7 +277,7 @@
     
     for (unsigned int frame = 0; frame < frames; frame++)
     {
-      std::auto_ptr<Slice> slice(new Slice);
+      std::auto_ptr<Deprecated::Slice> slice(new Deprecated::Slice);
       if (slice->ParseOrthancFrame(dicom, instanceId, frame))
       {
         CoordinateSystem3D geometry = slice->GetGeometry();
@@ -308,7 +308,7 @@
     Orthanc::DicomMap dicom;
     MessagingToolbox::ConvertDataset(dicom, dataset);
     
-    std::auto_ptr<Slice> slice(new Slice);
+    std::auto_ptr<Deprecated::Slice> slice(new Deprecated::Slice);
     if (slice->ParseOrthancFrame(dicom, instanceId, frame))
     {
       LOG(INFO) << "Loaded instance geometry " << instanceId;
@@ -721,14 +721,14 @@
   }
   
   
-  const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
+  const Deprecated::Slice& OrthancSlicesLoader::GetSlice(size_t index) const
   {
     if (state_ != State_GeometryReady)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
 
-    return dynamic_cast<const Slice&>(slices_.GetSlicePayload(index));
+    return dynamic_cast<const Deprecated::Slice&>(slices_.GetSlicePayload(index));
   }
   
   
@@ -746,7 +746,7 @@
   }
   
   
-  void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
+  void OrthancSlicesLoader::ScheduleSliceImagePng(const Deprecated::Slice& slice,
                                                   size_t index)
   {
     std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
@@ -781,7 +781,7 @@
         static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng));
 }
   
-  void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
+  void OrthancSlicesLoader::ScheduleSliceImagePam(const Deprecated::Slice& slice,
                                                   size_t index)
   {
     std::string uri = 
@@ -819,7 +819,7 @@
 
 
   
-  void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
+  void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Deprecated::Slice& slice,
                                                    size_t index,
                                                    SliceImageQuality quality)
   {
@@ -870,7 +870,7 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
     
-    const Slice& slice = GetSlice(index);
+    const Deprecated::Slice& slice = GetSlice(index);
     
     if (slice.HasOrthancDecoding())
     {
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue May 21 10:28:43 2019 +0200
@@ -46,14 +46,14 @@
       
     private:
       unsigned int                   sliceIndex_;
-      const Slice&                   slice_;
+      const Deprecated::Slice&                   slice_;
       const Orthanc::ImageAccessor&  image_;
       SliceImageQuality              effectiveQuality_;
 
     public:
       SliceImageReadyMessage(const OrthancSlicesLoader& origin,
                              unsigned int sliceIndex,
-                             const Slice& slice,
+                             const Deprecated::Slice& slice,
                              const Orthanc::ImageAccessor& image,
                              SliceImageQuality effectiveQuality) :
         OriginMessage(origin),
@@ -69,7 +69,7 @@
         return sliceIndex_;
       }
 
-      const Slice& GetSlice() const
+      const Deprecated::Slice& GetSlice() const
       {
         return slice_;
       }
@@ -91,14 +91,14 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      const Slice&       slice_;
+      const Deprecated::Slice&       slice_;
       unsigned int       sliceIndex_;
       SliceImageQuality  effectiveQuality_;
 
     public:
       SliceImageErrorMessage(const OrthancSlicesLoader& origin,
                              unsigned int sliceIndex,
-                             const Slice& slice,
+                             const Deprecated::Slice& slice,
                              SliceImageQuality effectiveQuality) :
         OriginMessage(origin),
         slice_(slice),
@@ -111,7 +111,7 @@
         return sliceIndex_;
       }
 
-      const Slice& GetSlice() const
+      const Deprecated::Slice& GetSlice() const
       {
         return slice_;
       }
@@ -170,13 +170,13 @@
 
     void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
-    void ScheduleSliceImagePng(const Slice& slice,
+    void ScheduleSliceImagePng(const Deprecated::Slice& slice,
                                size_t index);
 
-    void ScheduleSliceImagePam(const Slice& slice,
+    void ScheduleSliceImagePam(const Deprecated::Slice& slice,
                                size_t index);
 
-    void ScheduleSliceImageJpeg(const Slice& slice,
+    void ScheduleSliceImageJpeg(const Deprecated::Slice& slice,
                                 size_t index,
                                 SliceImageQuality quality);
 
@@ -198,7 +198,7 @@
 
     size_t GetSlicesCount() const;
 
-    const Slice& GetSlice(size_t index) const;
+    const Deprecated::Slice& GetSlice(size_t index) const;
 
     bool LookupSlice(size_t& index,
                      const CoordinateSystem3D& plane) const;
--- a/Framework/Toolbox/Slice.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Toolbox/Slice.cpp	Tue May 21 10:28:43 2019 +0200
@@ -30,7 +30,7 @@
 
 #include <boost/lexical_cast.hpp>
 
-namespace OrthancStone
+namespace Deprecated
 {
   static bool ParseDouble(double& target,
                           const std::string& source)
@@ -118,16 +118,16 @@
       return false;
     }
 
-    if (!LinearAlgebra::IsCloseToZero(offset0))
+    if (!OrthancStone::LinearAlgebra::IsCloseToZero(offset0))
     {
       LOG(ERROR) << "Invalid syntax";
       return false;
     }
 
-    geometry_ = CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(),
-                                   //+ 650 * geometry_.GetAxisX(),
-                                   geometry_.GetAxisX(),
-                                   geometry_.GetAxisY());
+    geometry_ = OrthancStone::CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(),
+                                                 //+ 650 * geometry_.GetAxisX(),
+                                                 geometry_.GetAxisX(),
+                                                 geometry_.GetAxisY());
 
     thickness_ = offset1 - offset0;
     if (thickness_ < 0)
@@ -185,19 +185,19 @@
     
     converter_.ReadParameters(dataset);
 
-    GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset);
+    OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset);
 
     std::string position, orientation;
     if (dataset.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
         dataset.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
     {
-      geometry_ = CoordinateSystem3D(position, orientation);
+      geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
 
       bool ok = true;
 
-      switch (StringToSopClassUid(sopClassUid_))
+      switch (OrthancStone::StringToSopClassUid(sopClassUid_))
       {
-        case SopClassUid_RTDose:
+        case OrthancStone::SopClassUid_RTDose:
           type_ = Type_OrthancRawFrame;
           ok = ComputeRTDoseGeometry(dataset, frame);
           break;
@@ -243,7 +243,7 @@
   }
 
   
-  const CoordinateSystem3D& Slice::GetGeometry() const
+  const OrthancStone::CoordinateSystem3D& Slice::GetGeometry() const
   {
     if (type_ == Type_Invalid)
     {
@@ -320,7 +320,7 @@
   }
 
 
-  bool Slice::ContainsPlane(const CoordinateSystem3D& plane) const
+  bool Slice::ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const
   {
     if (type_ == Type_Invalid)
     {
@@ -328,16 +328,16 @@
     }
 
     bool opposite;
-    return (GeometryToolbox::IsParallelOrOpposite(opposite,
-                                                  GetGeometry().GetNormal(),
-                                                  plane.GetNormal()) &&
-            LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
-                                  GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
-                                  thickness_ / 2.0));
+    return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite,
+                                                                GetGeometry().GetNormal(),
+                                                                plane.GetNormal()) &&
+            OrthancStone::LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()),
+                                                GetGeometry().ProjectAlongNormal(plane.GetOrigin()),
+                                                thickness_ / 2.0));
   }
 
   
-  void Slice::GetExtent(std::vector<Vector>& points) const
+  void Slice::GetExtent(std::vector<OrthancStone::Vector>& points) const
   {
     double sx = GetPixelSpacingX();
     double sy = GetPixelSpacingY();
--- a/Framework/Toolbox/Slice.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Toolbox/Slice.h	Tue May 21 10:28:43 2019 +0200
@@ -27,7 +27,7 @@
 #include <Core/DicomFormat/DicomImageInformation.h>
 #include <Core/IDynamicObject.h>
 
-namespace OrthancStone
+namespace Deprecated
 {
   // TODO - Remove this class
   class Slice :
@@ -51,7 +51,7 @@
     std::string          sopClassUid_;
     unsigned int         frame_;
     unsigned int         frameCount_;   // TODO : Redundant with "imageInformation_"
-    CoordinateSystem3D   geometry_;
+    OrthancStone::CoordinateSystem3D   geometry_;
     double               pixelSpacingX_;
     double               pixelSpacingY_;
     double               thickness_;
@@ -79,7 +79,7 @@
 
     // TODO Is this constructor the best way to go to tackle missing
     // layers within SliceViewerWidget?
-    Slice(const CoordinateSystem3D& plane,
+    Slice(const OrthancStone::CoordinateSystem3D& plane,
           double thickness) :
       type_(Type_Standalone),
       frame_(0),
@@ -93,7 +93,7 @@
     {      
     }
 
-    Slice(const CoordinateSystem3D& plane,
+    Slice(const OrthancStone::CoordinateSystem3D& plane,
           double pixelSpacingX,
           double pixelSpacingY,
           double thickness,
@@ -130,7 +130,7 @@
 
     unsigned int GetFrame() const;
 
-    const CoordinateSystem3D& GetGeometry() const;
+    const OrthancStone::CoordinateSystem3D& GetGeometry() const;
 
     double GetThickness() const;
 
@@ -144,9 +144,9 @@
 
     const DicomFrameConverter& GetConverter() const;
 
-    bool ContainsPlane(const CoordinateSystem3D& plane) const;
+    bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const;
 
-    void GetExtent(std::vector<Vector>& points) const;
+    void GetExtent(std::vector<OrthancStone::Vector>& points) const;
 
     const Orthanc::DicomImageInformation& GetImageInformation() const;
 
--- a/Framework/Volumes/ISlicedVolume.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Volumes/ISlicedVolume.h	Tue May 21 10:28:43 2019 +0200
@@ -41,12 +41,12 @@
       
     private:
       size_t        sliceIndex_;
-      const Slice&  slice_;
+      const Deprecated::Slice&  slice_;
       
     public:
       SliceContentChangedMessage(ISlicedVolume& origin,
                                  size_t sliceIndex,
-                                 const Slice& slice) :
+                                 const Deprecated::Slice& slice) :
         OriginMessage(origin),
         sliceIndex_(sliceIndex),
         slice_(slice)
@@ -58,7 +58,7 @@
         return sliceIndex_;
       }
 
-      const Slice& GetSlice() const
+      const Deprecated::Slice& GetSlice() const
       {
         return slice_;
       }
@@ -72,6 +72,6 @@
     
     virtual size_t GetSliceCount() const = 0;
 
-    virtual const Slice& GetSlice(size_t slice) const = 0;
+    virtual const Deprecated::Slice& GetSlice(size_t slice) const = 0;
   };
 }
--- a/Framework/Volumes/ImageBuffer3D.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Tue May 21 10:28:43 2019 +0200
@@ -259,7 +259,7 @@
 
 
   bool ImageBuffer3D::FitWindowingToRange(RenderStyle& style,
-                                          const DicomFrameConverter& converter) const
+                                          const Deprecated::DicomFrameConverter& converter) const
   {
     if (hasRange_)
     {
--- a/Framework/Volumes/ImageBuffer3D.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Volumes/ImageBuffer3D.h	Tue May 21 10:28:43 2019 +0200
@@ -122,7 +122,7 @@
                   float& maxValue) const;
 
     bool FitWindowingToRange(RenderStyle& style,
-                             const DicomFrameConverter& converter) const;
+                             const Deprecated::DicomFrameConverter& converter) const;
 
     uint8_t GetVoxelGrayscale8Unchecked(unsigned int x,
                                         unsigned int y,
--- a/Framework/Widgets/SliceViewerWidget.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Widgets/SliceViewerWidget.cpp	Tue May 21 10:28:43 2019 +0200
@@ -510,7 +510,7 @@
               << "," << plane.GetOrigin()[1]
               << "," << plane.GetOrigin()[2] << ")";
     
-    Slice displayedSlice(plane_, THIN_SLICE_THICKNESS);
+    Deprecated::Slice displayedSlice(plane_, THIN_SLICE_THICKNESS);
 
     //if (!displayedSlice.ContainsPlane(slice))
     {
--- a/Framework/Widgets/SliceViewerWidget.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/Widgets/SliceViewerWidget.h	Tue May 21 10:28:43 2019 +0200
@@ -46,17 +46,17 @@
       ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
       
     private:
-      const Slice& slice_;
+      const Deprecated::Slice& slice_;
 
     public:
       DisplayedSliceMessage(SliceViewerWidget& origin,
-                            const Slice& slice) :
+                            const Deprecated::Slice& slice) :
         OriginMessage(origin),
         slice_(slice)
       {
       }
 
-      const Slice& GetSlice() const
+      const Deprecated::Slice& GetSlice() const
       {
         return slice_;
       }
--- a/Framework/dev.h	Tue May 21 10:27:54 2019 +0200
+++ b/Framework/dev.h	Tue May 21 10:28:43 2019 +0200
@@ -64,8 +64,8 @@
     }
 
 
-    static bool IsCompatible(const Slice& a,
-                             const Slice& b)
+    static bool IsCompatible(const Deprecated::Slice& a,
+                             const Deprecated::Slice& b)
     {
       if (!GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(),
                                        b.GetGeometry().GetNormal()))
@@ -98,8 +98,8 @@
     }
 
 
-    static double GetDistance(const Slice& a,
-                              const Slice& b)
+    static double GetDistance(const Deprecated::Slice& a,
+                              const Deprecated::Slice& b)
     {
       return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) -
                   a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin()));
@@ -268,7 +268,7 @@
       return loader_.GetSlicesCount();
     }
 
-    virtual const Slice& GetSlice(size_t index) const
+    virtual const Deprecated::Slice& GetSlice(size_t index) const
     {
       return loader_.GetSlice(index);
     }
@@ -287,7 +287,7 @@
     }
 
     bool FitWindowingToRange(RenderStyle& style,
-                             const DicomFrameConverter& converter) const
+                             const Deprecated::DicomFrameConverter& converter) const
     {
       if (image_.get() == NULL)
       {
@@ -311,7 +311,7 @@
     double               pixelSpacingY_;
     double               sliceThickness_;
     CoordinateSystem3D   reference_;
-    DicomFrameConverter  converter_;
+    Deprecated::DicomFrameConverter  converter_;
 
     double ComputeAxialThickness(const OrthancVolumeImage& volume) const
     {
@@ -320,8 +320,8 @@
       size_t n = volume.GetSlicesCount();
       if (n > 1)
       {
-        const Slice& a = volume.GetSlice(0);
-        const Slice& b = volume.GetSlice(n - 1);
+        const Deprecated::Slice& a = volume.GetSlice(0);
+        const Deprecated::Slice& b = volume.GetSlice(n - 1);
         thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) -
                       reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) /
                      (static_cast<double>(n) - 1.0));
@@ -345,7 +345,7 @@
 
     void SetupAxial(const OrthancVolumeImage& volume)
     {
-      const Slice& axial = volume.GetSlice(0);
+      const Deprecated::Slice& axial = volume.GetSlice(0);
 
       width_ = axial.GetWidth();
       height_ = axial.GetHeight();
@@ -360,7 +360,7 @@
 
     void SetupCoronal(const OrthancVolumeImage& volume)
     {
-      const Slice& axial = volume.GetSlice(0);
+      const Deprecated::Slice& axial = volume.GetSlice(0);
       double axialThickness = ComputeAxialThickness(volume);
 
       width_ = axial.GetWidth();
@@ -382,7 +382,7 @@
 
     void SetupSagittal(const OrthancVolumeImage& volume)
     {
-      const Slice& axial = volume.GetSlice(0);
+      const Deprecated::Slice& axial = volume.GetSlice(0);
       double axialThickness = ComputeAxialThickness(volume);
 
       width_ = axial.GetHeight();
@@ -470,7 +470,7 @@
       }
     }
 
-    Slice* GetSlice(size_t slice) const
+    Deprecated::Slice* GetSlice(size_t slice) const
     {
       if (slice >= depth_)
       {
@@ -483,8 +483,8 @@
                                   reference_.GetAxisX(),
                                   reference_.GetAxisY());
 
-        return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_,
-                         width_, height_, converter_);
+        return new Deprecated::Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_,
+                                     width_, height_, converter_);
       }
     }
   };
@@ -500,12 +500,12 @@
     {
     private:
       const Orthanc::ImageAccessor&  frame_;
-      const Slice&                   slice_;
+      const Deprecated::Slice&                   slice_;
       bool                           isFullQuality_;
 
     public:
       RendererFactory(const Orthanc::ImageAccessor& frame,
-                      const Slice& slice,
+                      const Deprecated::Slice& slice,
                       bool isFullQuality) :
                       frame_(frame),
                       slice_(slice),
@@ -662,7 +662,7 @@
       {
         // As the slices of the volumic image are arranged in a box,
         // we only consider one single reference slice (the one with index 0).
-        std::auto_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0));
+        std::auto_ptr<Deprecated::Slice> slice(GetProjectionGeometry(projection).GetSlice(0));
         slice->GetExtent(points);
 
         return true;
@@ -693,7 +693,7 @@
             frame.reset(Orthanc::Image::Clone(reader.GetAccessor()));
           }
 
-          std::auto_ptr<Slice> slice(geometry.GetSlice(closest));
+          std::auto_ptr<Deprecated::Slice> slice(geometry.GetSlice(closest));
 
           RendererFactory factory(*frame, *slice, isFullQuality);
 
@@ -858,7 +858,7 @@
       {
         slice_ = slice;
 
-        std::auto_ptr<Slice> tmp(slices_->GetSlice(slice_));
+        std::auto_ptr<Deprecated::Slice> tmp(slices_->GetSlice(slice_));
         widget_.SetSlice(tmp->GetGeometry());
       }
     }
@@ -917,7 +917,7 @@
 
     virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice)
     {
-      Slice reference(viewportSlice, 0.001);
+      Deprecated::Slice reference(viewportSlice, 0.001);
 
       Vector p, d;
 
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue May 21 10:27:54 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue May 21 10:28:43 2019 +0200
@@ -362,7 +362,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PointerTypes.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h
-  
+
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphAlphabet.cpp
@@ -380,6 +380,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingItemsSorter.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
--- a/Samples/Sdl/Loader.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Tue May 21 10:28:43 2019 +0200
@@ -19,15 +19,19 @@
  **/
 
 // From Stone
+#include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
+#include "../../Framework/Loaders/BasicFetchingStrategy.h"
 #include "../../Framework/Messages/ICallable.h"
 #include "../../Framework/Messages/IMessage.h"
 #include "../../Framework/Messages/IObservable.h"
 #include "../../Framework/Messages/MessageBroker.h"
+#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
+#include "../../Framework/Scene2D/FloatTextureSceneLayer.h"
+#include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Toolbox/GeometryToolbox.h"
 #include "../../Framework/Toolbox/SlicesSorter.h"
 #include "../../Framework/Volumes/ImageBuffer3D.h"
-#include "../../Framework/Scene2D/Scene2D.h"
 
 // From Orthanc framework
 #include <Core/Compression/GzipCompressor.h>
@@ -399,6 +403,30 @@
       uri_ = uri;
     }
 
+    void SetInstanceUri(const std::string& instance,
+                        Orthanc::PixelFormat pixelFormat)
+    {
+      uri_ = "/instances/" + instance;
+          
+      switch (pixelFormat)
+      {
+        case Orthanc::PixelFormat_RGB24:
+          uri_ += "/preview";
+          break;
+      
+        case Orthanc::PixelFormat_Grayscale16:
+          uri_ += "/image-uint16";
+          break;
+      
+        case Orthanc::PixelFormat_SignedGrayscale16:
+          uri_ += "/image-int16";
+          break;
+      
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
     void SetHttpHeader(const std::string& key,
                        const std::string& value)
     {
@@ -788,7 +816,7 @@
       OrthancStone::Vector              frameOffsets_;
       bool                              isColor_;
       bool                              hasRescale_;
-      double                            rescaleOffset_;
+      double                            rescaleIntercept_;
       double                            rescaleSlope_;
       bool                              hasDefaultWindowing_;
       float                             defaultWindowingCenter_;
@@ -882,7 +910,7 @@
 
         double doseGridScaling;
 
-        if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
+        if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
             dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
         {
           hasRescale_ = true;
@@ -890,7 +918,7 @@
         else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
         {
           hasRescale_ = true;
-          rescaleOffset_ = 0;
+          rescaleIntercept_ = 0;
           rescaleSlope_ = doseGridScaling;
         }
         else
@@ -992,7 +1020,47 @@
         return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
                 distance <= thickness_ / 2.0);
       }
+
+      
+      void ApplyRescale(Orthanc::ImageAccessor& image,
+                        bool useDouble) const
+      {
+        if (image.GetFormat() != Orthanc::PixelFormat_Float32)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+        }
+    
+        if (hasRescale_)
+        {
+          const unsigned int width = image.GetWidth();
+          const unsigned int height = image.GetHeight();
+        
+          for (unsigned int y = 0; y < height; y++)
+          {
+            float* p = reinterpret_cast<float*>(image.GetRow(y));
+
+            if (useDouble)
+            {
+              // Slower, accurate implementation using double
+              for (unsigned int x = 0; x < width; x++, p++)
+              {
+                double value = static_cast<double>(*p);
+                *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
+              }
+            }
+            else
+            {
+              // Fast, approximate implementation using float
+              for (unsigned int x = 0; x < width; x++, p++)
+              {
+                *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
+              }
+            }
+          }
+        }
+      }
     };
+
     
     Data  data_;
 
@@ -1085,11 +1153,11 @@
       return data_.hasRescale_;
     }
 
-    double GetRescaleOffset() const
+    double GetRescaleIntercept() const
     {
       if (data_.hasRescale_)
       {
-        return data_.rescaleOffset_;
+        return data_.rescaleIntercept_;
       }
       else
       {
@@ -1142,6 +1210,58 @@
     {
       return data_.expectedPixelFormat_;
     }
+
+
+    OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const
+    {
+      assert(sizeof(float) == 4);
+
+      Orthanc::PixelFormat sourceFormat = source.GetFormat();
+
+      if (sourceFormat != GetExpectedPixelFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+
+      if (sourceFormat == Orthanc::PixelFormat_RGB24)
+      {
+        // This is the case of a color image. No conversion has to be done.
+        return new OrthancStone::ColorTextureSceneLayer(source);
+      }
+      else
+      {
+        if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
+            sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
+            sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+
+        std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture;
+        
+        {
+          // This is the case of a grayscale frame. Convert it to Float32.
+          std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                                     source.GetWidth(), 
+                                                                     source.GetHeight(),
+                                                                     false));
+          Orthanc::ImageProcessing::Convert(*converted, source);
+
+          // Correct rescale slope/intercept if need be
+          data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
+
+          texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted));
+        }
+
+        if (data_.hasDefaultWindowing_)
+        {
+          texture->SetCustomWindowing(data_.defaultWindowingCenter_,
+                                      data_.defaultWindowingWidth_);
+        }
+        
+        return texture.release();
+      }
+    }
   };
 
 
@@ -1152,6 +1272,7 @@
     std::vector<DicomInstanceParameters*>       slices_;
     uint64_t                                    revision_;
     std::vector<uint64_t>                       slicesRevision_;
+    std::vector<unsigned int>                   slicesQuality_;
 
     void CheckSlice(size_t index,
                     const DicomInstanceParameters& reference) const
@@ -1221,6 +1342,10 @@
         assert(slices_[i] != NULL);
         delete slices_[i];
       }
+
+      slices_.clear();
+      slicesRevision_.clear();
+      slicesQuality_.clear();
     }
 
 
@@ -1270,14 +1395,14 @@
       else
       {
         slices_.reserve(slices.GetSlicesCount());
-        slicesRevision_.resize(slices.GetSlicesCount());
+        slicesRevision_.resize(slices.GetSlicesCount(), 0);
+        slicesQuality_.resize(slices.GetSlicesCount(), 0);
 
         for (size_t i = 0; i < slices.GetSlicesCount(); i++)
         {
           const DicomInstanceParameters& slice =
             dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
           slices_.push_back(new DicomInstanceParameters(slice));
-          slicesRevision_[i] = 0;
         }
 
         CheckVolume();
@@ -1349,97 +1474,115 @@
     }
 
     void SetSliceContent(size_t index,
-                         const Orthanc::ImageAccessor& image)
+                         const Orthanc::ImageAccessor& image,
+                         unsigned int quality)
     {
       CheckSliceIndex(index);
-      
+
+      // If a better image quality is already available, don't update the content
+      if (quality >= slicesQuality_[index])
       {
-        OrthancStone::ImageBuffer3D::SliceWriter writer
-          (*image_, OrthancStone::VolumeProjection_Axial, index);
-        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
+        {
+          OrthancStone::ImageBuffer3D::SliceWriter writer
+            (*image_, OrthancStone::VolumeProjection_Axial, index);
+          Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
+        }
+        
+        revision_ ++;
+        slicesRevision_[index] += 1;
       }
+    }
+  };
 
-      revision_ ++;
-      slicesRevision_[index] += 1;
+
+
+  class IDicomVolumeSource : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomVolumeSource()
+    {
     }
+
+    virtual const DicomVolumeImage& GetVolume() const = 0;
+
+    virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0;
   };
   
   
 
-  class VolumeSeriesOrthancLoader : public OrthancStone::IObserver
+  class VolumeSeriesOrthancLoader :
+    public OrthancStone::IObserver,
+    public IDicomVolumeSource
   {
   private:
-    class MessageHandler : public Orthanc::IDynamicObject
+    static const unsigned int LOW_QUALITY = 0;
+    static const unsigned int MIDDLE_QUALITY = 1;
+    static const unsigned int BEST_QUALITY = 2;
+    
+    
+    static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
+    {
+      return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
+    }
+
+
+    void ScheduleNextSliceDownload()
     {
-    public:
-      virtual void Handle(const Json::Value& body) const
+      assert(strategy_.get() != NULL);
+      
+      unsigned int sliceIndex, quality;
+      
+      if (strategy_->GetNext(sliceIndex, quality))
       {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-      }
+        assert(quality <= BEST_QUALITY);
+
+        const DicomInstanceParameters& slice = volume_.GetSliceParameters(sliceIndex);
+          
+        const std::string& instance = slice.GetOrthancInstanceIdentifier();
+        if (instance.empty())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
 
-      virtual void Handle(const Orthanc::ImageAccessor& image) const
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        std::auto_ptr<Refactoring::OracleCommandWithPayload> command;
+        
+        if (quality == BEST_QUALITY)
+        {
+          std::auto_ptr<Refactoring::GetOrthancImageCommand> tmp(
+            new Refactoring::GetOrthancImageCommand);
+          tmp->SetHttpHeader("Accept-Encoding", "gzip");
+          tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+          tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());          
+          tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
+          command.reset(tmp.release());
+        }
+        else
+        {
+          std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> tmp(
+            new Refactoring::GetOrthancWebViewerJpegCommand);
+          tmp->SetHttpHeader("Accept-Encoding", "gzip");
+          tmp->SetInstance(instance);
+          tmp->SetQuality((quality == 0 ? 50 : 90));
+          tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
+          command.reset(tmp.release());
+        }
+
+        command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
+        oracle_.Schedule(*this, command.release());
       }
-    };
+    }
 
-    void Handle(const OrthancRestApiCommand::SuccessMessage& message)
+
+    void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
     {
       Json::Value body;
       message.ParseJsonBody(body);
-
+      
       if (body.type() != Json::objectValue)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
 
-      dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(body);
-    }
-
-    void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
-    {
-      dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message.GetImage());
-    }
-
-    void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
-    {
-      dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message.GetImage());
-    }
-
-
-    class LoadSliceImage : public MessageHandler
-    {
-    private:
-      DicomVolumeImage&   target_;
-      size_t              slice_;
-
-    public:
-      LoadSliceImage(DicomVolumeImage& target,
-                     size_t slice) :
-        target_(target),
-        slice_(slice)
-      {
-      }
-
-      virtual void Handle(const Orthanc::ImageAccessor& image) const
-      {
-        target_.SetSliceContent(slice_, image);
-      }
-    };
-
-
-    class LoadSeriesGeometryHandler : public MessageHandler
-    {
-    private:
-      VolumeSeriesOrthancLoader&  that_;
-
-    public:
-      LoadSeriesGeometryHandler(VolumeSeriesOrthancLoader& that) :
-      that_(that)
-      {
-      }
-
-      virtual void Handle(const Json::Value& body) const
       {
         Json::Value::Members instances = body.getMemberNames();
 
@@ -1457,90 +1600,60 @@
           slices.AddSlice(geometry, instance.release());
         }
 
-        that_.volume_.SetGeometry(slices);
-
-        {
-          OrthancStone::LinearAlgebra::Print(that_.volume_.GetImage().GetGeometry().GetCoordinates(0, 0, 0));
-          OrthancStone::LinearAlgebra::Print(that_.volume_.GetImage().GetGeometry().GetCoordinates(1, 1, 1));
-          return;
-        }
-
-        for (size_t i = 0; i < that_.volume_.GetSlicesCount(); i++)
-        {
-          const DicomInstanceParameters& slice = that_.volume_.GetSliceParameters(i);
-          
-          const std::string& instance = slice.GetOrthancInstanceIdentifier();
-          if (instance.empty())
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
+        volume_.SetGeometry(slices);
+      }
 
-#if 0
-          std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> command(
-            new Refactoring::GetOrthancWebViewerJpegCommand);
-          command->SetInstance(instance);
-          command->SetQuality(95);
-#else
-          std::string uri = "/instances/" + instance;
-          
-          switch (slice.GetExpectedPixelFormat())
-          {
-            case Orthanc::PixelFormat_RGB24:
-              uri += "/preview";
-              break;
-      
-            case Orthanc::PixelFormat_Grayscale16:
-              uri += "/image-uint16";
-              break;
-      
-            case Orthanc::PixelFormat_SignedGrayscale16:
-              uri += "/image-int16";
-              break;
-      
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-          }
-          
-          std::auto_ptr<Refactoring::GetOrthancImageCommand> command(
-            new Refactoring::GetOrthancImageCommand);
-          command->SetHttpHeader("Accept-Encoding", "gzip");
-          command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
-          command->SetUri(uri);
-#endif
+      if (volume_.GetSlicesCount() != 0)
+      {
+        strategy_.reset(new OrthancStone::BasicFetchingStrategy(
+                          new OrthancStone::BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY));
 
-          command->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
-          command->SetPayload(new LoadSliceImage(that_.volume_, i));
-
-          that_.oracle_.Schedule(that_, command.release());
+        for (unsigned int i = 0; i < 4; i++)   // Schedule up to 4 simultaneous downloads (TODO - parameter)
+        {
+          ScheduleNextSliceDownload();
         }
       }
-    };
+    }
+
+
+    void LoadBestQualitySliceContent(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
+    {      
+      volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()),
+                              message.GetImage(), BEST_QUALITY);
+
+      ScheduleNextSliceDownload();
+    }
 
 
-    class LoadInstanceGeometryHandler : public MessageHandler
+    void LoadJpegSliceContent(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
     {
-    private:
-      VolumeSeriesOrthancLoader&  that_;
+      unsigned int quality;
+      
+      switch (message.GetOrigin().GetQuality())
+      {
+        case 50:
+          quality = LOW_QUALITY;
+          break;
 
-    public:
-      LoadInstanceGeometryHandler(VolumeSeriesOrthancLoader& that) :
-      that_(that)
-      {
+        case 90:
+          quality = MIDDLE_QUALITY;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
+      
+      volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
 
-      virtual void Handle(const Json::Value& body) const
-      {
-        Orthanc::DicomMap dicom;
-        dicom.FromDicomAsJson(body);
-
-        DicomInstanceParameters instance(dicom);
-      }
-    };
+      ScheduleNextSliceDownload();
+    }
 
 
     IOracle&          oracle_;
     bool              active_;
     DicomVolumeImage  volume_;
+    
+    std::auto_ptr<OrthancStone::IFetchingStrategy>   strategy_;
 
   public:
     VolumeSeriesOrthancLoader(IOracle& oracle,
@@ -1551,15 +1664,15 @@
     {
       oracleObservable.RegisterObserverCallback(
         new OrthancStone::Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::Handle));
+        (*this, &VolumeSeriesOrthancLoader::LoadGeometry));
 
       oracleObservable.RegisterObserverCallback(
         new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::Handle));
+        (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent));
 
       oracleObservable.RegisterObserverCallback(
         new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
-        (*this, &VolumeSeriesOrthancLoader::Handle));
+        (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent));
     }
 
     void LoadSeries(const std::string& seriesId)
@@ -1573,123 +1686,159 @@
 
       std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
       command->SetUri("/series/" + seriesId + "/instances-tags");
-      command->SetPayload(new LoadSeriesGeometryHandler(*this));
 
       oracle_.Schedule(*this, command.release());
     }
+    
 
-    void LoadInstance(const std::string& instanceId)
+    virtual const DicomVolumeImage& GetVolume() const
     {
-      if (active_)
+      return volume_;
+    }
+
+    
+    virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex)
+    {
+      if (strategy_.get() == NULL)
       {
+        // Should have called GetVolume().HasGeometry() before
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
       }
-
-      active_ = true;
-
-      // Tag "3004-000c" is "Grid Frame Offset Vector", which is
-      // mandatory to read RT DOSE, but is too long to be returned by default
-
-      // TODO => Should be part of a second call if needed
-
-      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
-      command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
-      command->SetPayload(new LoadInstanceGeometryHandler(*this));
-
-      oracle_.Schedule(*this, command.release());
+      else
+      {
+        strategy_->SetCurrent(sliceIndex);
+      }
     }
   };
 
 
 
+#if 0
+  void LoadInstance(const std::string& instanceId)
+  {
+    if (active_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    active_ = true;
+
+    // Tag "3004-000c" is "Grid Frame Offset Vector", which is
+    // mandatory to read RT DOSE, but is too long to be returned by default
+
+    // TODO => Should be part of a second call if needed
+
+    std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
+    command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
+    command->SetPayload(new LoadInstanceGeometryHandler(*this));
+
+    oracle_.Schedule(*this, command.release());
+  }
+#endif
+
 
   /*  class VolumeSlicerBase : public IVolumeSlicer
-  {
-  private:
-    OrthancStone::Scene2D&            scene_;
-    int                               layerDepth_;
-    bool                              first_;
-    OrthancStone::CoordinateSystem3D  lastPlane_;
+      {
+      private:
+      OrthancStone::Scene2D&            scene_;
+      int                               layerDepth_;
+      bool                              first_;
+      OrthancStone::CoordinateSystem3D  lastPlane_;
 
-  protected:
-    bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const
-    {
+      protected:
+      bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const
+      {
       if (first_ ||
-          !OrthancStone::LinearAlgebra::IsCloseToZero(
-            boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal())))
+      !OrthancStone::LinearAlgebra::IsCloseToZero(
+      boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal())))
       {
-        // This is the first rendering, or the plane has not the same orientation
-        return false;
+      // This is the first rendering, or the plane has not the same orientation
+      return false;
       }
       else
       {
-        double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin());
-        double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin());
-        return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1);
+      double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin());
+      double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin());
+      return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1);
       }
-    }
+      }
 
-    void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
-    {
+      void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
+      {
       first_ = false;
       lastPlane_ = plane;
-    }
+      }
 
-    void SetLayer(OrthancStone::ISceneLayer* layer)
-    {
+      void SetLayer(OrthancStone::ISceneLayer* layer)
+      {
       scene_.SetLayer(layerDepth_, layer);
-    }
+      }
 
-    void DeleteLayer()
-    {
+      void DeleteLayer()
+      {
       scene_.DeleteLayer(layerDepth_);
-    }
+      }
     
-  public:
-    VolumeSlicerBase(OrthancStone::Scene2D& scene,
-                     int layerDepth) :
+      public:
+      VolumeSlicerBase(OrthancStone::Scene2D& scene,
+      int layerDepth) :
       scene_(scene),
       layerDepth_(layerDepth),
       first_(true)
-    {
-    }
-    };*/
+      {
+      }
+      };*/
   
 
 
-  class DicomVolumeSlicer : public IVolumeSlicer
+  class DicomVolumeMPRSlicer : public IVolumeSlicer
   {
   private:
+    bool                            linearInterpolation_;
     OrthancStone::Scene2D&          scene_;
     int                             layerDepth_;
-    const DicomVolumeImage&         volume_;
+    IDicomVolumeSource&             source_;
     bool                            first_;
     OrthancStone::VolumeProjection  lastProjection_;
     unsigned int                    lastSliceIndex_;
     uint64_t                        lastSliceRevision_;
 
   public:
-    DicomVolumeSlicer(OrthancStone::Scene2D& scene,
-                      int layerDepth,
-                      const DicomVolumeImage& volume) :
+    DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene,
+                         int layerDepth,
+                         IDicomVolumeSource& source) :
+      linearInterpolation_(false),
       scene_(scene),
       layerDepth_(layerDepth),
-      volume_(volume),
+      source_(source),
       first_(true)
     {
     }
+
+    void SetLinearInterpolation(bool enabled)
+    {
+      linearInterpolation_ = enabled;
+    }
+
+    bool IsLinearInterpolation() const
+    {
+      return linearInterpolation_;
+    }
     
     virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane)
     {
-      if (!volume_.HasGeometry())
+      if (!source_.GetVolume().HasGeometry() ||
+          source_.GetVolume().GetSlicesCount() == 0)
       {
         scene_.DeleteLayer(layerDepth_);
         return;
       }
 
+      const OrthancStone::VolumeImageGeometry& geometry = source_.GetVolume().GetImage().GetGeometry();
+
       OrthancStone::VolumeProjection projection;
       unsigned int sliceIndex;
-      if (!volume_.GetImage().GetGeometry().DetectSlice(projection, sliceIndex, plane))
+      if (!geometry.DetectSlice(projection, sliceIndex, plane))
       {
         // The cutting plane is neither axial, nor coronal, nor
         // sagittal. Could use "VolumeReslicer" here.
@@ -1700,35 +1849,67 @@
       uint64_t sliceRevision;
       if (projection == OrthancStone::VolumeProjection_Axial)
       {
-        sliceRevision = volume_.GetSliceRevision(sliceIndex);
+        sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex);
+
+        if (first_ ||
+            lastSliceIndex_ != sliceIndex)
+        {
+          // Reorder the prefetching queue
+          source_.NotifyAxialSliceAccessed(sliceIndex);
+        }
       }
       else
       {
         // For coronal and sagittal projections, we take the global
         // revision of the volume
-        sliceRevision = volume_.GetRevision();
+        sliceRevision = source_.GetVolume().GetRevision();
       }
 
       if (first_ ||
-          lastProjection_ == projection ||
-          lastSliceIndex_ == sliceIndex ||
-          lastSliceRevision_ == sliceRevision)
+          lastProjection_ != projection ||
+          lastSliceIndex_ != sliceIndex ||
+          lastSliceRevision_ != sliceRevision)
       {
-        // Eiter the viewport plane, or the content of the slice have not
+        // Either the viewport plane, or the content of the slice have not
         // changed since the last time the layer was set: Update is needed
 
         first_ = false;
         lastProjection_ = projection;
         lastSliceIndex_ = sliceIndex;
         lastSliceRevision_ = sliceRevision;
+
+        std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture;
         
         {
-          OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, sliceIndex);
+          const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters
+            (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0);
+
+          OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex);
+          texture.reset(parameters.CreateTexture(reader.GetAccessor()));
+        }
+
+        const OrthancStone::CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection);
+
+        double x0, y0, x1, y1;
+        system.ProjectPoint(x0, y0, system.GetOrigin());
+        system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX());
+        texture->SetOrigin(x0, y0);
 
-          // TODO: Convert the image to Float32 or RGB24
-          
-          // TODO: Set the layer
+        double dx = x1 - x0;
+        double dy = y1 - y0;
+        if (!OrthancStone::LinearAlgebra::IsCloseToZero(dx) ||
+            !OrthancStone::LinearAlgebra::IsCloseToZero(dy))
+        {
+          texture->SetAngle(atan2(dy, dx));
         }
+        
+        OrthancStone::Vector tmp;
+        geometry.GetVoxelDimensions(projection);
+        texture->SetPixelSpacing(tmp[0], tmp[1]);
+
+        texture->SetLinearInterpolation(linearInterpolation_);
+    
+        scene_.SetLayer(layerDepth_, texture.release());    
       }
     }
   };
@@ -1995,7 +2176,7 @@
 
   public:
     NativeOracle(IMessageEmitter& emitter) :
-    emitter_(emitter),
+      emitter_(emitter),
       state_(State_Setup),
       workers_(4)
     {
@@ -2107,8 +2288,8 @@
 
     public:
       ReaderLock(NativeApplicationContext& that) : 
-      that_(that),
-      lock_(that.mutex_)
+        that_(that),
+        lock_(that.mutex_)
       {
       }
     };
@@ -2122,8 +2303,8 @@
 
     public:
       WriterLock(NativeApplicationContext& that) : 
-      that_(that),
-      lock_(that.mutex_)
+        that_(that),
+        lock_(that.mutex_)
       {
       }
 
@@ -2193,7 +2374,7 @@
 
     oracle.RegisterObserverCallback
       (new OrthancStone::Callable
-      <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
 
     oracle.RegisterObserverCallback
       (new OrthancStone::Callable
@@ -2282,7 +2463,7 @@
 
   // 2017-11-17-Anonymized
   //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
-  loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
+  //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
 
   // Delphine
   loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e");  // CT
--- a/UnitTestsSources/TestStrategy.cpp	Tue May 21 10:27:54 2019 +0200
+++ b/UnitTestsSources/TestStrategy.cpp	Tue May 21 10:28:43 2019 +0200
@@ -21,275 +21,10 @@
 
 #include "gtest/gtest.h"
 
-#include <Core/OrthancException.h>
-
-#include <boost/noncopyable.hpp>
-#include <vector>
-
-namespace OrthancStone
-{
-  class IFetchingStrategy : public boost::noncopyable
-  {
-  public:
-    virtual ~IFetchingStrategy()
-    {
-    }
-
-    virtual unsigned int GetItemsCount() const = 0;
-
-    virtual unsigned int GetMaxQuality() const = 0;
-
-    virtual bool GetNext(unsigned int& item,
-                         unsigned int& quality) = 0;
-
-    virtual void SetCurrent(unsigned int item) = 0;
-
-    // Ask the strategy to re-schedule the item with the lowest
-    // priority in the fetching order. This allows to know which item
-    // should be dropped from a cache.
-    virtual void RecycleFurthest(unsigned int& item) = 0;
-  };
-
-
-  class IFetchingItemsSorter : public boost::noncopyable
-  {
-  public:
-    virtual ~IFetchingItemsSorter()
-    {
-    }
-
-    virtual unsigned int GetItemsCount() const = 0;
-
-    // Sort a set of items given the current item
-    virtual void Sort(std::vector<unsigned int>& target,
-                      unsigned int current) = 0;
-  };
-
-
-
-  class BasicFetchingItemsSorter : public IFetchingItemsSorter
-  {
-  private:
-    unsigned int  itemsCount_;
-
-  public:
-    BasicFetchingItemsSorter(unsigned int itemsCount) :
-      itemsCount_(itemsCount)
-    {
-      if (itemsCount == 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    virtual unsigned int GetItemsCount() const
-    {
-      return itemsCount_;
-    }
-
-    virtual void Sort(std::vector<unsigned int>& target,
-                      unsigned int current)
-    {
-      if (current >= itemsCount_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      target.clear();
-      target.reserve(itemsCount_);
-      target.push_back(current);
-
-      const unsigned int countBelow = current;
-      const unsigned int countAbove = (itemsCount_ - 1) - current;
-      const unsigned int n = std::min(countBelow, countAbove);
-
-      for (unsigned int i = 1; i <= n; i++)
-      {
-        assert(current + i < itemsCount_ &&
-               current >= i);
-        target.push_back(current + i);
-        target.push_back(current - i);
-      }
-
-      for (unsigned int i = current - n; i > 0; i--)
-      {
-        target.push_back(i - 1);
-      }
-
-      for (unsigned int i = current + n + 1; i < itemsCount_; i++)
-      {
-        target.push_back(i);
-      }
-
-      assert(target.size() == itemsCount_);
-    }
-  };
-
-
-  class BasicFetchingStrategy : public IFetchingStrategy
-  {
-  private:
-    class ContentItem
-    {
-    private:
-      unsigned int  item_;
-      unsigned int  quality_;
-
-    public:
-      ContentItem(unsigned int item,
-           unsigned int quality) :
-        item_(item),
-        quality_(quality)
-      {
-      }
-
-      unsigned int GetItem() const
-      {
-        return item_;
-      }
-
-      unsigned int GetQuality() const
-      {
-        return quality_;
-      }
-    };
+#include "../Framework/Loaders/BasicFetchingStrategy.h"
+#include "../Framework/Loaders/BasicFetchingItemsSorter.h"
 
-    std::auto_ptr<IFetchingItemsSorter>  sorter_;
-    std::vector<unsigned int>            nextQuality_;
-    unsigned int                         maxQuality_;
-    std::vector<ContentItem>             content_;
-    size_t                               position_;
-    unsigned int                         blockSize_;
-
-    void Schedule(unsigned int item,
-                unsigned int quality)
-    {
-      assert(item < GetItemsCount() &&
-             quality <= maxQuality_);
-      
-      if (nextQuality_[item] <= quality)
-      {
-        content_.push_back(ContentItem(item, quality));
-      }
-    }
-    
-  public:
-    BasicFetchingStrategy(IFetchingItemsSorter* sorter,   // Takes ownership
-                          unsigned int maxQuality) :
-      sorter_(sorter),
-      maxQuality_(maxQuality),
-      position_(0),
-      blockSize_(2)
-    {
-      if (sorter == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-
-      nextQuality_.resize(sorter_->GetItemsCount(), 0),   // Does not change along calls to "SetCurrent()"
-      
-      SetCurrent(0);
-    }
-
-    virtual unsigned int GetItemsCount() const
-    {
-      return sorter_->GetItemsCount();
-    }
-
-    virtual unsigned int GetMaxQuality() const
-    {
-      return maxQuality_;
-    }
-
-    void SetBlockSize(unsigned int size)
-    {
-      if (size <= 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      blockSize_ = size;
-    }
-
-    virtual bool GetNext(unsigned int& item,
-                         unsigned int& quality)
-    {
-      if (position_ >= content_.size())
-      {
-        return false;
-      }
-      else
-      {
-        item = content_[position_].GetItem();       
-        quality = content_[position_].GetQuality();
-
-        assert(nextQuality_[item] <= quality);
-        nextQuality_[item] = quality + 1;
-
-        position_ ++;
-        return true;
-      }
-    }
-    
-    virtual void SetCurrent(unsigned int item)
-    {
-      // TODO - This function is O(N) complexity where "N" is the
-      // number of items times the max quality. Could use a LRU index.
-
-      position_ = 0;
-      
-      std::vector<unsigned int> v;
-      sorter_->Sort(v, item);
-
-      assert(v.size() == GetItemsCount());
-
-      if (v.size() == 0)
-      {
-        return;
-      }
-      
-      content_.clear();
-      content_.reserve(v.size() * maxQuality_);
-
-      Schedule(v.front(), maxQuality_);
-
-      for (unsigned int q = 0; q <= maxQuality_; q++)
-      {
-        unsigned int start = 1 + q * blockSize_;
-        unsigned int end = start + blockSize_;
-
-        if (q == maxQuality_ ||
-            end > v.size())
-        {
-          end = v.size();
-        }
-
-        unsigned int a = 0;
-        if (maxQuality_ >= q + 1)
-        {
-          a = maxQuality_ - q - 1;
-        }
-        
-        for (unsigned int j = a; j <= maxQuality_; j++)
-        {
-          for (unsigned int i = start; i < end; i++)
-          {
-            Schedule(v[i], j);
-          }
-        }
-      }
-    }
-
-    // Ask the strategy to re-schedule the item with the lowest
-    // priority in the fetching order. This allows to know which item
-    // should be dropped from a cache.
-    virtual void RecycleFurthest(unsigned int& item)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  };
-}
-
+#include <Core/OrthancException.h>
 
 
 namespace