# HG changeset patch # User Sebastien Jodogne # Date 1557934258 -7200 # Node ID e9339f2b5de7db226a398cd02553f688d4127f93 # Parent 419e5662d7a5cb886efba83a8e5f10980d8852fe refactoring of VolumeImage diff -r 419e5662d7a5 -r e9339f2b5de7 Framework/Toolbox/SlicesSorter.cpp --- 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(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; + } } diff -r 419e5662d7a5 -r e9339f2b5de7 Framework/Toolbox/SlicesSorter.h --- 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; }; } diff -r 419e5662d7a5 -r e9339f2b5de7 Framework/Volumes/ImageBuffer3D.cpp --- 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)"; } diff -r 419e5662d7a5 -r e9339f2b5de7 Resources/CMake/OrthancStoneConfiguration.cmake --- 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"'. diff -r 419e5662d7a5 -r e9339f2b5de7 Samples/Sdl/Loader.cpp --- 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 slices_; + std::auto_ptr image_; + + const DicomInstanceParameters& GetSliceParameters(size_t index) const + { + return dynamic_cast(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 slices(new OrthancStone::SlicesSorter); + for (size_t i = 0; i < instances.size(); i++) { Orthanc::DicomMap dicom; @@ -1496,16 +1627,10 @@ std::auto_ptr 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 image_; - OrthancStone::SlicesSorter slices_; + bool active_; + VolumeImage image_; public: AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) :