changeset 68:1526d38ef6da wasm

SliceSorter
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 22 May 2017 17:49:26 +0200
parents acb60cbb8301
children 1553b67b24e5
files UnitTestsSources/UnitTestsMain.cpp
diffstat 1 files changed, 570 insertions(+), 258 deletions(-) [+]
line wrap: on
line diff
--- a/UnitTestsSources/UnitTestsMain.cpp	Thu May 18 17:51:41 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Mon May 22 17:49:26 2017 +0200
@@ -27,12 +27,368 @@
 #include "../Framework/Widgets/LayerWidget.h"
 
 
+#include "../Resources/Orthanc/Core/Images/PngReader.h"
 #include "../Framework/Toolbox/MessagingToolbox.h"
 #include "../Framework/Toolbox/DicomFrameConverter.h"
 
+#include <boost/lexical_cast.hpp>
+
 namespace OrthancStone
 {
-  class SeriesLoader :
+  class Slice
+  {
+  private:
+    enum Type
+    {
+      Type_Invalid,
+      Type_OrthancInstance
+      // TODO A slice could come from some DICOM file (URL)
+    };
+
+    Type                 type_;
+    std::string          orthancInstanceId_;
+    unsigned int         frame_;
+    SliceGeometry        geometry_;
+    double               pixelSpacingX_;
+    double               pixelSpacingY_;
+    double               thickness_;
+    unsigned int         width_;
+    unsigned int         height_;
+    DicomFrameConverter  converter_;
+      
+  public:
+    Slice() : type_(Type_Invalid)
+    {        
+    }
+
+    bool ParseOrthancFrame(const OrthancPlugins::IDicomDataset& dataset,
+                           const std::string& instanceId,
+                           unsigned int frame)
+    {
+      OrthancPlugins::DicomDatasetReader reader(dataset);
+
+      unsigned int frameCount;
+      if (!reader.GetUnsignedIntegerValue(frameCount, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES))
+      {
+        frameCount = 1;   // Assume instance with one frame
+      }
+
+      if (frame >= frameCount)
+      {
+        return false;
+      }
+
+      if (!reader.GetDoubleValue(thickness_, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS))
+      {
+        thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
+      }
+
+      GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset);
+
+      std::string position, orientation;
+      if (dataset.GetStringValue(position, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) &&
+          dataset.GetStringValue(orientation, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) &&
+          reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) &&
+          reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS))
+      {
+        orthancInstanceId_ = instanceId;
+        frame_ = frame;
+        geometry_ = SliceGeometry(position, orientation);
+        converter_.ReadParameters(dataset);
+
+        type_ = Type_OrthancInstance;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    bool IsOrthancInstance() const
+    {
+      return type_ == Type_OrthancInstance;
+    }
+
+    const std::string GetOrthancInstanceId() const
+    {
+      if (type_ != Type_OrthancInstance)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return orthancInstanceId_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return frame_;
+    }
+
+    const SliceGeometry& GetGeometry() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return geometry_;
+    }
+
+    double GetThickness() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return thickness_;
+    }
+
+    double GetPixelSpacingX() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return pixelSpacingX_;
+    }
+
+    double GetPixelSpacingY() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return pixelSpacingY_;
+    }
+
+    unsigned int GetWidth() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return height_;
+    }
+
+    const DicomFrameConverter& GetConverter() const
+    {
+      if (type_ == Type_Invalid)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+        
+      return converter_;
+    }
+  };
+
+
+  class SliceSorter : public boost::noncopyable
+  {
+  private:
+    class SliceWithDepth : public boost::noncopyable
+    {
+    private:
+      Slice   slice_;
+      double  depth_;
+
+    public:
+      SliceWithDepth(const Slice& slice) :
+        slice_(slice),
+        depth_(0)
+      {
+      }
+
+      void SetNormal(const Vector& normal)
+      {
+        depth_ = boost::numeric::ublas::inner_prod
+          (slice_.GetGeometry().GetOrigin(), normal);
+      }
+
+      double GetDepth() const
+      {
+        return depth_;
+      }
+
+      const Slice& GetSlice() const
+      {
+        return slice_;
+      }
+    };
+
+    struct Comparator
+    {
+      bool operator() (const SliceWithDepth* const& a,
+                       const SliceWithDepth* const& b) const
+      {
+        return a->GetDepth() < b->GetDepth();
+      }
+    };
+
+    typedef std::vector<SliceWithDepth*>  Slices;
+
+    Slices  slices_;
+    bool    hasNormal_;
+    
+  public:
+    SliceSorter() : hasNormal_(false)
+    {
+    }
+
+    ~SliceSorter()
+    {
+      for (size_t i = 0; i < slices_.size(); i++)
+      {
+        assert(slices_[i] != NULL);
+        delete slices_[i];
+      }
+    }
+    
+    void Reserve(size_t count)
+    {
+      slices_.reserve(count);
+    }
+
+    void AddSlice(const Slice& slice)
+    {
+      slices_.push_back(new SliceWithDepth(slice));
+    }
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const Slice& GetSlice(size_t i) const
+    {
+      if (i >= slices_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      assert(slices_[i] != NULL);
+      return slices_[i]->GetSlice();
+    }
+
+    void SetNormal(const Vector& normal)
+    {
+      for (size_t i = 0; i < slices_.size(); i++)
+      {
+        slices_[i]->SetNormal(normal);
+      }
+
+      hasNormal_ = true;
+    }
+    
+    void Sort()
+    {
+      if (!hasNormal_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      Comparator comparator;
+      std::sort(slices_.begin(), slices_.end(), comparator);
+    }
+
+    void FilterNormal(const Vector& normal)
+    {
+      size_t pos = 0;
+
+      for (size_t i = 0; i < slices_.size(); i++)
+      {
+        if (GeometryToolbox::IsParallel(normal, slices_[i]->GetSlice().GetGeometry().GetNormal()))
+        {
+          // This slice is compatible with the selected normal
+          slices_[pos] = slices_[i];
+          pos += 1;
+        }
+        else
+        {
+          delete slices_[i];
+          slices_[i] = NULL;
+        }
+      }
+
+      slices_.resize(pos);
+    }
+    
+    bool SelectNormal(Vector& normal) const
+    {
+      std::vector<Vector>  normalCandidates;
+      std::vector<unsigned int>  normalCount;
+
+      bool found = false;
+
+      for (size_t i = 0; !found && i < GetSliceCount(); i++)
+      {
+        const Vector& normal = GetSlice(i).GetGeometry().GetNormal();
+
+        bool add = true;
+        for (size_t j = 0; add && j < normalCandidates.size(); j++)  // (*)
+        {
+          if (GeometryToolbox::IsParallel(normal, normalCandidates[j]))
+          {
+            normalCount[j] += 1;
+            add = false;
+          }
+        }
+
+        if (add)
+        {
+          if (normalCount.size() > 2)
+          {
+            // To get linear-time complexity in (*). This heuristics
+            // allows the series to have one single frame that is
+            // not parallel to the others (such a frame could be a
+            // generated preview)
+            found = false;
+          }
+          else
+          {
+            normalCandidates.push_back(normal);
+            normalCount.push_back(1);
+          }
+        }
+      }
+
+      for (size_t i = 0; !found && i < normalCandidates.size(); i++)
+      {
+        unsigned int count = normalCount[i];
+        if (count == GetSliceCount() ||
+            count + 1 == GetSliceCount())
+        {
+          normal = normalCandidates[i];
+          found = true;
+        }
+      }
+
+      return found;
+    }
+  };
+
+
+
+  class OrthancSliceLoader :
     public IWebService::ICallback // TODO move to PImpl
   {
   public:
@@ -43,246 +399,41 @@
       {
       }
 
-      virtual void NotifyGeometryReady(const SeriesLoader& series) = 0;
+      virtual void NotifyGeometryReady(const OrthancSliceLoader& loader) = 0;
+
+      virtual void NotifyGeometryError(const OrthancSliceLoader& loader) = 0;
 
-      virtual void NotifyGeometryError(const SeriesLoader& series) = 0;
+      virtual void NotifySliceImageReady(const OrthancSliceLoader& loader,
+                                         unsigned int sliceIndex,
+                                         const Slice& slice,
+                                         Orthanc::ImageAccessor* image) = 0;
+
+      virtual void NotifySliceImageError(const OrthancSliceLoader& loader,
+                                         unsigned int sliceIndex,
+                                         const Slice& slice) = 0;
     };
     
   private:
-    class Slice : public boost::noncopyable
-    {
-    private:
-      std::string          instanceId_;
-      SliceGeometry        geometry_;
-      double               thickness_;
-      unsigned int         width_;
-      unsigned int         height_;
-      double               projectionAlongNormal_;
-      DicomFrameConverter  converter_;
-      
-    public:
-      Slice(const std::string& instanceId,
-            const std::string& imagePositionPatient,
-            const std::string& imageOrientationPatient,
-            double thickness,
-            unsigned int width,
-            unsigned int height,
-            const OrthancPlugins::IDicomDataset& dataset) :
-        instanceId_(instanceId),
-        geometry_(imagePositionPatient, imageOrientationPatient),
-        thickness_(thickness),
-        width_(width),
-        height_(height)
-      {
-        converter_.ReadParameters(dataset);
-      }
-
-      const std::string GetInstanceId() const
-      {
-        return instanceId_;
-      }
-
-      const SliceGeometry& GetGeometry() const
-      {
-        return geometry_;
-      }
-
-      double GetThickness() const
-      {
-        return thickness_;
-      }
-
-      unsigned int GetWidth() const
-      {
-        return width_;
-      }
-
-      unsigned int GetHeight() const
-      {
-        return height_;
-      }
-
-      const DicomFrameConverter& GetConverter() const
-      {
-        return converter_;
-      }
-
-      void SetNormal(const Vector& normal)
-      {
-        projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal);
-      }
-
-      double GetProjectionAlongNormal() const
-      {
-        return projectionAlongNormal_;
-      }
-    };
-
-
-    class SetOfSlices : public boost::noncopyable
+    enum State
     {
-    private:
-      std::vector<Slice*>  slices_;
-
-      struct Comparator
-      {
-        bool operator() (const Slice* const a,
-                         const Slice* const b) const
-        {
-          return a->GetProjectionAlongNormal() < b->GetProjectionAlongNormal();
-        }
-      };
-
-    public:
-      ~SetOfSlices()
-      {
-        for (size_t i = 0; i < slices_.size(); i++)
-        {
-          assert(slices_[i] != NULL);
-          delete slices_[i];
-        }
-      }
-
-      void Reserve(size_t size)
-      {
-        slices_.reserve(size);
-      }
-
-      void AddSlice(const std::string& instanceId,
-                    const Json::Value& value)
-      {
-        OrthancPlugins::FullOrthancDataset dataset(value);
-        OrthancPlugins::DicomDatasetReader reader(dataset);
-
-        std::string position, orientation;
-        double thickness;
-        unsigned int width, height;
-        if (dataset.GetStringValue(position, OrthancPlugins::DICOM_TAG_IMAGE_POSITION_PATIENT) &&
-            dataset.GetStringValue(orientation, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) &&
-            reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS) &&
-            reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) &&
-            reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS));
-        {
-          slices_.push_back(new Slice(instanceId, position, orientation,
-                                      thickness, width, height, dataset));
-        }       
-      }
-
-      size_t GetSliceCount() const
-      {
-        return slices_.size();
-      }
-
-      const Slice& GetSlice(size_t index) const
-      {
-        assert(slices_[index] != NULL);
-        return *slices_[index];
-      }
-      
-      void Sort(const Vector& normal)
-      {
-        for (size_t i = 0; i < slices_.size(); i++)
-        {
-          slices_[i]->SetNormal(normal);
-        }
-
-        Comparator comparator;
-        std::sort(slices_.begin(), slices_.end(), comparator);
-      }
-
-
-      void SelectNormal(Vector& normal) const
-      {
-        std::vector<Vector>  normalCandidates;
-        std::vector<unsigned int>  normalCount;
-
-        bool found = false;
-
-        for (size_t i = 0; !found && i < GetSliceCount(); i++)
-        {
-          const Vector& normal = GetSlice(i).GetGeometry().GetNormal();
-
-          bool add = true;
-          for (size_t j = 0; add && j < normalCandidates.size(); j++)  // (*)
-          {
-            if (GeometryToolbox::IsParallel(normal, normalCandidates[j]))
-            {
-              normalCount[j] += 1;
-              add = false;
-            }
-          }
-
-          if (add)
-          {
-            if (normalCount.size() > 2)
-            {
-              // To get linear-time complexity in (*). This heuristics
-              // allows the series to have one single frame that is
-              // not parallel to the others (such a frame could be a
-              // generated preview)
-              found = false;
-            }
-            else
-            {
-              normalCandidates.push_back(normal);
-              normalCount.push_back(1);
-            }
-          }
-        }
-
-        for (size_t i = 0; !found && i < normalCandidates.size(); i++)
-        {
-          unsigned int count = normalCount[i];
-          if (count == GetSliceCount() ||
-              count + 1 == GetSliceCount())
-          {
-            normal = normalCandidates[i];
-            found = true;
-          }
-        }
-
-        if (!found)
-        {
-          LOG(ERROR) << "Cannot select a normal that is shared by most of the slices of this series";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
-        }
-      }
-
-
-      void FilterNormal(const Vector& normal)
-      {
-        size_t pos = 0;
-
-        for (size_t i = 0; i < slices_.size(); i++)
-        {
-          if (GeometryToolbox::IsParallel(normal, slices_[i]->GetGeometry().GetNormal()))
-          {
-            // This slice is compatible with the selected normal
-            slices_[pos] = slices_[i];
-            pos += 1;
-          }
-          else
-          {
-            delete slices_[i];
-            slices_[i] = NULL;
-          }
-        }
-
-        slices_.resize(pos);
-      }
+      State_Error,
+      State_Initialization,
+      State_LoadingGeometry,
+      State_GeometryReady
     };
     
-    
     enum Mode
     {
-      Mode_Geometry
+      Mode_SeriesGeometry,
+      Mode_LoadImage
     };
 
     class Operation : public Orthanc::IDynamicObject
     {
     private:
-      Mode         mode_;
-      unsigned int instance_;
+      Mode          mode_;
+      unsigned int  sliceIndex_;
+      const Slice*  slice_;
 
       Operation(Mode mode) :
         mode_(mode)
@@ -294,21 +445,42 @@
       {
         return mode_;
       }
+
+      unsigned int GetSliceIndex() const
+      {
+        assert(mode_ == Mode_LoadImage);
+        return sliceIndex_;
+      }
+
+      const Slice& GetSlice() const
+      {
+        assert(mode_ == Mode_LoadImage && slice_ != NULL);
+        return *slice_;
+      }
       
-      static Operation* DownloadGeometry()
+      static Operation* DownloadSeriesGeometry()
       {
-        return new Operation(Mode_Geometry);
+        return new Operation(Mode_SeriesGeometry);
+      }
+
+      static Operation* DownloadSliceImage(unsigned int  sliceIndex,
+                                           const Slice&  slice)
+      {
+        std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
+        tmp->sliceIndex_ = sliceIndex;
+        tmp->slice_ = &slice;
+        return tmp.release();
       }
     };
     
     ICallback&    callback_;
     IWebService&  orthanc_;
-    std::string   seriesId_;
-    SetOfSlices   slices_;
+    State         state_;
+    SliceSorter   slices_;
 
 
-    void ParseGeometry(const void* answer,
-                       size_t size)
+    void ParseSeriesGeometry(const void* answer,
+                             size_t size)
     {
       Json::Value series;
       if (!MessagingToolbox::ParseJson(series, answer, size) ||
@@ -324,7 +496,17 @@
 
       for (size_t i = 0; i < instances.size(); i++)
       {
-        slices_.AddSlice(instances[i], series[instances[i]]);
+        OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
+
+        Slice slice;
+        if (slice.ParseOrthancFrame(dataset, instances[i], 0 /* todo */))
+        {
+          slices_.AddSlice(slice);
+        }
+        else
+        {
+          LOG(WARNING) << "Skipping invalid instance " << instances[i];
+        }
       }
 
       bool ok = false;
@@ -332,41 +514,141 @@
       if (slices_.GetSliceCount() > 0)
       {
         Vector normal;
-        slices_.SelectNormal(normal);
-        slices_.FilterNormal(normal);
-        slices_.Sort(normal);
-        ok = true;
+        if (slices_.SelectNormal(normal))
+        {
+          slices_.FilterNormal(normal);
+          slices_.SetNormal(normal);
+          slices_.Sort();
+          ok = true;
+        }
       }
 
       if (ok)
       {
-        printf("%d\n", slices_.GetSliceCount());
+        LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
         callback_.NotifyGeometryReady(*this);
       }
       else
       {
         LOG(ERROR) << "This series is empty";
         callback_.NotifyGeometryError(*this);
-        return;
+      }
+    }
+
+
+    void ParseSliceImage(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());
+      
+      if (ok &&
+          operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+          Orthanc::PixelFormat_SignedGrayscale16)
+      {
+        if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+        {
+          image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+        }
+        else
+        {
+          ok = false;
+        }
+      }
+
+      if (ok)
+      {
+        callback_.NotifySliceImageReady(*this, operation.GetSliceIndex(),
+                                        operation.GetSlice(), image.release());
+      }
+      else
+      {
+        callback_.NotifySliceImageError(*this, operation.GetSliceIndex(),
+                                        operation.GetSlice());
       }
     }
     
     
   public:
-    SeriesLoader(ICallback& callback,
-                 IWebService& orthanc,
-                 const std::string& seriesId) :
+    OrthancSliceLoader(ICallback& callback,
+                 IWebService& orthanc) :
       callback_(callback),
       orthanc_(orthanc),
-      seriesId_(seriesId)
+      state_(State_Initialization)
+    {
+    }
+
+    void ScheduleLoadSeries(const std::string& seriesId)
     {
-      std::string uri = "/series/" + seriesId + "/instances-tags";
-      orthanc.ScheduleGetRequest(*this, uri, Operation::DownloadGeometry());
+      if (state_ != State_Initialization)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        state_ = State_LoadingGeometry;
+        std::string uri = "/series/" + seriesId + "/instances-tags";
+        orthanc_.ScheduleGetRequest(*this, uri, Operation::DownloadSeriesGeometry());
+      }
+    }
+
+    size_t GetSliceCount() const
+    {
+      if (state_ != State_GeometryReady)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      return slices_.GetSliceCount();
     }
 
-    const std::string& GetSeriesId() const
+    const Slice& GetSlice(size_t index) const
+    {
+      if (state_ != State_GeometryReady)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      return slices_.GetSlice(index);
+    }
+
+    void ScheduleLoadSliceImage(size_t index)
     {
-      return seriesId_;
+      if (state_ != State_GeometryReady)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      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(*this, uri, Operation::DownloadSliceImage(index, slice));
+      }
     }
 
     virtual void NotifySuccess(const std::string& uri,
@@ -378,8 +660,13 @@
 
       switch (operation->GetMode())
       {
-        case Mode_Geometry:
-          ParseGeometry(answer, answerSize);
+        case Mode_SeriesGeometry:
+          ParseSeriesGeometry(answer, answerSize);
+          state_ = State_GeometryReady;
+          break;
+
+        case Mode_LoadImage:
+          ParseSliceImage(*operation, answer, answerSize);
           break;
 
         default:
@@ -395,10 +682,15 @@
 
       switch (operation->GetMode())
       {
-        case Mode_Geometry:
+        case Mode_SeriesGeometry:
           callback_.NotifyGeometryError(*this);
+          state_ = State_Error;
           break;
 
+        case Mode_LoadImage:
+          callback_.NotifySliceImageError(*this, operation->GetSliceIndex(), operation->GetSlice());
+          break;
+          
         default:
           throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
@@ -406,17 +698,33 @@
   };
 
 
-  class Tata : public SeriesLoader::ICallback
+  class Tata : public OrthancSliceLoader::ICallback
   {
   public:
-    virtual void NotifyGeometryReady(const SeriesLoader& series)
+    virtual void NotifyGeometryReady(const OrthancSliceLoader& loader)
     {
-      printf("Done %s\n", series.GetSeriesId().c_str());
+      printf("Done\n");
+    }
+
+    virtual void NotifyGeometryError(const OrthancSliceLoader& loader)
+    {
+      printf("Error\n");
     }
 
-    virtual void NotifyGeometryError(const SeriesLoader& series)
+    virtual void NotifySliceImageReady(const OrthancSliceLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       Orthanc::ImageAccessor* image)
     {
-      printf("Error %s\n", series.GetSeriesId().c_str());
+      std::auto_ptr<Orthanc::ImageAccessor> tmp(image);
+      printf("Slice OK\n");
+    }
+
+    virtual void NotifySliceImageError(const OrthancSliceLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice)
+    {
+      printf("ERROR 2\n");
     }
   };
 }
@@ -428,8 +736,12 @@
   OrthancStone::OrthancWebService orthanc(web);
 
   OrthancStone::Tata tata;
-  //OrthancStone::SeriesLoader loader(tata, orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5");
-  OrthancStone::SeriesLoader loader(tata, orthanc, "67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");
+  OrthancStone::OrthancSliceLoader loader(tata, orthanc);
+  //loader.ScheduleLoadSeries("c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5");
+  loader.ScheduleLoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");
+
+  printf(">> %d\n", loader.GetSliceCount());
+  loader.ScheduleLoadSliceImage(31);
 }