changeset 73:ffa6dded91bd wasm

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 24 May 2017 11:59:24 +0200
parents c1cc3bdba18c
children 6546dbcc0a7d
files Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Toolbox/Slice.cpp Framework/Toolbox/Slice.h Framework/Toolbox/SlicesSorter.cpp Framework/Toolbox/SlicesSorter.h Resources/CMake/OrthancStone.cmake UnitTestsSources/UnitTestsMain.cpp
diffstat 8 files changed, 1026 insertions(+), 768 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Wed May 24 11:59:24 2017 +0200
@@ -0,0 +1,394 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "OrthancSlicesLoader.h"
+
+#include "MessagingToolbox.h"
+
+#include "../../Resources/Orthanc/Core/Images/PngReader.h"
+#include "../../Resources/Orthanc/Core/Logging.h"
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
+#include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancStone
+{
+  class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
+  {
+  private:
+    Mode          mode_;
+    unsigned int  frame_;
+    unsigned int  sliceIndex_;
+    const Slice*  slice_;
+    std::string   instanceId_;
+
+    Operation(Mode mode) :
+      mode_(mode)
+    {
+    }
+
+  public:
+    Mode GetMode() const
+    {
+      return mode_;
+    }
+
+    unsigned int GetSliceIndex() const
+    {
+      assert(mode_ == Mode_LoadImage);
+      return sliceIndex_;
+    }
+
+    const Slice& GetSlice() const
+    {
+      assert(mode_ == Mode_LoadImage && slice_ != NULL);
+      return *slice_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      assert(mode_ == Mode_InstanceGeometry);
+      return frame_;
+    }
+      
+    const std::string& GetInstanceId() const
+    {
+      assert(mode_ == Mode_InstanceGeometry);
+      return instanceId_;
+    }
+      
+    static Operation* DownloadSeriesGeometry()
+    {
+      return new Operation(Mode_SeriesGeometry);
+    }
+
+    static Operation* DownloadInstanceGeometry(const std::string& instanceId,
+                                               unsigned int frame)
+    {
+      std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
+      operation->instanceId_ = instanceId;
+      operation->frame_ = frame;
+      return operation.release();
+    }
+
+    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();
+    }
+  };
+    
+
+  class OrthancSlicesLoader::WebCallback : public IWebService::ICallback
+  {
+  private:
+    OrthancSlicesLoader&  that_;
+
+  public:
+    WebCallback(OrthancSlicesLoader&  that) :
+      that_(that)
+    {
+    }
+
+    virtual void NotifySuccess(const std::string& uri,
+                               const void* answer,
+                               size_t answerSize,
+                               Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
+
+      switch (operation->GetMode())
+      {
+        case Mode_SeriesGeometry:
+          that_.ParseSeriesGeometry(answer, answerSize);
+          break;
+
+        case Mode_InstanceGeometry:
+          that_.ParseInstanceGeometry(operation->GetInstanceId(),
+                                      operation->GetFrame(), answer, answerSize);
+          break;
+
+        case Mode_LoadImage:
+          that_.ParseSliceImage(*operation, answer, answerSize);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    virtual void NotifyError(const std::string& uri,
+                             Orthanc::IDynamicObject* payload)
+    {
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
+      LOG(ERROR) << "Cannot download " << uri;
+
+      switch (operation->GetMode())
+      {
+        case Mode_SeriesGeometry:
+          that_.userCallback_.NotifyGeometryError(that_);
+          that_.state_ = State_Error;
+          break;
+
+        case Mode_LoadImage:
+          that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
+                                                    operation->GetSlice());
+          break;
+          
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }     
+  };
+
+  
+  void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
+                                                size_t size)
+  {
+    Json::Value series;
+    if (!MessagingToolbox::ParseJson(series, answer, size) ||
+        series.type() != Json::objectValue)
+    {
+      userCallback_.NotifyGeometryError(*this);
+      return;
+    }
+
+    Json::Value::Members instances = series.getMemberNames();
+
+    slices_.Reserve(instances.size());
+
+    for (size_t i = 0; i < instances.size(); 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;
+      
+    if (slices_.GetSliceCount() > 0)
+    {
+      Vector normal;
+      if (slices_.SelectNormal(normal))
+      {
+        slices_.FilterNormal(normal);
+        slices_.SetNormal(normal);
+        slices_.Sort();
+        ok = true;
+      }
+    }
+
+    state_ = State_GeometryReady;
+
+    if (ok)
+    {
+      LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
+      userCallback_.NotifyGeometryReady(*this);
+    }
+    else
+    {
+      LOG(ERROR) << "This series is empty";
+      userCallback_.NotifyGeometryError(*this);
+    }
+  }
+
+
+  void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
+                                                  unsigned int frame,
+                                                  const void* answer,
+                                                  size_t size)
+  {
+    Json::Value tags;
+    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
+        tags.type() != Json::objectValue)
+    {
+      userCallback_.NotifyGeometryError(*this);
+      return;
+    }
+
+    OrthancPlugins::FullOrthancDataset dataset(tags);
+
+    state_ = State_GeometryReady;
+      
+    Slice slice;
+    if (slice.ParseOrthancFrame(dataset, instanceId, frame))
+    {
+      LOG(INFO) << "Loaded instance " << instanceId;
+      slices_.AddSlice(slice);
+      userCallback_.NotifyGeometryReady(*this);
+    }
+    else
+    {
+      LOG(WARNING) << "Skipping invalid instance " << instanceId;
+      userCallback_.NotifyGeometryError(*this);
+    }
+  }
+
+
+  void OrthancSlicesLoader::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)
+    {
+      userCallback_.NotifySliceImageReady(*this, operation.GetSliceIndex(),
+                                          operation.GetSlice(), image.release());
+    }
+    else
+    {
+      userCallback_.NotifySliceImageError(*this, operation.GetSliceIndex(),
+                                          operation.GetSlice());
+    }
+  }
+    
+    
+  OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback,
+                                           IWebService& orthanc) :
+    webCallback_(new WebCallback(*this)),
+    userCallback_(callback),
+    orthanc_(orthanc),
+    state_(State_Initialization)
+  {
+  }
+
+  
+  void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
+  {
+    if (state_ != State_Initialization)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_LoadingGeometry;
+      std::string uri = "/series/" + seriesId + "/instances-tags";
+      orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry());
+    }
+  }
+
+
+  void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId,
+                                                 unsigned int frame)
+  {
+    if (state_ != State_Initialization)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_LoadingGeometry;
+      std::string uri = "/instances/" + instanceId + "/tags";
+      orthanc_.ScheduleGetRequest
+        (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId, frame));
+    }
+  }
+  
+
+  size_t OrthancSlicesLoader::GetSliceCount() const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return slices_.GetSliceCount();
+  }
+
+  
+  const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
+  {
+    if (state_ != State_GeometryReady)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return slices_.GetSlice(index);
+  }
+  
+
+  void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index)
+  {
+    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(*webCallback_, uri, Operation::DownloadSliceImage(index, slice));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Wed May 24 11:59:24 2017 +0200
@@ -0,0 +1,110 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "IWebService.h"
+#include "SlicesSorter.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  class OrthancSlicesLoader : public boost::noncopyable
+  {
+  public:
+    class ICallback : public boost::noncopyable
+    {
+    public:
+      virtual ~ICallback()
+      {
+      }
+
+      virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0;
+
+      virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0;
+
+      virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                         unsigned int sliceIndex,
+                                         const Slice& slice,
+                                         Orthanc::ImageAccessor* image) = 0;
+
+      virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                         unsigned int sliceIndex,
+                                         const Slice& slice) = 0;
+    };
+    
+  private:
+    enum State
+    {
+      State_Error,
+      State_Initialization,
+      State_LoadingGeometry,
+      State_GeometryReady
+    };
+    
+    enum Mode
+    {
+      Mode_SeriesGeometry,
+      Mode_InstanceGeometry,
+      Mode_LoadImage
+    };
+
+    class Operation;
+    class WebCallback;
+
+    boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
+
+    ICallback&    userCallback_;
+    IWebService&  orthanc_;
+    State         state_;
+    SlicesSorter  slices_;
+
+
+    void ParseSeriesGeometry(const void* answer,
+                             size_t size);
+
+    void ParseInstanceGeometry(const std::string& instanceId,
+                               unsigned int frame,
+                               const void* answer,
+                               size_t size);
+
+    void ParseSliceImage(const Operation& operation,
+                         const void* answer,
+                         size_t size);
+    
+    
+  public:
+    OrthancSlicesLoader(ICallback& callback,
+                        IWebService& orthanc);
+
+    void ScheduleLoadSeries(const std::string& seriesId);
+
+    void ScheduleLoadInstance(const std::string& instanceId,
+                              unsigned int frame);
+
+    size_t GetSliceCount() const;
+
+    const Slice& GetSlice(size_t index) const;
+
+    void ScheduleLoadSliceImage(size_t index);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Slice.cpp	Wed May 24 11:59:24 2017 +0200
@@ -0,0 +1,173 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "Slice.h"
+
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  bool Slice::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))
+    {
+      geometry_ = SliceGeometry(position, orientation);
+    }
+      
+    if (reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) &&
+        reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS))
+    {
+      orthancInstanceId_ = instanceId;
+      frame_ = frame;
+      converter_.ReadParameters(dataset);
+
+      type_ = Type_OrthancInstance;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  const std::string Slice::GetOrthancInstanceId() const
+  {
+    if (type_ != Type_OrthancInstance)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return orthancInstanceId_;
+  }
+
+  
+  unsigned int Slice::GetFrame() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return frame_;
+  }
+
+  
+  const SliceGeometry& Slice::GetGeometry() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return geometry_;
+  }
+
+  
+  double Slice::GetThickness() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return thickness_;
+  }
+
+  
+  double Slice::GetPixelSpacingX() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return pixelSpacingX_;
+  }
+
+  
+  double Slice::GetPixelSpacingY() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return pixelSpacingY_;
+  }
+
+  
+  unsigned int Slice::GetWidth() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return width_;
+  }
+
+  
+  unsigned int Slice::GetHeight() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return height_;
+  }
+
+
+  const DicomFrameConverter& Slice::GetConverter() const
+  {
+    if (type_ == Type_Invalid)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+        
+    return converter_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/Slice.h	Wed May 24 11:59:24 2017 +0200
@@ -0,0 +1,82 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "SliceGeometry.h"
+#include "DicomFrameConverter.h"
+
+namespace OrthancStone
+{
+  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);
+
+    bool IsOrthancInstance() const
+    {
+      return type_ == Type_OrthancInstance;
+    }
+
+    const std::string GetOrthancInstanceId() const;
+
+    unsigned int GetFrame() const;
+
+    const SliceGeometry& GetGeometry() const;
+
+    double GetThickness() const;
+
+    double GetPixelSpacingX() const;
+
+    double GetPixelSpacingY() const;
+
+    unsigned int GetWidth() const;
+
+    unsigned int GetHeight() const;
+
+    const DicomFrameConverter& GetConverter() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SlicesSorter.cpp	Wed May 24 11:59:24 2017 +0200
@@ -0,0 +1,195 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "SlicesSorter.h"
+
+#include "../../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancStone
+{
+  class SlicesSorter::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 SlicesSorter::Comparator
+  {
+    bool operator() (const SliceWithDepth* const& a,
+                     const SliceWithDepth* const& b) const
+    {
+      return a->GetDepth() < b->GetDepth();
+    }
+  };
+
+
+  SlicesSorter::~SlicesSorter()
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      assert(slices_[i] != NULL);
+      delete slices_[i];
+    }
+  }
+
+
+  void SlicesSorter::AddSlice(const Slice& slice)
+  {
+    slices_.push_back(new SliceWithDepth(slice));
+  }
+
+  
+  const Slice& SlicesSorter::GetSlice(size_t i) const
+  {
+    if (i >= slices_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(slices_[i] != NULL);
+    return slices_[i]->GetSlice();
+  }
+
+  
+  void SlicesSorter::SetNormal(const Vector& normal)
+  {
+    for (size_t i = 0; i < slices_.size(); i++)
+    {
+      slices_[i]->SetNormal(normal);
+    }
+
+    hasNormal_ = true;
+  }
+  
+    
+  void SlicesSorter::Sort()
+  {
+    if (!hasNormal_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    Comparator comparator;
+    std::sort(slices_.begin(), slices_.end(), comparator);
+  }
+  
+
+  void SlicesSorter::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 SlicesSorter::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;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/SlicesSorter.h	Wed May 24 11:59:24 2017 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "Slice.h"
+
+namespace OrthancStone
+{
+  class SlicesSorter : public boost::noncopyable
+  {
+  private:
+    class SliceWithDepth;
+    struct Comparator;
+
+    typedef std::vector<SliceWithDepth*>  Slices;
+
+    Slices  slices_;
+    bool    hasNormal_;
+    
+  public:
+    SlicesSorter() : hasNormal_(false)
+    {
+    }
+
+    ~SlicesSorter();
+    
+    void Reserve(size_t count)
+    {
+      slices_.reserve(count);
+    }
+
+    void AddSlice(const Slice& slice);
+
+    size_t GetSliceCount() const
+    {
+      return slices_.size();
+    }
+
+    const Slice& GetSlice(size_t i) const;
+
+    void SetNormal(const Vector& normal);
+    
+    void Sort();
+
+    void FilterNormal(const Vector& normal);
+    
+    bool SelectNormal(Vector& normal) const;
+  };
+}
--- a/Resources/CMake/OrthancStone.cmake	Wed May 24 10:36:41 2017 +0200
+++ b/Resources/CMake/OrthancStone.cmake	Wed May 24 11:59:24 2017 +0200
@@ -202,10 +202,13 @@
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/MessagingToolbox.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancAsynchronousWebService.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSeriesLoader.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSlicesLoader.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/OrthancSynchronousWebService.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlices.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/ParallelSlicesCursor.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/Slice.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/SliceGeometry.cpp
+  ${ORTHANC_STONE_DIR}/Framework/Toolbox/SlicesSorter.cpp
   ${ORTHANC_STONE_DIR}/Framework/Toolbox/ViewportGeometry.cpp
   ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoContext.cpp
   ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoFont.cpp
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed May 24 10:36:41 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Wed May 24 11:59:24 2017 +0200
@@ -22,15 +22,8 @@
 #include "gtest/gtest.h"
 
 #include "../Framework/Toolbox/OrthancAsynchronousWebService.h"
+#include "../Framework/Toolbox/OrthancSlicesLoader.h"
 #include "../Resources/Orthanc/Core/Logging.h"
-#include "../Framework/Toolbox/OrthancSynchronousWebService.h"
-#include "../Framework/Layers/OrthancFrameLayerSource.h"
-#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>
 #include <boost/date_time/posix_time/posix_time.hpp>
@@ -38,766 +31,6 @@
 
 namespace OrthancStone
 {
-  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))
-      {
-        geometry_ = SliceGeometry(position, orientation);
-      }
-      
-      if (reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) &&
-          reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS))
-      {
-        orthancInstanceId_ = instanceId;
-        frame_ = frame;
-        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 OrthancSlicesLoader : public boost::noncopyable
-  {
-  public:
-    class ICallback : public boost::noncopyable
-    {
-    public:
-      virtual ~ICallback()
-      {
-      }
-
-      virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0;
-
-      virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0;
-
-      virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
-                                         unsigned int sliceIndex,
-                                         const Slice& slice,
-                                         Orthanc::ImageAccessor* image) = 0;
-
-      virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
-                                         unsigned int sliceIndex,
-                                         const Slice& slice) = 0;
-    };
-    
-  private:
-    enum State
-    {
-      State_Error,
-      State_Initialization,
-      State_LoadingGeometry,
-      State_GeometryReady
-    };
-    
-    enum Mode
-    {
-      Mode_SeriesGeometry,
-      Mode_InstanceGeometry,
-      Mode_LoadImage
-    };
-
-    class Operation : public Orthanc::IDynamicObject
-    {
-    private:
-      Mode          mode_;
-      unsigned int  frame_;
-      unsigned int  sliceIndex_;
-      const Slice*  slice_;
-      std::string   instanceId_;
-
-      Operation(Mode mode) :
-        mode_(mode)
-      {
-      }
-
-    public:
-      Mode GetMode() const
-      {
-        return mode_;
-      }
-
-      unsigned int GetSliceIndex() const
-      {
-        assert(mode_ == Mode_LoadImage);
-        return sliceIndex_;
-      }
-
-      const Slice& GetSlice() const
-      {
-        assert(mode_ == Mode_LoadImage && slice_ != NULL);
-        return *slice_;
-      }
-
-      unsigned int GetFrame() const
-      {
-        assert(mode_ == Mode_InstanceGeometry);
-        return frame_;
-      }
-      
-      const std::string& GetInstanceId() const
-      {
-        assert(mode_ == Mode_InstanceGeometry);
-        return instanceId_;
-      }
-      
-      static Operation* DownloadSeriesGeometry()
-      {
-        return new Operation(Mode_SeriesGeometry);
-      }
-
-      static Operation* DownloadInstanceGeometry(const std::string& instanceId,
-                                                 unsigned int frame)
-      {
-        std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
-        operation->instanceId_ = instanceId;
-        operation->frame_ = frame;
-        return operation.release();
-      }
-
-      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();
-      }
-    };
-    
-
-    class WebCallback : public IWebService::ICallback
-    {
-    private:
-      OrthancSlicesLoader&  that_;
-
-    public:
-      WebCallback(OrthancSlicesLoader&  that) :
-        that_(that)
-      {
-      }
-
-      virtual void NotifySuccess(const std::string& uri,
-                                 const void* answer,
-                                 size_t answerSize,
-                                 Orthanc::IDynamicObject* payload)
-      {
-        std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-
-        switch (operation->GetMode())
-        {
-          case Mode_SeriesGeometry:
-            that_.ParseSeriesGeometry(answer, answerSize);
-            break;
-
-          case Mode_InstanceGeometry:
-            that_.ParseInstanceGeometry(operation->GetInstanceId(),
-                                        operation->GetFrame(), answer, answerSize);
-            break;
-
-          case Mode_LoadImage:
-            that_.ParseSliceImage(*operation, answer, answerSize);
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-      }
-
-      virtual void NotifyError(const std::string& uri,
-                               Orthanc::IDynamicObject* payload)
-      {
-        std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-        LOG(ERROR) << "Cannot download " << uri;
-
-        switch (operation->GetMode())
-        {
-          case Mode_SeriesGeometry:
-            that_.userCallback_.NotifyGeometryError(that_);
-            that_.state_ = State_Error;
-            break;
-
-          case Mode_LoadImage:
-            that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
-                                                      operation->GetSlice());
-            break;
-          
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-      }     
-    };
-
-
-    WebCallback   webCallback_;
-    ICallback&    userCallback_;
-    IWebService&  orthanc_;
-    State         state_;
-    SliceSorter   slices_;
-
-
-    void ParseSeriesGeometry(const void* answer,
-                             size_t size)
-    {
-      Json::Value series;
-      if (!MessagingToolbox::ParseJson(series, answer, size) ||
-          series.type() != Json::objectValue)
-      {
-        userCallback_.NotifyGeometryError(*this);
-        return;
-      }
-
-      Json::Value::Members instances = series.getMemberNames();
-
-      slices_.Reserve(instances.size());
-
-      for (size_t i = 0; i < instances.size(); 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;
-      
-      if (slices_.GetSliceCount() > 0)
-      {
-        Vector normal;
-        if (slices_.SelectNormal(normal))
-        {
-          slices_.FilterNormal(normal);
-          slices_.SetNormal(normal);
-          slices_.Sort();
-          ok = true;
-        }
-      }
-
-      state_ = State_GeometryReady;
-
-      if (ok)
-      {
-        LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-        userCallback_.NotifyGeometryReady(*this);
-      }
-      else
-      {
-        LOG(ERROR) << "This series is empty";
-        userCallback_.NotifyGeometryError(*this);
-      }
-    }
-
-
-    void ParseInstanceGeometry(const std::string& instanceId,
-                               unsigned int frame,
-                               const void* answer,
-                               size_t size)
-    {
-      Json::Value tags;
-      if (!MessagingToolbox::ParseJson(tags, answer, size) ||
-          tags.type() != Json::objectValue)
-      {
-        userCallback_.NotifyGeometryError(*this);
-        return;
-      }
-
-      OrthancPlugins::FullOrthancDataset dataset(tags);
-
-      state_ = State_GeometryReady;
-      
-      Slice slice;
-      if (slice.ParseOrthancFrame(dataset, instanceId, frame))
-      {
-        LOG(INFO) << "Loaded instance " << instanceId;
-        slices_.AddSlice(slice);
-        userCallback_.NotifyGeometryReady(*this);
-      }
-      else
-      {
-        LOG(WARNING) << "Skipping invalid instance " << instanceId;
-        userCallback_.NotifyGeometryError(*this);
-      }
-    }
-
-
-    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)
-      {
-        userCallback_.NotifySliceImageReady(*this, operation.GetSliceIndex(),
-                                        operation.GetSlice(), image.release());
-      }
-      else
-      {
-        userCallback_.NotifySliceImageError(*this, operation.GetSliceIndex(),
-                                        operation.GetSlice());
-      }
-    }
-    
-    
-  public:
-    OrthancSlicesLoader(ICallback& callback,
-                 IWebService& orthanc) :
-      webCallback_(*this),
-      userCallback_(callback),
-      orthanc_(orthanc),
-      state_(State_Initialization)
-    {
-    }
-
-    void ScheduleLoadSeries(const std::string& seriesId)
-    {
-      if (state_ != State_Initialization)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        state_ = State_LoadingGeometry;
-        std::string uri = "/series/" + seriesId + "/instances-tags";
-        orthanc_.ScheduleGetRequest(webCallback_, uri, Operation::DownloadSeriesGeometry());
-      }
-    }
-
-    void ScheduleLoadInstance(const std::string& instanceId,
-                              unsigned int frame)
-    {
-      if (state_ != State_Initialization)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        state_ = State_LoadingGeometry;
-        std::string uri = "/instances/" + instanceId + "/tags";
-        orthanc_.ScheduleGetRequest
-          (webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId, frame));
-      }
-    }
-
-    size_t GetSliceCount() const
-    {
-      if (state_ != State_GeometryReady)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-
-      return slices_.GetSliceCount();
-    }
-
-    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)
-    {
-      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(webCallback_, uri, Operation::DownloadSliceImage(index, slice));
-      }
-    }
-  };
-
-
   class Tata : public OrthancSlicesLoader::ICallback
   {
   public: