changeset 93:5945e81734a3 wasm

decoding of JPEG images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 29 May 2017 17:28:31 +0200
parents 961ee171d933
children 7b14c12a3be5
files Framework/Enumerations.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Widgets/LayerWidget.cpp UnitTestsSources/UnitTestsMain.cpp
diffstat 7 files changed, 384 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Enumerations.h	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Enumerations.h	Mon May 29 17:28:31 2017 +0200
@@ -71,4 +71,12 @@
     KeyboardModifiers_Control = (1 << 1),
     KeyboardModifiers_Alt = (1 << 2)
   };
+
+  enum SliceImageQuality
+  {
+    SliceImageQuality_Full,
+    SliceImageQuality_Jpeg50,
+    SliceImageQuality_Jpeg90,
+    SliceImageQuality_Jpeg95
+  };
 }
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Mon May 29 17:28:31 2017 +0200
@@ -29,29 +29,18 @@
 
 #include <boost/lexical_cast.hpp>
 
-
-// TODO REMOVE THIS
-#include "../Widgets/LayerWidget.h"
-
 namespace OrthancStone
 {
   void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader)
   {
     if (loader.GetSliceCount() > 0)
     {
-      // Make sure all the slices are parallel. TODO Alleviate this constraint
-      for (size_t i = 1; i < loader.GetSliceCount(); i++)
-      {
-        if (!GeometryToolbox::IsParallel(loader.GetSlice(i).GetGeometry().GetNormal(),
-                                         loader.GetSlice(0).GetGeometry().GetNormal()))
-        {
-          LayerSourceBase::NotifyGeometryError();
-          return;
-        }
-      }
+      LayerSourceBase::NotifyGeometryReady();
     }
-
-    LayerSourceBase::NotifyGeometryReady();
+    else
+    {
+      LayerSourceBase::NotifyGeometryError();
+    }
   }
 
   void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader)
@@ -62,14 +51,17 @@
   void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader,
                                                       unsigned int sliceIndex,
                                                       const Slice& slice,
-                                                      Orthanc::ImageAccessor* image)
+                                                      Orthanc::ImageAccessor* image,
+                                                      SliceImageQuality quality)
   {
-    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image, slice, true), slice);
+    bool isFull = (quality == SliceImageQuality_Full);
+    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image, slice, isFull), slice);
   }
 
   void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader,
                                                       unsigned int sliceIndex,
-                                                      const Slice& slice)
+                                                      const Slice& slice,
+                                                      SliceImageQuality quality)
   {
     LayerSourceBase::NotifyLayerError(slice.GetGeometry());
   }
@@ -133,7 +125,8 @@
     {
       if (loader_.LookupSlice(index, viewportSlice))
       {
-        loader_.ScheduleLoadSliceImage(index);
+        //loader_.ScheduleLoadSliceImage(index, SliceImageQuality_Full);
+        loader_.ScheduleLoadSliceImage(index, SliceImageQuality_Jpeg50);
       }
       else
       {
--- a/Framework/Layers/OrthancFrameLayerSource.h	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Mon May 29 17:28:31 2017 +0200
@@ -43,11 +43,13 @@
     virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
-                                       Orthanc::ImageAccessor* image);
+                                       Orthanc::ImageAccessor* image,
+                                       SliceImageQuality quality);
 
     virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
-                                       const Slice& slice);
+                                       const Slice& slice,
+                                       SliceImageQuality quality);
 
   public:
     OrthancFrameLayerSource(IWebService& orthanc,
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon May 29 17:28:31 2017 +0200
@@ -23,9 +23,13 @@
 
 #include "MessagingToolbox.h"
 
+#include "../../Resources/Orthanc/Core/Images/Image.h"
+#include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
+#include "../../Resources/Orthanc/Core/Images/JpegReader.h"
 #include "../../Resources/Orthanc/Core/Images/PngReader.h"
 #include "../../Resources/Orthanc/Core/Logging.h"
 #include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Core/Toolbox.h"
 #include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
 #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
 
@@ -36,11 +40,12 @@
   class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
   {
   private:
-    Mode          mode_;
-    unsigned int  frame_;
-    unsigned int  sliceIndex_;
-    const Slice*  slice_;
-    std::string   instanceId_;
+    Mode               mode_;
+    unsigned int       frame_;
+    unsigned int       sliceIndex_;
+    const Slice*       slice_;
+    std::string        instanceId_;
+    SliceImageQuality  quality_;
 
     Operation(Mode mode) :
       mode_(mode)
@@ -53,6 +58,12 @@
       return mode_;
     }
 
+    SliceImageQuality GetQuality() const
+    {
+      assert(mode_ == Mode_LoadImage);
+      return quality_;
+    }
+
     unsigned int GetSliceIndex() const
     {
       assert(mode_ == Mode_LoadImage);
@@ -92,11 +103,13 @@
     }
 
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
-                                         const Slice&  slice)
+                                         const Slice&  slice,
+                                         SliceImageQuality quality)
     {
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
       tmp->sliceIndex_ = sliceIndex;
       tmp->slice_ = &slice;
+      tmp->quality_ = quality;
       return tmp.release();
     }
   };
@@ -132,7 +145,22 @@
           break;
 
         case Mode_LoadImage:
-          that_.ParseSliceImage(*operation, answer, answerSize);
+          switch (operation->GetQuality())
+          {
+            case SliceImageQuality_Full:
+              that_.ParseSliceImagePng(*operation, answer, answerSize);
+              break;
+
+            case SliceImageQuality_Jpeg50:
+            case SliceImageQuality_Jpeg90:
+            case SliceImageQuality_Jpeg95:
+              that_.ParseSliceImageJpeg(*operation, answer, answerSize);
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+                
           break;
 
         default:
@@ -155,7 +183,8 @@
 
         case Mode_LoadImage:
           that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
-                                                    operation->GetSlice());
+                                                    operation->GetSlice(),
+                                                    operation->GetQuality());
           break;
           
         default:
@@ -164,6 +193,29 @@
     }     
   };
 
+
+  
+  void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
+                                                    Orthanc::ImageAccessor* image) const
+  {
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      userCallback_.NotifySliceImageReady
+        (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
+    }
+  }
+
+  
+  void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
+  {
+    userCallback_.NotifySliceImageError
+      (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
+  }
+
   
   void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
                                                 size_t size)
@@ -256,18 +308,32 @@
   }
 
 
-  void OrthancSlicesLoader::ParseSliceImage(const Operation& operation,
-                                            const void* answer,
-                                            size_t size)
+  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
+                                               const void* answer,
+                                               size_t size)
   {
     std::auto_ptr<Orthanc::PngReader>  image(new Orthanc::PngReader);
-    image->ReadFromMemory(answer, size);
 
-    bool ok = (image->GetWidth() == operation.GetSlice().GetWidth() ||
-               image->GetHeight() == operation.GetSlice().GetHeight());
+    bool ok = false;
+    
+    try
+    {
+      image->ReadFromMemory(answer, size);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
+        image->GetHeight() != operation.GetSlice().GetHeight())
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
       
-    if (ok &&
-        operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
         Orthanc::PixelFormat_SignedGrayscale16)
     {
       if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
@@ -276,20 +342,149 @@
       }
       else
       {
-        ok = false;
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+
+    NotifySliceImageSuccess(operation, image.release());
+  }
+    
+    
+  void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
+                                                const void* answer,
+                                                size_t size)
+  {
+    Json::Value encoded;
+    if (!MessagingToolbox::ParseJson(encoded, answer, size) ||
+        encoded.type() != Json::objectValue ||
+        !encoded.isMember("Orthanc") ||
+        encoded["Orthanc"].type() != Json::objectValue)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    Json::Value& info = encoded["Orthanc"];
+    if (!info.isMember("PixelData") ||
+        !info.isMember("Stretched") ||
+        !info.isMember("Compression") ||
+        info["Compression"].type() != Json::stringValue ||
+        info["PixelData"].type() != Json::stringValue ||
+        info["Stretched"].type() != Json::booleanValue ||
+        info["Compression"].asString() != "Jpeg")
+    {
+      NotifySliceImageError(operation);
+      return;
+    }          
+
+    bool isSigned = false;
+    bool isStretched = info["Stretched"].asBool();
+
+    if (info.isMember("IsSigned"))
+    {
+      if (info["IsSigned"].type() != Json::booleanValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }          
+      else
+      {
+        isSigned = info["IsSigned"].asBool();
       }
     }
 
-    if (ok)
+    std::string jpeg;
+    Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+
+    std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
+    try
+    {
+      reader->ReadFromMemory(jpeg);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    Orthanc::PixelFormat expectedFormat =
+      operation.GetSlice().GetConverter().GetExpectedPixelFormat();
+
+    if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
     {
-      userCallback_.NotifySliceImageReady(*this, operation.GetSliceIndex(),
-                                          operation.GetSlice(), image.release());
+      if (expectedFormat != Orthanc::PixelFormat_RGB24)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+
+      if (isSigned || isStretched)
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        NotifySliceImageSuccess(operation, reader.release());
+        return;
+      }
+    }
+
+    if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+    {
+      NotifySliceImageError(operation);
+      return;
     }
-    else
+
+    if (!isStretched)
+    {
+      if (expectedFormat != reader->GetFormat())
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+      else
+      {
+        NotifySliceImageSuccess(operation, reader.release());
+        return;
+      }
+    }
+
+    int32_t stretchLow = 0;
+    int32_t stretchHigh = 0;
+
+    if (!info.isMember("StretchLow") ||
+        !info.isMember("StretchHigh") ||
+        info["StretchLow"].type() != Json::intValue ||
+        info["StretchHigh"].type() != Json::intValue)
     {
-      userCallback_.NotifySliceImageError(*this, operation.GetSliceIndex(),
-                                          operation.GetSlice());
+      NotifySliceImageError(operation);
+      return;
     }
+
+    stretchLow = info["StretchLow"].asInt();
+    stretchHigh = info["StretchHigh"].asInt();
+
+    if (stretchLow < -32768 ||
+        stretchHigh > 65535 ||
+        (stretchLow < 0 && stretchHigh > 32767))
+    {
+      // This range cannot be represented with a uint16_t or an int16_t
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+    std::auto_ptr<Orthanc::ImageAccessor> image
+      (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
+
+    float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    float offset = static_cast<float>(stretchLow) / scaling;
+      
+    Orthanc::ImageProcessing::Convert(*image, *reader);
+    Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling);
+
+    NotifySliceImageSuccess(operation, image.release());
   }
     
     
@@ -375,38 +570,86 @@
   }
   
 
-  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index)
+  void OrthancSlicesLoader::ScheduleSliceImagePng(size_t index)
+  {
+    const Slice& slice = GetSlice(index);
+
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    switch (slice.GetConverter().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);
+    }
+
+    orthanc_.ScheduleGetRequest(*webCallback_, uri,
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
+  }
+
+
+  void OrthancSlicesLoader::ScheduleSliceImageJpeg(size_t index,
+                                                   SliceImageQuality quality)
+  {
+    unsigned int value;
+
+    switch (quality)
+    {
+      case SliceImageQuality_Jpeg50:
+        value = 50;
+        break;
+    
+      case SliceImageQuality_Jpeg90:
+        value = 90;
+        break;
+    
+      case SliceImageQuality_Jpeg95:
+        value = 95;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    // This requires the official Web viewer plugin to be installed!
+    const Slice& slice = GetSlice(index);
+    std::string uri = ("web-viewer/instances/jpeg" + 
+                       boost::lexical_cast<std::string>(value) + 
+                       "-" + slice.GetOrthancInstanceId() + "_" + 
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    orthanc_.ScheduleGetRequest(*webCallback_, uri,
+                                Operation::DownloadSliceImage(index, slice, quality));
+  }
+
+
+  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
+                                                   SliceImageQuality quality)
   {
     if (state_ != State_GeometryReady)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
+
+    if (quality == SliceImageQuality_Full)
+    {
+      ScheduleSliceImagePng(index);
+    }
     else
     {
-      const Slice& slice = GetSlice(index);
-
-      std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + 
-                         boost::lexical_cast<std::string>(slice.GetFrame()));
-
-      switch (slice.GetConverter().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);
-      }
-
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSliceImage(index, slice));
+      ScheduleSliceImageJpeg(index, quality);
     }
   }
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Mon May 29 17:28:31 2017 +0200
@@ -23,6 +23,7 @@
 
 #include "IWebService.h"
 #include "SlicesSorter.h"
+#include "../Enumerations.h"
 
 #include <boost/shared_ptr.hpp>
 
@@ -45,11 +46,13 @@
       virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
                                          unsigned int sliceIndex,
                                          const Slice& slice,
-                                         Orthanc::ImageAccessor* image) = 0;
+                                         Orthanc::ImageAccessor* image,
+                                         SliceImageQuality quality) = 0;
 
       virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
                                          unsigned int sliceIndex,
-                                         const Slice& slice) = 0;
+                                         const Slice& slice,
+                                         SliceImageQuality quality) = 0;
     };
     
   private:
@@ -78,7 +81,11 @@
     State         state_;
     SlicesSorter  slices_;
 
-
+    void NotifySliceImageSuccess(const Operation& operation,
+                                 Orthanc::ImageAccessor* image) const;
+  
+    void NotifySliceImageError(const Operation& operation) const;
+    
     void ParseSeriesGeometry(const void* answer,
                              size_t size);
 
@@ -87,10 +94,18 @@
                                const void* answer,
                                size_t size);
 
-    void ParseSliceImage(const Operation& operation,
-                         const void* answer,
-                         size_t size);
+    void ParseSliceImagePng(const Operation& operation,
+                            const void* answer,
+                            size_t size);
+
+    void ParseSliceImageJpeg(const Operation& operation,
+                             const void* answer,
+                             size_t size);
+
+    void ScheduleSliceImagePng(size_t index);
     
+    void ScheduleSliceImageJpeg(size_t index,
+                                SliceImageQuality quality);
     
   public:
     OrthancSlicesLoader(ICallback& callback,
@@ -110,6 +125,7 @@
     bool LookupSlice(size_t& index,
                      const SliceGeometry& plane) const;
 
-    void ScheduleLoadSliceImage(size_t index);
+    void ScheduleLoadSliceImage(size_t index,
+                                SliceImageQuality quality);
   };
 }
--- a/Framework/Widgets/LayerWidget.cpp	Mon May 29 11:42:53 2017 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Mon May 29 17:28:31 2017 +0200
@@ -89,6 +89,11 @@
       return slice_;
     }
 
+    bool HasRenderer(size_t index)
+    {
+      return renderers_[index] != NULL;
+    }
+
     bool IsComplete() const
     {
       return countMissing_ == 0;
@@ -208,7 +213,8 @@
       assert(layers_[i] != NULL);
       if (GetAndFixExtent(ax, ay, bx, by, *layers_[i]))
       {
-        LOG(INFO) << "Extent of layer " << i << ": (" << ax << "," << ay << ")->(" << bx << "," << by << ")";
+        LOG(INFO) << "Extent of layer " << i << ": (" << ax << "," << ay
+                  << ")->(" << bx << "," << by << ")";
 
         if (first)
         {
@@ -379,6 +385,10 @@
 
   void LayerWidget::SetSlice(const SliceGeometry& slice)
   {
+    LOG(INFO) << "Setting slice origin: (" << slice.GetOrigin()[0]
+              << "," << slice.GetOrigin()[1]
+              << "," << slice.GetOrigin()[2] << ")";
+    
     Slice displayedSlice(slice_, THIN_SLICE_THICKNESS);
 
     if (!displayedSlice.ContainsPlane(slice))
@@ -490,6 +500,7 @@
       double x1, y1, x2, y2;
       if (GetAndFixExtent(x1, y1, x2, y2, *layers_[index]))
       {
+        printf("**%d** %f %f %f %f\n", index, x1, y1, x2, y2);
         UpdateLayer(index, new MissingLayerRenderer(x1, y1, x2, y2), Slice(slice, THIN_SLICE_THICKNESS));
       }
     }
--- a/UnitTestsSources/UnitTestsMain.cpp	Mon May 29 11:42:53 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Mon May 29 17:28:31 2017 +0200
@@ -48,7 +48,7 @@
 
       for (size_t i = 0; i < loader.GetSliceCount(); i++)
       {
-        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i);
+        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_Full);
       }
     }
 
@@ -60,7 +60,8 @@
     virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
-                                       Orthanc::ImageAccessor* image)
+                                       Orthanc::ImageAccessor* image,
+                                       SliceImageQuality quality)
     {
       std::auto_ptr<Orthanc::ImageAccessor> tmp(image);
       printf("Slice OK %dx%d\n", tmp->GetWidth(), tmp->GetHeight());
@@ -68,14 +69,15 @@
 
     virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
-                                       const Slice& slice)
+                                       const Slice& slice,
+                                       SliceImageQuality quality)
     {
       printf("ERROR 2\n");
     }
   };
 
 
-  class OrthancVolumeImageLoader : 
+  class OrthancVolumeImage : 
     public SlicedVolumeBase,
     private OrthancSlicesLoader::ICallback
   { 
@@ -92,7 +94,7 @@
       unsigned int slice;
       if (downloadStack_->Pop(slice))
       {
-        loader_.ScheduleLoadSliceImage(slice);
+        loader_.ScheduleLoadSliceImage(slice, SliceImageQuality_Full);
       }
     }
 
@@ -172,7 +174,11 @@
       
       for (size_t i = 1; i < loader.GetSliceCount(); i++)
       {
-        if (!GeometryToolbox::IsNear(spacingZ, GetDistance(loader.GetSlice(i - 1), loader.GetSlice(i))))
+        printf("%d %s %f\n", i, loader.GetSlice(i).GetOrthancInstanceId().c_str(),
+               GetDistance(loader.GetSlice(i - 1), loader.GetSlice(i)));
+        
+        if (!GeometryToolbox::IsNear(spacingZ, GetDistance(loader.GetSlice(i - 1), loader.GetSlice(i)),
+                                     0.001 /* this is expressed in mm */))
         {
           LOG(ERROR) << "The distance between successive slices is not constant in a volume image";
           SlicedVolumeBase::NotifyGeometryError();
@@ -210,7 +216,8 @@
     virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
                                        const Slice& slice,
-                                       Orthanc::ImageAccessor* image)
+                                       Orthanc::ImageAccessor* image,
+                                       SliceImageQuality quality)
     {
       std::auto_ptr<Orthanc::ImageAccessor> protection(image);
 
@@ -226,14 +233,15 @@
 
     virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
                                        unsigned int sliceIndex,
-                                       const Slice& slice)
+                                       const Slice& slice,
+                                       SliceImageQuality quality)
     {
       LOG(ERROR) << "Cannot download slice " << sliceIndex << " in a volume image";
       ScheduleSliceDownload();
     }
 
   public:
-    OrthancVolumeImageLoader(IWebService& orthanc) : 
+    OrthancVolumeImage(IWebService& orthanc) : 
       loader_(*this, orthanc)
     {
     }
@@ -258,6 +266,18 @@
     {
       return loader_.GetSlice(index);
     }
+
+    ImageBuffer3D& GetImage()
+    {
+      if (image_.get() == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return *image_;
+      }
+    }
   };
 }
 
@@ -296,11 +316,13 @@
 
   Orthanc::WebServiceParameters web;
   OrthancStone::OracleWebService orthanc(oracle, web);
-  OrthancStone::OrthancVolumeImageLoader volume(orthanc);
+  OrthancStone::OrthancVolumeImage volume(orthanc);
 
-  volume.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
+  //volume.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
   //volume.ScheduleLoadSeries("318603c5-03e8cffc-a82b6ee1-3ccd3c1e-18d7e3bb"); // COMUNIX PET
+  //volume.ScheduleLoadSeries("7124dba7-09803f33-98b73826-33f14632-ea842d29"); // COMUNIX CT
   //volume.ScheduleLoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // Delphine sagital
+  volume.ScheduleLoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); // Delphine ax 2.5
 
   boost::this_thread::sleep(boost::posix_time::milliseconds(1000));