# HG changeset patch # User Sebastien Jodogne # Date 1558537514 -7200 # Node ID f7c236894c1a2a4767d18883867bec29dfcd757c # Parent f3a7092ed10e20a51ba997f47795ed496cbbf712 cont diff -r f3a7092ed10e -r f7c236894c1a Framework/Loaders/BasicFetchingItemsSorter.h --- a/Framework/Loaders/BasicFetchingItemsSorter.h Wed May 22 16:15:24 2019 +0200 +++ b/Framework/Loaders/BasicFetchingItemsSorter.h Wed May 22 17:05:14 2019 +0200 @@ -31,6 +31,15 @@ unsigned int itemsCount_; public: + class Factory : public IFactory + { + public: + virtual IFetchingItemsSorter* CreateSorter(unsigned int itemsCount) const + { + return new BasicFetchingItemsSorter(itemsCount); + } + }; + BasicFetchingItemsSorter(unsigned int itemsCount); virtual unsigned int GetItemsCount() const diff -r f3a7092ed10e -r f7c236894c1a Framework/Loaders/IFetchingItemsSorter.h --- a/Framework/Loaders/IFetchingItemsSorter.h Wed May 22 16:15:24 2019 +0200 +++ b/Framework/Loaders/IFetchingItemsSorter.h Wed May 22 17:05:14 2019 +0200 @@ -29,6 +29,16 @@ class IFetchingItemsSorter : public boost::noncopyable { public: + class IFactory : public boost::noncopyable + { + public: + virtual ~IFactory() + { + } + + virtual IFetchingItemsSorter* CreateSorter(unsigned int itemsCount) const = 0; + }; + virtual ~IFetchingItemsSorter() { } diff -r f3a7092ed10e -r f7c236894c1a Framework/Toolbox/CoordinateSystem3D.cpp --- a/Framework/Toolbox/CoordinateSystem3D.cpp Wed May 22 16:15:24 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Wed May 22 17:05:14 2019 +0200 @@ -189,9 +189,9 @@ } - bool CoordinateSystem3D::GetDistance(double& distance, - const CoordinateSystem3D& a, - const CoordinateSystem3D& b) + bool CoordinateSystem3D::ComputeDistance(double& distance, + const CoordinateSystem3D& a, + const CoordinateSystem3D& b) { bool opposite; // Ignored diff -r f3a7092ed10e -r f7c236894c1a Framework/Toolbox/CoordinateSystem3D.h --- a/Framework/Toolbox/CoordinateSystem3D.h Wed May 22 16:15:24 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Wed May 22 17:05:14 2019 +0200 @@ -104,8 +104,8 @@ const Vector& direction) const; // Returns "false" is the two planes are not parallel - static bool GetDistance(double& distance, - const CoordinateSystem3D& a, - const CoordinateSystem3D& b); + static bool ComputeDistance(double& distance, + const CoordinateSystem3D& a, + const CoordinateSystem3D& b); }; } diff -r f3a7092ed10e -r f7c236894c1a Framework/Toolbox/DicomInstanceParameters.cpp --- a/Framework/Toolbox/DicomInstanceParameters.cpp Wed May 22 16:15:24 2019 +0200 +++ b/Framework/Toolbox/DicomInstanceParameters.cpp Wed May 22 17:05:14 2019 +0200 @@ -230,7 +230,7 @@ double distance; - return (CoordinateSystem3D::GetDistance(distance, tmp, plane) && + return (CoordinateSystem3D::ComputeDistance(distance, tmp, plane) && distance <= thickness_ / 2.0); } diff -r f3a7092ed10e -r f7c236894c1a Framework/Toolbox/SlicesSorter.cpp --- a/Framework/Toolbox/SlicesSorter.cpp Wed May 22 16:15:24 2019 +0200 +++ b/Framework/Toolbox/SlicesSorter.cpp Wed May 22 17:05:14 2019 +0200 @@ -271,7 +271,7 @@ assert(slices_[i] != NULL); double tmp; - if (CoordinateSystem3D::GetDistance(tmp, slices_[i]->GetGeometry(), slice)) + if (CoordinateSystem3D::ComputeDistance(tmp, slices_[i]->GetGeometry(), slice)) { if (!found || tmp < distance) diff -r f3a7092ed10e -r f7c236894c1a Samples/Sdl/Loader.cpp --- a/Samples/Sdl/Loader.cpp Wed May 22 16:15:24 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Wed May 22 17:05:14 2019 +0200 @@ -26,9 +26,6 @@ #include "../../Framework/Oracle/OrthancRestApiCommand.h" #include "../../Framework/Oracle/SleepOracleCommand.h" #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" -#include "../../Framework/Messages/IMessageEmitter.h" -#include "../../Framework/Oracle/OracleCommandWithPayload.h" -#include "../../Framework/Oracle/IOracle.h" // From Stone #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" @@ -41,33 +38,220 @@ #include "../../Framework/Volumes/VolumeImageGeometry.h" // From Orthanc framework -#include -#include -#include -#include -#include -#include #include #include -#include #include #include -#include - -#include -#include -#include - -#include -#include - namespace OrthancStone { - class DicomVolumeImage : public boost::noncopyable + static bool IsSameCuttingPlane(const CoordinateSystem3D& a, + const CoordinateSystem3D& b) + { + double distance; + return (CoordinateSystem3D::ComputeDistance(distance, a, b) && + LinearAlgebra::IsCloseToZero(distance)); + } + + + class IVolumeSlicer : public boost::noncopyable + { + public: + class ExtractedSlice : public boost::noncopyable + { + public: + virtual ~ExtractedSlice() + { + } + + virtual bool IsValid() = 0; + + // Must be a cheap call + virtual uint64_t GetRevision() = 0; + + // This call can take some time + virtual ISceneLayer* CreateSceneLayer() = 0; + }; + + virtual ~IVolumeSlicer() + { + } + + virtual ExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) const = 0; + }; + + + + class DicomVolumeImageOrthogonalSlice : public IVolumeSlicer::ExtractedSlice { private: + CoordinateSystem3D cuttingPlane_; + bool isInitialized_; + bool valid_; + VolumeProjection projection_; + unsigned int sliceIndex_; + uint64_t revision_; + + void Initialize() + { + if (!isInitialized_) + { + valid_ = DetectSlice(projection_, sliceIndex_, revision_, cuttingPlane_); + isInitialized_ = true; + } + } + + protected: + virtual const ImageBuffer3D& GetImage() const = 0; + + virtual const VolumeImageGeometry& GetGeometry() const = 0; + + // WARNING - This cannot be invoked from the constructor, hence lazy "Initialize()" + virtual bool DetectSlice(VolumeProjection& projection, + unsigned int& sliceIndex, + uint64_t& revision, + const CoordinateSystem3D& cuttingPlane) const = 0; + + virtual const DicomInstanceParameters& GetDicomParameters(VolumeProjection projection, + unsigned int sliceIndex) const = 0; + + public: + DicomVolumeImageOrthogonalSlice(const CoordinateSystem3D& cuttingPlane) : + cuttingPlane_(cuttingPlane), + isInitialized_(false) + { + } + + virtual bool IsValid() + { + Initialize(); + + return valid_; + } + + virtual uint64_t GetRevision() + { + Initialize(); + + if (!valid_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return revision_; + } + } + + virtual ISceneLayer* CreateSceneLayer() + { + Initialize(); + + if (!valid_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + std::auto_ptr texture; + + { + ImageBuffer3D::SliceReader reader(GetImage(), projection_, sliceIndex_); + texture.reset(GetDicomParameters(projection_, sliceIndex_).CreateTexture(reader.GetAccessor())); + } + + const CoordinateSystem3D& system = GetGeometry().GetProjectionGeometry(projection_); + + double x0, y0, x1, y1; + system.ProjectPoint(x0, y0, system.GetOrigin()); + system.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); + texture->SetOrigin(x0, y0); + + double dx = x1 - x0; + double dy = y1 - y0; + if (!LinearAlgebra::IsCloseToZero(dx) || + !LinearAlgebra::IsCloseToZero(dy)) + { + texture->SetAngle(atan2(dy, dx)); + } + + Vector tmp; + GetGeometry().GetVoxelDimensions(projection_); + texture->SetPixelSpacing(tmp[0], tmp[1]); + + // texture->SetLinearInterpolation(linearInterpolation_); // TODO + + return texture.release(); + } + } + }; + + + // This class combines a 3D image buffer, a 3D volume geometry and + // information about the DICOM parameters of each slice. + class DicomSeriesVolumeImage : public IVolumeSlicer + { + private: + class Slice : public DicomVolumeImageOrthogonalSlice + { + private: + const DicomSeriesVolumeImage& that_; + + protected: + virtual const ImageBuffer3D& GetImage() const + { + return that_.GetImage(); + } + + virtual const VolumeImageGeometry& GetGeometry() const + { + return that_.GetGeometry(); + } + + virtual bool DetectSlice(VolumeProjection& projection, + unsigned int& sliceIndex, + uint64_t& revision, + const CoordinateSystem3D& cuttingPlane) const + { + if (!that_.HasGeometry() || + !that_.GetGeometry().DetectSlice(projection, sliceIndex, cuttingPlane)) + { + return false; + } + else + { + if (projection == VolumeProjection_Axial) + { + revision = that_.GetSliceRevision(sliceIndex); + } + else + { + // For coronal and sagittal projections, we take the global + // revision of the volume + revision = that_.GetRevision(); + } + + return true; + } + } + + virtual const DicomInstanceParameters& GetDicomParameters(VolumeProjection projection, + unsigned int sliceIndex) const + { + return that_.GetSliceParameters(projection == VolumeProjection_Axial ? sliceIndex : 0); + } + + public: + Slice(const DicomSeriesVolumeImage& that, + const CoordinateSystem3D& plane) : + DicomVolumeImageOrthogonalSlice(plane), + that_(that) + { + } + }; + + std::auto_ptr image_; std::auto_ptr geometry_; std::vector slices_; @@ -168,11 +352,11 @@ public: - DicomVolumeImage() + DicomSeriesVolumeImage() { } - ~DicomVolumeImage() + ~DicomSeriesVolumeImage() { Clear(); } @@ -310,10 +494,16 @@ slicesRevision_[index] += 1; } } + + virtual IVolumeSlicer::ExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) const + { + return new Slice(*this, cuttingPlane); + } }; + // This class combines a 3D DICOM volume together with its loader class IDicomVolumeImageSource : public boost::noncopyable { public: @@ -321,14 +511,14 @@ { } - virtual const DicomVolumeImage& GetVolume() const = 0; + virtual const DicomSeriesVolumeImage& GetVolume() const = 0; virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0; }; - class VolumeSeriesOrthancLoader : + class OrthancSeriesVolumeProgressiveLoader : public IObserver, public IDicomVolumeImageSource { @@ -422,9 +612,10 @@ if (volume_.GetSlicesCount() != 0) { strategy_.reset(new BasicFetchingStrategy( - new BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY)); + sorter_->CreateSorter(volume_.GetSlicesCount()), BEST_QUALITY)); - for (unsigned int i = 0; i < 4; i++) // Schedule up to 4 simultaneous downloads (TODO - parameter) + assert(simultaneousDownloads_ != 0); + for (unsigned int i = 0; i < simultaneousDownloads_; i++) { ScheduleNextSliceDownload(); } @@ -467,28 +658,48 @@ IOracle& oracle_; bool active_; - DicomVolumeImage volume_; - - std::auto_ptr strategy_; + DicomSeriesVolumeImage volume_; + unsigned int simultaneousDownloads_; + + std::auto_ptr sorter_; + std::auto_ptr strategy_; public: - VolumeSeriesOrthancLoader(IOracle& oracle, - IObservable& oracleObservable) : + OrthancSeriesVolumeProgressiveLoader(IOracle& oracle, + IObservable& oracleObservable) : IObserver(oracleObservable.GetBroker()), oracle_(oracle), - active_(false) + active_(false), + simultaneousDownloads_(4), + sorter_(new BasicFetchingItemsSorter::Factory) { oracleObservable.RegisterObserverCallback( - new Callable - (*this, &VolumeSeriesOrthancLoader::LoadGeometry)); + new Callable + (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry)); + + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent)); oracleObservable.RegisterObserverCallback( - new Callable - (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent)); + new Callable + (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent)); + } - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent)); + void SetSimultaneousDownloads(unsigned int count) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (count == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + simultaneousDownloads_ = count; + } } void LoadSeries(const std::string& seriesId) @@ -497,17 +708,19 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - - active_ = true; + else + { + active_ = true; - std::auto_ptr command(new OrthancRestApiCommand); - command->SetUri("/series/" + seriesId + "/instances-tags"); + std::auto_ptr command(new OrthancRestApiCommand); + command->SetUri("/series/" + seriesId + "/instances-tags"); - oracle_.Schedule(*this, command.release()); + oracle_.Schedule(*this, command.release()); + } } - virtual const DicomVolumeImage& GetVolume() const + virtual const DicomSeriesVolumeImage& GetVolume() const { return volume_; } @@ -553,7 +766,7 @@ #endif - /* class VolumeSlicerBase : public IVolumeSlicer + /* class VolumeSlicerBase : public OLD_IVolumeSlicer { private: Scene2D& scene_; @@ -562,7 +775,7 @@ CoordinateSystem3D lastPlane_; protected: - bool HasViewportPlaneChanged(const CoordinateSystem3D& plane) const + bool HasCuttingPlaneChanged(const CoordinateSystem3D& plane) const { if (first_ || !LinearAlgebra::IsCloseToZero( @@ -579,7 +792,7 @@ } } - void SetLastViewportPlane(const CoordinateSystem3D& plane) + void SetLastCuttingPlane(const CoordinateSystem3D& plane) { first_ = false; lastPlane_ = plane; @@ -607,22 +820,22 @@ - class IVolumeSlicer : public boost::noncopyable + class OLD_IVolumeSlicer : public boost::noncopyable { public: - virtual ~IVolumeSlicer() + virtual ~OLD_IVolumeSlicer() { } - virtual void SetViewportPlane(const CoordinateSystem3D& plane) = 0; + virtual void SetCuttingPlane(Scene2D& scene, + const CoordinateSystem3D& plane) = 0; }; - class DicomVolumeMPRSlicer : public IVolumeSlicer + class DicomVolumeMPRSlicer : public OLD_IVolumeSlicer { private: bool linearInterpolation_; - Scene2D& scene_; int layerDepth_; IDicomVolumeImageSource& source_; bool first_; @@ -635,7 +848,6 @@ int layerDepth, IDicomVolumeImageSource& source) : linearInterpolation_(false), - scene_(scene), layerDepth_(layerDepth), source_(source), first_(true) @@ -652,12 +864,13 @@ return linearInterpolation_; } - virtual void SetViewportPlane(const CoordinateSystem3D& plane) + virtual void SetCuttingPlane(Scene2D& scene, + const CoordinateSystem3D& plane) { if (!source_.GetVolume().HasGeometry() || source_.GetVolume().GetSlicesCount() == 0) { - scene_.DeleteLayer(layerDepth_); + scene.DeleteLayer(layerDepth_); return; } @@ -669,7 +882,7 @@ { // The cutting plane is neither axial, nor coronal, nor // sagittal. Could use "VolumeReslicer" here. - scene_.DeleteLayer(layerDepth_); + scene.DeleteLayer(layerDepth_); return; } @@ -697,7 +910,7 @@ lastSliceIndex_ != sliceIndex || lastSliceRevision_ != sliceRevision) { - // Either the viewport plane, or the content of the slice have not + // Either the cutting plane, or the content of the slice have not // changed since the last time the layer was set: Update is needed first_ = false; @@ -736,7 +949,7 @@ texture->SetLinearInterpolation(linearInterpolation_); - scene_.SetLayer(layerDepth_, texture.release()); + scene.SetLayer(layerDepth_, texture.release()); } } }; @@ -887,13 +1100,13 @@ OrthancStone::IOracle& oracle) { std::auto_ptr toto; - std::auto_ptr loader1, loader2; + std::auto_ptr loader1, loader2; { OrthancStone::NativeApplicationContext::WriterLock lock(context); toto.reset(new Toto(lock.GetOracleObservable())); - loader1.reset(new OrthancStone::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); - loader2.reset(new OrthancStone::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); + loader1.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(oracle, lock.GetOracleObservable())); + loader2.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(oracle, lock.GetOracleObservable())); } if (0)