diff UnitTestsSources/UnitTestsMain.cpp @ 87:4a541cd4fa83 wasm

OrthancVolumeImageLoader
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 26 May 2017 15:31:58 +0200
parents cee8f308a4bc
children 90bf4116a23c
line wrap: on
line diff
--- a/UnitTestsSources/UnitTestsMain.cpp	Fri May 26 13:42:50 2017 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Fri May 26 15:31:58 2017 +0200
@@ -28,6 +28,11 @@
 #include "../Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.h"
 #include "../Resources/Orthanc/Core/OrthancException.h"
 
+#include "../Framework/Toolbox/IVolumeSlicesObserver.h"
+#include "../Framework/Volumes/ImageBuffer3D.h"
+#include "../Framework/Toolbox/DownloadStack.h"
+#include "../Resources/Orthanc/Core/Images/ImageProcessing.h"
+
 #include <boost/lexical_cast.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/thread/thread.hpp> 
@@ -68,10 +73,193 @@
       printf("ERROR 2\n");
     }
   };
+
+
+  class OrthancVolumeImageLoader : private OrthancSlicesLoader::ICallback
+  { 
+  private:
+    OrthancSlicesLoader           loader_;
+    IVolumeSlicesObserver*        observer_;
+    std::auto_ptr<ImageBuffer3D>  image_;
+    std::auto_ptr<DownloadStack>  downloadStack_;
+
+    
+    void ScheduleSliceDownload()
+    {
+      assert(downloadStack_.get() != NULL);
+
+      unsigned int slice;
+      if (downloadStack_->Pop(slice))
+      {
+        loader_.ScheduleLoadSliceImage(slice);
+      }
+    }
+
+
+    static bool IsCompatible(const Slice& a, 
+                             const Slice& b)
+    {
+      if (!GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(),
+                                       b.GetGeometry().GetNormal()))
+      {
+        LOG(ERROR) << "Some slice in the volume image is not parallel to the others";
+        return false;
+      }
+
+      if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat())
+      {
+        LOG(ERROR) << "The pixel format changes across the slices of the volume image";
+        return false;
+      }
+
+      if (a.GetWidth() != b.GetWidth() ||
+          a.GetHeight() != b.GetHeight())
+      {
+        LOG(ERROR) << "The width/height of the slices change across the volume image";
+        return false;
+      }
+
+      if (!GeometryToolbox::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
+          !GeometryToolbox::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY()))
+      {
+        LOG(ERROR) << "The pixel spacing of the slices change across the volume image";
+        return false;
+      }
+
+      return true;
+    }
+
+
+    static double GetDistance(const Slice& a, 
+                              const Slice& b)
+    {
+      return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) - 
+                  a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin()));
+    }
+
+
+    virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader)
+    {
+      if (loader.GetSliceCount() == 0)
+      {
+        LOG(ERROR) << "Empty volume image";
+        return;
+      }
+
+      for (size_t i = 1; i < loader.GetSliceCount(); i++)
+      {
+        if (!IsCompatible(loader.GetSlice(0), loader.GetSlice(i)))
+        {
+          return;
+        }
+      }
+
+      double spacingZ;
+
+      if (loader.GetSliceCount() > 1)
+      {
+        spacingZ = GetDistance(loader.GetSlice(0), loader.GetSlice(1));
+      }
+      else
+      {
+        // This is a volume with one single slice: Choose a dummy
+        // z-dimension for voxels
+        spacingZ = 1;
+      }
+      
+      for (size_t i = 1; i < loader.GetSliceCount(); i++)
+      {
+        if (!GeometryToolbox::IsNear(spacingZ, GetDistance(loader.GetSlice(i - 1), loader.GetSlice(i))))
+        {
+          LOG(ERROR) << "The distance between successive slices is not constant in a volume image";
+          return;
+        }
+      }
+
+      unsigned int width = loader.GetSlice(0).GetWidth();
+      unsigned int height = loader.GetSlice(0).GetHeight();
+      Orthanc::PixelFormat format = loader.GetSlice(0).GetConverter().GetExpectedPixelFormat();
+      LOG(INFO) << "Creating a volume image of size " << width << "x" << height 
+                << "x" << loader.GetSliceCount() << " in " << Orthanc::EnumerationToString(format);
+
+      image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount()));
+      image_->SetAxialGeometry(loader.GetSlice(0).GetGeometry());
+      image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), 
+                                 loader.GetSlice(0).GetPixelSpacingY(), spacingZ);
+      image_->Clear();
+
+      downloadStack_.reset(new DownloadStack(loader.GetSliceCount()));
+
+      for (unsigned int i = 0; i < 4; i++)  // Limit to 4 simultaneous downloads
+      {
+        ScheduleSliceDownload();
+      }
+    }
+
+    virtual void NotifyGeometryError(const OrthancSlicesLoader& loader)
+    {
+      LOG(ERROR) << "Unable to download a volume image";
+    }
+
+    virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice,
+                                       Orthanc::ImageAccessor* image)
+    {
+      std::auto_ptr<Orthanc::ImageAccessor> protection(image);
+
+      {
+        ImageBuffer3D::SliceWriter writer(*image_, VolumeProjection_Axial, 0);
+        Orthanc::ImageProcessing::Copy(writer.GetAccessor(), *protection);
+      }
+
+      ScheduleSliceDownload();
+    }
+
+    virtual void NotifySliceImageError(const OrthancSlicesLoader& loader,
+                                       unsigned int sliceIndex,
+                                       const Slice& slice)
+    {
+      LOG(ERROR) << "Cannot download slice " << sliceIndex << " in a volume image";
+      ScheduleSliceDownload();
+    }
+
+  public:
+    OrthancVolumeImageLoader(IWebService& orthanc) : 
+      loader_(*this, orthanc),
+      observer_(NULL)
+    {
+    }
+
+    void ScheduleLoadSeries(const std::string& seriesId)
+    {
+      loader_.ScheduleLoadSeries(seriesId);
+    }
+
+    void ScheduleLoadInstance(const std::string& instanceId,
+                              unsigned int frame)
+    {
+      loader_.ScheduleLoadInstance(instanceId, frame);
+    }
+
+    void SetObserver(IVolumeSlicesObserver& observer)
+    {
+      if (observer_ == NULL)
+      {
+        observer_ = &observer;
+      }
+      else
+      {
+        // Cannot add more than one observer
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+  };
 }
 
 
-TEST(Toto, Tutu)
+TEST(Toto, DISABLED_Tutu)
 {
   OrthancStone::Oracle oracle(4);
   oracle.Start();
@@ -98,6 +286,24 @@
 }
 
 
+TEST(Toto, Tata)
+{
+  OrthancStone::Oracle oracle(4);
+  oracle.Start();
+
+  Orthanc::WebServiceParameters web;
+  OrthancStone::OracleWebService orthanc(oracle, web);
+  OrthancStone::OrthancVolumeImageLoader volume(orthanc);
+
+  volume.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0);
+  //volume.ScheduleLoadSeries("318603c5-03e8cffc-a82b6ee1-3ccd3c1e-18d7e3bb"); // COMUNIX PET
+  //volume.ScheduleLoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // Delphine sagital
+
+  boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
+
+  oracle.Stop();
+}
+
 
 int main(int argc, char **argv)
 {