changeset 67:acb60cbb8301 wasm

refactoring SeriesLoader
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 18 May 2017 17:51:41 +0200
parents 298f375dcb68
children 1526d38ef6da
files Framework/Toolbox/MessagingToolbox.cpp Framework/Toolbox/MessagingToolbox.h Framework/Toolbox/OrthancSeriesLoader.cpp Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h UnitTestsSources/UnitTestsMain.cpp
diffstat 6 files changed, 382 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Toolbox/MessagingToolbox.cpp	Wed May 17 22:03:09 2017 +0200
+++ b/Framework/Toolbox/MessagingToolbox.cpp	Thu May 18 17:51:41 2017 +0200
@@ -152,8 +152,19 @@
     }
 
 
-    static void ParseJson(Json::Value& target,
-                          const std::string& source)
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size)
+    {
+      Json::Reader reader;
+      return reader.parse(reinterpret_cast<const char*>(content),
+                          reinterpret_cast<const char*>(content) + size,
+                          target);
+    }
+
+
+    static void ParseJsonException(Json::Value& target,
+                                   const std::string& source)
     {
       Json::Reader reader;
       if (!reader.parse(source, target))
@@ -169,7 +180,7 @@
     {
       std::string tmp;
       orthanc.RestApiGet(tmp, uri);
-      ParseJson(target, tmp);
+      ParseJsonException(target, tmp);
     }
 
 
@@ -180,7 +191,7 @@
     {
       std::string tmp;
       orthanc.RestApiPost(tmp, uri, body);
-      ParseJson(target, tmp);
+      ParseJsonException(target, tmp);
     }
 
 
--- a/Framework/Toolbox/MessagingToolbox.h	Wed May 17 22:03:09 2017 +0200
+++ b/Framework/Toolbox/MessagingToolbox.h	Thu May 18 17:51:41 2017 +0200
@@ -58,6 +58,10 @@
     };
 
 
+    bool ParseJson(Json::Value& target,
+                   const void* content,
+                   size_t size);
+
     void RestApiGet(Json::Value& target,
                     OrthancPlugins::IOrthancConnection& orthanc,
                     const std::string& uri);
--- a/Framework/Toolbox/OrthancSeriesLoader.cpp	Wed May 17 22:03:09 2017 +0200
+++ b/Framework/Toolbox/OrthancSeriesLoader.cpp	Thu May 18 17:51:41 2017 +0200
@@ -327,7 +327,7 @@
     slices_(new SetOfSlices)
   {
     /**
-     * The function "LoadSeriesFast()" might now behave properly if
+     * The function "LoadSeriesFast()" might not behave properly if
      * some slice has some outsider value for its normal, which
      * happens sometimes on reprojected series (e.g. coronal and
      * sagittal of Delphine). Don't use it.
--- a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp	Wed May 17 22:03:09 2017 +0200
+++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp	Thu May 18 17:51:41 2017 +0200
@@ -174,6 +174,13 @@
   }
 
 
+  FullOrthancDataset::FullOrthancDataset(const Json::Value& root) :
+    root_(root)
+  {
+    CheckRoot();
+  }
+
+
   bool FullOrthancDataset::GetStringValue(std::string& result,
                                           const DicomPath& path) const
   {
--- a/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h	Wed May 17 22:03:09 2017 +0200
+++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h	Thu May 18 17:51:41 2017 +0200
@@ -58,6 +58,8 @@
     FullOrthancDataset(const void* content,
                        size_t size);
 
+    FullOrthancDataset(const Json::Value& root);
+
     virtual bool GetStringValue(std::string& result,
                                 const DicomPath& path) const;
 
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed May 17 22:03:09 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Thu May 18 17:51:41 2017 +0200
@@ -27,44 +27,13 @@
 #include "../Framework/Widgets/LayerWidget.h"
 
 
+#include "../Framework/Toolbox/MessagingToolbox.h"
+#include "../Framework/Toolbox/DicomFrameConverter.h"
+
 namespace OrthancStone
 {
-  class Tata : public ILayerSource::IObserver
-  {
-  public:
-    virtual void NotifySourceChange(ILayerSource& source)
-    {
-      printf("Source change\n");
-
-      OrthancStone::SliceGeometry slice;
-      double x1, y1, x2, y2;
-      printf(">> %d: ", source.GetExtent(x1, y1, x2, y2, slice));
-      printf("(%f,%f) (%f,%f)\n", x1, y1, x2, y2);
-    }
-
-    virtual void NotifySliceChange(ILayerSource& source,
-                                   const SliceGeometry& slice)
-    {
-      printf("Slice change\n");
-    }
-
-    virtual void NotifyLayerReady(ILayerRenderer* layer,
-                                  ILayerSource& source,
-                                  const SliceGeometry& viewportSlice)
-    {
-      std::auto_ptr<ILayerRenderer> tmp(layer);
-      
-    }
-
-    virtual void NotifyLayerError(ILayerSource& source,
-                                  const SliceGeometry& viewportSlice)
-    {
-    }
-  };
-
-
-
-  /*class OrthancInstanceLoader : public IWebService::ICallback
+  class SeriesLoader :
+    public IWebService::ICallback // TODO move to PImpl
   {
   public:
     class ICallback : public boost::noncopyable
@@ -73,104 +42,394 @@
       virtual ~ICallback()
       {
       }
-      
-      virtual void NotifyInstanceLoaded(const std::string& instanceId,
-                                        const OrthancPlugins::FullOrthancDataset& dicom) = 0;
-      
-      virtual void NotifyInstanceError(const std::string& instanceId) = 0;
+
+      virtual void NotifyGeometryReady(const SeriesLoader& series) = 0;
+
+      virtual void NotifyGeometryError(const SeriesLoader& series) = 0;
     };
-
+    
   private:
-    class Operation : public Orthanc::IDynamicObject
+    class Slice : public boost::noncopyable
     {
     private:
-      ICallback&   callback_;
-      std::string  instanceId_;
-
+      std::string          instanceId_;
+      SliceGeometry        geometry_;
+      double               thickness_;
+      unsigned int         width_;
+      unsigned int         height_;
+      double               projectionAlongNormal_;
+      DicomFrameConverter  converter_;
+      
     public:
-      Operation(ICallback& callback,
-                const std::string& instanceId) :
-        callback_(callback),
-        instanceId_(instanceId)
+      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_;
       }
 
-      ICallback& GetCallback()
+      const SliceGeometry& GetGeometry() const
+      {
+        return geometry_;
+      }
+
+      double GetThickness() const
       {
-        return callback_;
+        return thickness_;
+      }
+
+      unsigned int GetWidth() const
+      {
+        return width_;
       }
 
-      const std::string& GetInstanceId() const
+      unsigned int GetHeight() const
+      {
+        return height_;
+      }
+
+      const DicomFrameConverter& GetConverter() const
       {
-        return instanceId_;
+        return converter_;
+      }
+
+      void SetNormal(const Vector& normal)
+      {
+        projectionAlongNormal_ = boost::numeric::ublas::inner_prod(geometry_.GetOrigin(), normal);
+      }
+
+      double GetProjectionAlongNormal() const
+      {
+        return projectionAlongNormal_;
       }
     };
 
-    IWebService&  orthanc_;
 
-  public:
-    OrthancInstanceLoader(IWebService&  orthanc) :
-      orthanc_(orthanc)
+    class SetOfSlices : public boost::noncopyable
     {
-    }
+    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);
+            }
+          }
+        }
 
-    void ScheduleLoadInstance(ICallback& callback,
-                              const std::string& instanceId)
+        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);
+      }
+    };
+    
+    
+    enum Mode
+    {
+      Mode_Geometry
+    };
+
+    class Operation : public Orthanc::IDynamicObject
     {
-      orthanc_.ScheduleGetRequest(*this,
-                                  "/instances/" + instanceId + "/tags",
-                                  new Operation(callback, instanceId));
+    private:
+      Mode         mode_;
+      unsigned int instance_;
+
+      Operation(Mode mode) :
+        mode_(mode)
+      {
+      }
+
+    public:
+      Mode GetMode() const
+      {
+        return mode_;
+      }
+      
+      static Operation* DownloadGeometry()
+      {
+        return new Operation(Mode_Geometry);
+      }
+    };
+    
+    ICallback&    callback_;
+    IWebService&  orthanc_;
+    std::string   seriesId_;
+    SetOfSlices   slices_;
+
+
+    void ParseGeometry(const void* answer,
+                       size_t size)
+    {
+      Json::Value series;
+      if (!MessagingToolbox::ParseJson(series, answer, size) ||
+          series.type() != Json::objectValue)
+      {
+        callback_.NotifyGeometryError(*this);
+        return;
+      }
+
+      Json::Value::Members instances = series.getMemberNames();
+
+      slices_.Reserve(instances.size());
+
+      for (size_t i = 0; i < instances.size(); i++)
+      {
+        slices_.AddSlice(instances[i], series[instances[i]]);
+      }
+
+      bool ok = false;
+      
+      if (slices_.GetSliceCount() > 0)
+      {
+        Vector normal;
+        slices_.SelectNormal(normal);
+        slices_.FilterNormal(normal);
+        slices_.Sort(normal);
+        ok = true;
+      }
+
+      if (ok)
+      {
+        printf("%d\n", slices_.GetSliceCount());
+        callback_.NotifyGeometryReady(*this);
+      }
+      else
+      {
+        LOG(ERROR) << "This series is empty";
+        callback_.NotifyGeometryError(*this);
+        return;
+      }
     }
     
-    void NotifySuccess(const std::string& uri,
-                       const void* answer,
-                       size_t answerSize,
-                       Orthanc::IDynamicObject* payload)
+    
+  public:
+    SeriesLoader(ICallback& callback,
+                 IWebService& orthanc,
+                 const std::string& seriesId) :
+      callback_(callback),
+      orthanc_(orthanc),
+      seriesId_(seriesId)
     {
-      std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload));
+      std::string uri = "/series/" + seriesId + "/instances-tags";
+      orthanc.ScheduleGetRequest(*this, uri, Operation::DownloadGeometry());
+    }
 
-      try
+    const std::string& GetSeriesId() const
+    {
+      return seriesId_;
+    }
+
+    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())
       {
-        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
-        operation->GetCallback().NotifyInstanceLoaded(operation->GetInstanceId(), dataset);
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        operation->GetCallback().NotifyInstanceError(operation->GetInstanceId());
+        case Mode_Geometry:
+          ParseGeometry(answer, answerSize);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
     }
-      
-    void NotifyError(const std::string& uri,
-                     Orthanc::IDynamicObject* payload)
+
+    virtual void NotifyError(const std::string& uri,
+                             Orthanc::IDynamicObject* payload)
     {
-      std::auto_ptr<Operation> operation(reinterpret_cast<Operation*>(payload));
-
+      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
       LOG(ERROR) << "Cannot download " << uri;
-      operation->GetCallback().NotifyInstanceError(operation->GetInstanceId());
+
+      switch (operation->GetMode())
+      {
+        case Mode_Geometry:
+          callback_.NotifyGeometryError(*this);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
     }
-    };*/
+  };
+
 
-  
+  class Tata : public SeriesLoader::ICallback
+  {
+  public:
+    virtual void NotifyGeometryReady(const SeriesLoader& series)
+    {
+      printf("Done %s\n", series.GetSeriesId().c_str());
+    }
 
+    virtual void NotifyGeometryError(const SeriesLoader& series)
+    {
+      printf("Error %s\n", series.GetSeriesId().c_str());
+    }
+  };
 }
 
 
-
 TEST(Toto, Tutu)
 {
   Orthanc::WebServiceParameters web;
   OrthancStone::OrthancWebService orthanc(web);
-  OrthancStone::OrthancFrameLayerSource source(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0);
 
   OrthancStone::Tata tata;
-  source.SetObserver(tata);
-
-  OrthancStone::SliceGeometry slice;
-  source.ScheduleLayerCreation(slice);
-
-
-  OrthancStone::LayerWidget widget;
-  printf(">> %d\n", widget.AddLayer(new OrthancStone::OrthancFrameLayerSource(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0)));
+  //OrthancStone::SeriesLoader loader(tata, orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5");
+  OrthancStone::SeriesLoader loader(tata, orthanc, "67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");
 }