Mercurial > hg > orthanc-stone
changeset 87:4a541cd4fa83 wasm
OrthancVolumeImageLoader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 26 May 2017 15:31:58 +0200 |
parents | 02c3a7a4938f |
children | 90bf4116a23c |
files | Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/LayerSourceBase.h Framework/Toolbox/DownloadStack.cpp Framework/Toolbox/DownloadStack.h Framework/Volumes/ImageBuffer3D.cpp Framework/Volumes/ImageBuffer3D.h Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Platforms/Generic/Oracle.cpp Platforms/Generic/Oracle.h Resources/CMake/OrthancStone.cmake UnitTestsSources/UnitTestsMain.cpp |
diffstat | 13 files changed, 286 insertions(+), 82 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Layers/ILayerSource.h Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Layers/ILayerSource.h Fri May 26 15:31:58 2017 +0200 @@ -38,25 +38,25 @@ // Triggered as soon as the source has enough information to // answer to "GetExtent()" - virtual void NotifyGeometryReady(ILayerSource& source) = 0; + virtual void NotifyGeometryReady(const ILayerSource& source) = 0; - virtual void NotifyGeometryError(ILayerSource& source) = 0; + virtual void NotifyGeometryError(const ILayerSource& source) = 0; - // Triggered if the extent or the content of the volume has changed - virtual void NotifySourceChange(ILayerSource& source) = 0; + // Triggered if the content of the volume has changed + virtual void NotifyContentChange(const ILayerSource& source) = 0; // Triggered if the content of some slice in the source volume has changed - virtual void NotifySliceChange(ILayerSource& source, + virtual void NotifySliceChange(const ILayerSource& source, const Slice& slice) = 0; // The layer must be deleted by the observer. "layer" will never // be "NULL", otherwise "NotifyLayerError()" would have been // called. virtual void NotifyLayerReady(ILayerRenderer *layer, - ILayerSource& source, + const ILayerSource& source, const Slice& slice) = 0; - virtual void NotifyLayerError(ILayerSource& source, + virtual void NotifyLayerError(const ILayerSource& source, const SliceGeometry& slice) = 0; };
--- a/Framework/Layers/LayerSourceBase.cpp Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Layers/LayerSourceBase.cpp Fri May 26 15:31:58 2017 +0200 @@ -41,11 +41,11 @@ } } - void LayerSourceBase::NotifySourceChange() + void LayerSourceBase::NotifyContentChange() { if (observer_ != NULL) { - observer_->NotifySourceChange(*this); + observer_->NotifyContentChange(*this); } }
--- a/Framework/Layers/LayerSourceBase.h Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Layers/LayerSourceBase.h Fri May 26 15:31:58 2017 +0200 @@ -35,7 +35,7 @@ void NotifyGeometryError(); - void NotifySourceChange(); + void NotifyContentChange(); void NotifySliceChange(const Slice& slice);
--- a/Framework/Toolbox/DownloadStack.cpp Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Toolbox/DownloadStack.cpp Fri May 26 15:31:58 2017 +0200 @@ -174,23 +174,23 @@ } - void DownloadStack::Writer::SetTopNode(unsigned int value) + void DownloadStack::SetTopNode(unsigned int value) { - if (value >= that_.nodes_.size()) + if (value >= nodes_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - that_.SetTopNodeInternal(value); + SetTopNodeInternal(value); } - void DownloadStack::Writer::SetTopNodePermissive(int value) + void DownloadStack::SetTopNodePermissive(int value) { if (value >= 0 && - value < static_cast<int>(that_.nodes_.size())) + value < static_cast<int>(nodes_.size())) { - that_.SetTopNodeInternal(value); + SetTopNodeInternal(value); } } }
--- a/Framework/Toolbox/DownloadStack.h Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Toolbox/DownloadStack.h Fri May 26 15:31:58 2017 +0200 @@ -24,8 +24,6 @@ #include <vector> #include <boost/noncopyable.hpp> -#include <boost/thread/mutex.hpp> // TODO remove - namespace OrthancStone { class DownloadStack : public boost::noncopyable @@ -55,22 +53,8 @@ bool Pop(unsigned int& value); - class Writer : public boost::noncopyable - { - private: - DownloadStack& that_; - boost::mutex::scoped_lock lock_; - - public: - Writer(DownloadStack& that) : - that_(that) - //lock_(that.mutex_) - { - } - - void SetTopNode(unsigned int value); + void SetTopNode(unsigned int value); - void SetTopNodePermissive(int value); - }; + void SetTopNodePermissive(int value); }; }
--- a/Framework/Volumes/ImageBuffer3D.cpp Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Volumes/ImageBuffer3D.cpp Fri May 26 15:31:58 2017 +0200 @@ -24,6 +24,8 @@ #include "../../Resources/Orthanc/Core/Images/ImageProcessing.h" #include "../../Resources/Orthanc/Core/OrthancException.h" +#include <string.h> + namespace OrthancStone { Orthanc::ImageAccessor ImageBuffer3D::GetAxialSliceAccessor(unsigned int slice, @@ -122,14 +124,12 @@ void ImageBuffer3D::Clear() { - WriteLock lock(mutex_); - Orthanc::ImageProcessing::Set(image_, 0); + memset(image_.GetBuffer(), 0, image_.GetHeight() * image_.GetPitch()); } void ImageBuffer3D::SetAxialGeometry(const SliceGeometry& geometry) { - WriteLock lock(mutex_); axialGeometry_ = geometry; } @@ -146,7 +146,6 @@ } { - WriteLock lock(mutex_); GeometryToolbox::AssignVector(voxelDimensions_, x, y, z); } } @@ -154,8 +153,6 @@ Vector ImageBuffer3D::GetVoxelDimensions(VolumeProjection projection) { - ReadLock lock(mutex_); - Vector result; switch (projection) { @@ -260,8 +257,7 @@ ImageBuffer3D::SliceReader::SliceReader(ImageBuffer3D& that, VolumeProjection projection, - unsigned int slice) : - lock_(that.mutex_) + unsigned int slice) { switch (projection) { @@ -286,10 +282,13 @@ void ImageBuffer3D::SliceWriter::Flush() { - if (sagittal_.get() != NULL) + if (modified_) { - // TODO - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + if (sagittal_.get() != NULL) + { + // TODO + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } } } @@ -297,7 +296,7 @@ ImageBuffer3D::SliceWriter::SliceWriter(ImageBuffer3D& that, VolumeProjection projection, unsigned int slice) : - lock_(that.mutex_) + modified_(false) { switch (projection) {
--- a/Framework/Volumes/ImageBuffer3D.h Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Volumes/ImageBuffer3D.h Fri May 26 15:31:58 2017 +0200 @@ -27,22 +27,11 @@ #include "../../Resources/Orthanc/Core/Images/Image.h" -#include <boost/thread/shared_mutex.hpp> - -#if defined(_WIN32) -# include <boost/thread/win32/mutex.hpp> -#endif - namespace OrthancStone { class ImageBuffer3D : public boost::noncopyable { private: - typedef boost::shared_mutex Mutex; - typedef boost::unique_lock<Mutex> WriteLock; - typedef boost::shared_lock<Mutex> ReadLock; - - Mutex mutex_; SliceGeometry axialGeometry_; Vector voxelDimensions_; Orthanc::Image image_; @@ -107,7 +96,6 @@ class SliceReader : public boost::noncopyable { private: - ReadLock lock_; Orthanc::ImageAccessor accessor_; std::auto_ptr<Orthanc::Image> sagittal_; // Unused for axial and coronal @@ -126,7 +114,7 @@ class SliceWriter : public boost::noncopyable { private: - WriteLock lock_; + bool modified_; Orthanc::ImageAccessor accessor_; std::auto_ptr<Orthanc::Image> sagittal_; // Unused for axial and coronal @@ -142,8 +130,14 @@ Flush(); } + const Orthanc::ImageAccessor& GetAccessor() const + { + return accessor_; + } + Orthanc::ImageAccessor& GetAccessor() { + modified_ = true; return accessor_; } };
--- a/Framework/Widgets/LayerWidget.cpp Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Widgets/LayerWidget.cpp Fri May 26 15:31:58 2017 +0200 @@ -144,7 +144,7 @@ bool LayerWidget::LookupLayer(size_t& index /* out */, - ILayerSource& layer) const + const ILayerSource& layer) const { LayersIndex::const_iterator found = layersIndex_.find(&layer); @@ -394,7 +394,7 @@ } - void LayerWidget::NotifyGeometryReady(ILayerSource& source) + void LayerWidget::NotifyGeometryReady(const ILayerSource& source) { size_t i; if (LookupLayer(i, source)) @@ -405,30 +405,38 @@ } - void LayerWidget::NotifyGeometryError(ILayerSource& source) + void LayerWidget::NotifyGeometryError(const ILayerSource& source) { LOG(ERROR) << "Cannot get geometry"; } - void LayerWidget::NotifySourceChange(ILayerSource& source) + void LayerWidget::NotifyContentChange(const ILayerSource& source) { - source.ScheduleLayerCreation(slice_); + size_t index; + if (LookupLayer(index, source)) + { + layers_[index]->ScheduleLayerCreation(slice_); + } } - void LayerWidget::NotifySliceChange(ILayerSource& source, + void LayerWidget::NotifySliceChange(const ILayerSource& source, const Slice& slice) { if (slice.ContainsPlane(slice_)) { - source.ScheduleLayerCreation(slice_); + size_t index; + if (LookupLayer(index, source)) + { + layers_[index]->ScheduleLayerCreation(slice_); + } } } void LayerWidget::NotifyLayerReady(ILayerRenderer* renderer, - ILayerSource& source, + const ILayerSource& source, const Slice& slice) { std::auto_ptr<ILayerRenderer> tmp(renderer); @@ -443,7 +451,7 @@ } - void LayerWidget::NotifyLayerError(ILayerSource& source, + void LayerWidget::NotifyLayerError(const ILayerSource& source, const SliceGeometry& slice) { size_t index; @@ -454,7 +462,7 @@ LOG(INFO) << "Unable to load a slice from layer " << index; double x1, y1, x2, y2; - if (GetAndFixExtent(x1, y1, x2, y2, source)) + if (GetAndFixExtent(x1, y1, x2, y2, *layers_[index])) { UpdateLayer(index, new MissingLayerRenderer(x1, y1, x2, y2), Slice(slice, THIN_SLICE_THICKNESS)); }
--- a/Framework/Widgets/LayerWidget.h Fri May 26 13:42:50 2017 +0200 +++ b/Framework/Widgets/LayerWidget.h Fri May 26 15:31:58 2017 +0200 @@ -35,7 +35,7 @@ private: class Scene; - typedef std::map<ILayerSource*, size_t> LayersIndex; + typedef std::map<const ILayerSource*, size_t> LayersIndex; bool started_; LayersIndex layersIndex_; @@ -47,7 +47,7 @@ bool LookupLayer(size_t& index /* out */, - ILayerSource& layer) const; + const ILayerSource& layer) const; bool GetAndFixExtent(double& x1, double& y1, @@ -55,20 +55,20 @@ double& y2, ILayerSource& source) const; - virtual void NotifyGeometryReady(ILayerSource& source); + virtual void NotifyGeometryReady(const ILayerSource& source); - virtual void NotifyGeometryError(ILayerSource& source); + virtual void NotifyGeometryError(const ILayerSource& source); - virtual void NotifySourceChange(ILayerSource& source); + virtual void NotifyContentChange(const ILayerSource& source); - virtual void NotifySliceChange(ILayerSource& source, + virtual void NotifySliceChange(const ILayerSource& source, const Slice& slice); virtual void NotifyLayerReady(ILayerRenderer* renderer, - ILayerSource& source, + const ILayerSource& source, const Slice& slice); - virtual void NotifyLayerError(ILayerSource& source, + virtual void NotifyLayerError(const ILayerSource& source, const SliceGeometry& slice);
--- a/Platforms/Generic/Oracle.cpp Fri May 26 13:42:50 2017 +0200 +++ b/Platforms/Generic/Oracle.cpp Fri May 26 15:31:58 2017 +0200 @@ -98,6 +98,11 @@ } } + Orthanc::SharedMessageQueue& GetQueue() + { + return queue_; + } + void Submit(IOracleCommand* command) { std::auto_ptr<IOracleCommand> protection(command); @@ -202,4 +207,10 @@ { pimpl_->Stop(); } + + + void Oracle::WaitEmpty() + { + pimpl_->GetQueue().WaitEmpty(50); + } }
--- a/Platforms/Generic/Oracle.h Fri May 26 13:42:50 2017 +0200 +++ b/Platforms/Generic/Oracle.h Fri May 26 15:31:58 2017 +0200 @@ -45,6 +45,8 @@ void Submit(IOracleCommand* command); + void WaitEmpty(); // For unit tests + void Stop(); }; }
--- a/Resources/CMake/OrthancStone.cmake Fri May 26 13:42:50 2017 +0200 +++ b/Resources/CMake/OrthancStone.cmake Fri May 26 15:31:58 2017 +0200 @@ -219,10 +219,10 @@ ${ORTHANC_STONE_DIR}/Framework/Viewport/CairoSurface.cpp ${ORTHANC_STONE_DIR}/Framework/Viewport/WidgetViewport.cpp ${ORTHANC_STONE_DIR}/Framework/Volumes/ImageBuffer3D.cpp - ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImage.cpp - ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImagePolicyBase.cpp - ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageProgressivePolicy.cpp - ${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageSimplePolicy.cpp + #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImage.cpp + #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImagePolicyBase.cpp + #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageProgressivePolicy.cpp + #${ORTHANC_STONE_DIR}/Framework/Volumes/VolumeImageSimplePolicy.cpp ${ORTHANC_STONE_DIR}/Framework/Widgets/CairoWidget.cpp ${ORTHANC_STONE_DIR}/Framework/Widgets/EmptyWidget.cpp ${ORTHANC_STONE_DIR}/Framework/Widgets/LayeredSceneWidget.cpp
--- 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) {