changeset 667:e9339f2b5de7

refactoring of VolumeImage
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 15 May 2019 17:30:58 +0200
parents 419e5662d7a5
children 6e13c7f98168
files Framework/Toolbox/SlicesSorter.cpp Framework/Toolbox/SlicesSorter.h Framework/Volumes/ImageBuffer3D.cpp Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/Loader.cpp
diffstat 5 files changed, 180 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Toolbox/SlicesSorter.cpp	Wed May 15 12:28:58 2019 +0200
+++ b/Framework/Toolbox/SlicesSorter.cpp	Wed May 15 17:30:58 2019 +0200
@@ -285,4 +285,42 @@
 
     return found;
   }
+
+
+  double SlicesSorter::ComputeSpacingBetweenSlices() const
+  {
+    if (GetSlicesCount() <= 1)
+    {
+      // This is a volume that is empty or that contains one single
+      // slice: Choose a dummy z-dimension for voxels
+      return 1.0;
+    }
+    
+    const OrthancStone::CoordinateSystem3D& reference = GetSliceGeometry(0);
+
+    double referencePosition = reference.ProjectAlongNormal(reference.GetOrigin());
+        
+    double p = reference.ProjectAlongNormal(GetSliceGeometry(1).GetOrigin());
+    double spacingZ = p - referencePosition;
+
+    if (spacingZ <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                      "Please call the Sort() method before");
+    }
+
+    for (size_t i = 1; i < GetSlicesCount(); i++)
+    {
+      OrthancStone::Vector p = reference.GetOrigin() + spacingZ * static_cast<double>(i) * reference.GetNormal();        
+      double d = boost::numeric::ublas::norm_2(p - GetSliceGeometry(i).GetOrigin());
+
+      if (!OrthancStone::LinearAlgebra::IsNear(d, 0, 0.001 /* tolerance expressed in mm */))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "The origins of the slices of a volume image are not regularly spaced");
+      }
+    }
+
+    return spacingZ;
+  }
 }
--- a/Framework/Toolbox/SlicesSorter.h	Wed May 15 12:28:58 2019 +0200
+++ b/Framework/Toolbox/SlicesSorter.h	Wed May 15 17:30:58 2019 +0200
@@ -88,5 +88,8 @@
     bool LookupClosestSlice(size_t& index,
                             double& distance,
                             const CoordinateSystem3D& slice) const;
+
+    // WARNING - The slices must have been sorted before calling this method
+    double ComputeSpacingBetweenSlices() const;
   };
 }
--- a/Framework/Volumes/ImageBuffer3D.cpp	Wed May 15 12:28:58 2019 +0200
+++ b/Framework/Volumes/ImageBuffer3D.cpp	Wed May 15 17:30:58 2019 +0200
@@ -118,8 +118,9 @@
   {
     LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1);
 
-    LOG(INFO) << "Created an image of "
-              << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB";
+    LOG(INFO) << "Created a 3D image of size " << width << "x" << height
+              << "x" << depth << " in " << Orthanc::EnumerationToString(format)
+              << " (" << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB)";
   }
 
 
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 15 12:28:58 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 15 17:30:58 2019 +0200
@@ -147,7 +147,7 @@
 
 if (ENABLE_OPENGL)
   if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
-    # If including "FindOpenGL.cmake" using Emscripten (targetting
+    # If including "FindOpenGL.cmake" using Emscripten (targeting
     # WebAssembly), the "OPENGL_LIBRARIES" value incorrectly includes
     # the "nul" library, which leads to warning message in Emscripten:
     # 'shared:WARNING: emcc: cannot find library "nul"'.
--- a/Samples/Sdl/Loader.cpp	Wed May 15 12:28:58 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Wed May 15 17:30:58 2019 +0200
@@ -1359,6 +1359,7 @@
       }
     }
 
+    // TODO - Is this necessary?
     bool FrameContainsPlane(unsigned int frame,
                             const OrthancStone::CoordinateSystem3D& plane) const
     {
@@ -1450,6 +1451,134 @@
   };
 
 
+  class VolumeImage : public boost::noncopyable
+  {
+  private:
+    std::auto_ptr<OrthancStone::SlicesSorter>   slices_;
+    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;
+
+    const DicomInstanceParameters& GetSliceParameters(size_t index) const
+    {
+      return dynamic_cast<const DicomInstanceParameters&>(slices_->GetSlicePayload(index));
+    }
+
+    void CheckSlice(size_t index,
+                    const OrthancStone::CoordinateSystem3D& reference,
+                    const DicomInstanceParameters& a) const
+    {
+      const OrthancStone::CoordinateSystem3D& slice = slices_->GetSliceGeometry(index);
+      const DicomInstanceParameters& b = GetSliceParameters(index);
+      
+      if (!OrthancStone::GeometryToolbox::IsParallel(reference.GetNormal(), slice.GetNormal()))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "A slice in the volume image is not parallel to the others");
+      }
+
+      if (a.GetExpectedPixelFormat() != b.GetExpectedPixelFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
+                                        "The pixel format changes across the slices of the volume image");
+      }
+
+      if (a.GetImageInformation().GetWidth() != b.GetImageInformation().GetWidth() ||
+          a.GetImageInformation().GetHeight() != b.GetImageInformation().GetHeight())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
+                                        "The width/height of slices are not constant in the volume image");
+      }
+
+      if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) ||
+          !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY()))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
+                                        "The pixel spacing of the slices change across the volume image");
+      }
+    }
+
+    
+    void CheckVolume()
+    {
+      if (slices_->GetSlicesCount() != 0)
+      {
+        const OrthancStone::CoordinateSystem3D& reference = slices_->GetSliceGeometry(0);
+        const DicomInstanceParameters& dicom = GetSliceParameters(0);
+
+        for (size_t i = 1; i < slices_->GetSlicesCount(); i++)
+        {
+          CheckSlice(i, reference, dicom);
+        }
+      }
+    }
+
+    
+  public:
+    VolumeImage()
+    {
+    }
+
+    // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
+    void SetGeometry(OrthancStone::SlicesSorter* slices)  // Takes ownership
+    {
+      image_.reset();
+      slices_.reset(slices);
+      
+      if (!slices_->Sort())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Cannot sort the 3D slices of a DICOM series");          
+      }
+
+      CheckVolume();
+
+      const double spacingZ = slices_->ComputeSpacingBetweenSlices();
+      LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
+      
+      const DicomInstanceParameters& parameters = GetSliceParameters(0);
+
+      image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(),
+                                                   parameters.GetImageInformation().GetWidth(),
+                                                   parameters.GetImageInformation().GetHeight(),
+                                                   slices_->GetSlicesCount(), false /* don't compute range */));      
+
+      image_->SetAxialGeometry(slices_->GetSliceGeometry(0));
+      image_->SetVoxelDimensions(parameters.GetPixelSpacingX(), parameters.GetPixelSpacingY(), spacingZ);
+      image_->Clear();
+    }
+
+    bool IsGeometryReady() const
+    {
+      return (image_.get() != NULL &&
+              slices_.get() != NULL);
+    }
+
+    const OrthancStone::SlicesSorter& GetSlices() const
+    {
+      if (!IsGeometryReady())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return *slices_;
+      }
+    }
+
+    const OrthancStone::ImageBuffer3D& GetImage() const
+    {
+      if (!IsGeometryReady())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return *image_;
+      }
+    }      
+  };
+  
+  
+
   class AxialVolumeOrthancLoader : public OrthancStone::IObserver
   {
   private:
@@ -1488,6 +1617,8 @@
 
         Json::Value::Members instances = value.getMemberNames();
 
+        std::auto_ptr<OrthancStone::SlicesSorter> slices(new OrthancStone::SlicesSorter);
+        
         for (size_t i = 0; i < instances.size(); i++)
         {
           Orthanc::DicomMap dicom;
@@ -1496,16 +1627,10 @@
           std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
 
           OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
-          that_.slices_.AddSlice(geometry, instance.release());
+          slices->AddSlice(geometry, instance.release());
         }
 
-        if (!that_.slices_.Sort())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                          "Cannot sort the 3D slices of a DICOM series");          
-        }
-
-        printf("series sorted %d => %d\n", instances.size(), that_.slices_.GetSlicesCount());
+        that_.image_.SetGeometry(slices.release());
       }
     };
 
@@ -1539,9 +1664,8 @@
     };
 
 
-    bool                                        active_;
-    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;
-    OrthancStone::SlicesSorter                  slices_;
+    bool         active_;
+    VolumeImage  image_;
 
   public:
     AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) :