# HG changeset patch # User Sebastien Jodogne # Date 1558542846 -7200 # Node ID 1181e1ad98ec70c22c9ca3c399aa6d13cb5874d5 # Parent 774681b2c77c841d13d898bb98eb249dec1c5765 progressive loading working diff -r 774681b2c77c -r 1181e1ad98ec Framework/Oracle/ThreadedOracle.cpp --- a/Framework/Oracle/ThreadedOracle.cpp Wed May 22 17:25:44 2019 +0200 +++ b/Framework/Oracle/ThreadedOracle.cpp Wed May 22 18:34:06 2019 +0200 @@ -87,7 +87,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - expiration_ = (boost::posix_time::second_clock::local_time() + + expiration_ = (boost::posix_time::microsec_clock::local_time() + boost::posix_time::milliseconds(command_->GetDelay())); } @@ -134,7 +134,7 @@ { boost::mutex::scoped_lock lock(mutex_); - const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); Content stillSleeping; @@ -439,7 +439,7 @@ } - void ThreadedOracle::SetWorkersCount(unsigned int count) + void ThreadedOracle::SetThreadsCount(unsigned int count) { boost::mutex::scoped_lock lock(mutex_); diff -r 774681b2c77c -r 1181e1ad98ec Framework/Oracle/ThreadedOracle.h --- a/Framework/Oracle/ThreadedOracle.h Wed May 22 17:25:44 2019 +0200 +++ b/Framework/Oracle/ThreadedOracle.h Wed May 22 18:34:06 2019 +0200 @@ -79,7 +79,7 @@ void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc); - void SetWorkersCount(unsigned int count); + void SetThreadsCount(unsigned int count); void SetSleepingTimeResolution(unsigned int milliseconds); diff -r 774681b2c77c -r 1181e1ad98ec Framework/Toolbox/CoordinateSystem3D.cpp --- a/Framework/Toolbox/CoordinateSystem3D.cpp Wed May 22 17:25:44 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Wed May 22 18:34:06 2019 +0200 @@ -143,6 +143,19 @@ } + void CoordinateSystem3D::SetOrigin(const Vector& origin) + { + if (origin.size() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + origin_ = origin; + } + } + + Vector CoordinateSystem3D::MapSliceToWorldCoordinates(double x, double y) const { diff -r 774681b2c77c -r 1181e1ad98ec Framework/Toolbox/CoordinateSystem3D.h --- a/Framework/Toolbox/CoordinateSystem3D.h Wed May 22 17:25:44 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Wed May 22 18:34:06 2019 +0200 @@ -86,6 +86,8 @@ return axisY_; } + void SetOrigin(const Vector& origin); + Vector MapSliceToWorldCoordinates(double x, double y) const; diff -r 774681b2c77c -r 1181e1ad98ec Samples/Sdl/Loader.cpp --- a/Samples/Sdl/Loader.cpp Wed May 22 17:25:44 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Wed May 22 18:34:06 2019 +0200 @@ -30,6 +30,7 @@ // From Stone #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" #include "../../Framework/Loaders/BasicFetchingStrategy.h" +#include "../../Framework/Scene2D/CairoCompositor.h" #include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/StoneInitialization.h" #include "../../Framework/Toolbox/GeometryToolbox.h" @@ -38,7 +39,9 @@ #include "../../Framework/Volumes/VolumeImageGeometry.h" // From Orthanc framework +#include #include +#include #include #include #include @@ -46,15 +49,6 @@ namespace OrthancStone { - 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: @@ -111,6 +105,14 @@ VolumeProjection projection_; unsigned int sliceIndex_; + void CheckValid() const + { + if (!valid_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + protected: virtual uint64_t GetRevisionInternal(VolumeProjection projection, unsigned int sliceIndex) const = 0; @@ -128,6 +130,18 @@ valid_ = geometry_.DetectSlice(projection_, sliceIndex_, cuttingPlane); } + VolumeProjection GetProjection() const + { + CheckValid(); + return projection_; + } + + unsigned int GetSliceIndex() const + { + CheckValid(); + return sliceIndex_; + } + virtual bool IsValid() { return valid_; @@ -135,65 +149,51 @@ virtual uint64_t GetRevision() { - if (!valid_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return GetRevisionInternal(projection_, sliceIndex_); - } + CheckValid(); + return GetRevisionInternal(projection_, sliceIndex_); } virtual ISceneLayer* CreateSceneLayer() { - if (!valid_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - std::auto_ptr texture; + CheckValid(); + + std::auto_ptr texture; - { - const DicomInstanceParameters& parameters = GetDicomParameters(projection_, sliceIndex_); - ImageBuffer3D::SliceReader reader(image_, projection_, sliceIndex_); - texture.reset(parameters.CreateTexture(reader.GetAccessor())); - } + { + const DicomInstanceParameters& parameters = GetDicomParameters(projection_, sliceIndex_); + ImageBuffer3D::SliceReader reader(image_, projection_, sliceIndex_); + texture.reset(parameters.CreateTexture(reader.GetAccessor())); + } - const CoordinateSystem3D& system = geometry_.GetProjectionGeometry(projection_); + const CoordinateSystem3D& system = geometry_.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 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)); - } + double dx = x1 - x0; + double dy = y1 - y0; + if (!LinearAlgebra::IsCloseToZero(dx) || + !LinearAlgebra::IsCloseToZero(dy)) + { + texture->SetAngle(atan2(dy, dx)); + } - Vector tmp; - geometry_.GetVoxelDimensions(projection_); - texture->SetPixelSpacing(tmp[0], tmp[1]); + Vector tmp = geometry_.GetVoxelDimensions(projection_); + texture->SetPixelSpacing(tmp[0], tmp[1]); - // texture->SetLinearInterpolation(linearInterpolation_); // TODO - - return texture.release(); - } + 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 + class DicomSeriesVolumeImage : public boost::noncopyable { - private: - class Slice : public DicomVolumeImageOrthogonalSlice + public: + class ExtractedSlice : public DicomVolumeImageOrthogonalSlice { private: const DicomSeriesVolumeImage& that_; @@ -221,8 +221,8 @@ } public: - Slice(const DicomSeriesVolumeImage& that, - const CoordinateSystem3D& plane) : + ExtractedSlice(const DicomSeriesVolumeImage& that, + const CoordinateSystem3D& plane) : DicomVolumeImageOrthogonalSlice(that.GetImage(), that.GetGeometry(), plane), that_(that) { @@ -230,6 +230,7 @@ }; + private: std::auto_ptr image_; std::auto_ptr geometry_; std::vector slices_; @@ -472,40 +473,13 @@ slicesRevision_[index] += 1; } } - - virtual IVolumeSlicer::ExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) const - { - if (HasGeometry()) - { - return new Slice(*this, cuttingPlane); - } - else - { - return new InvalidExtractedSlice; - } - } }; - // This class combines a 3D DICOM volume together with its loader - class IDicomVolumeImageSource : public boost::noncopyable - { - public: - virtual ~IDicomVolumeImageSource() - { - } - - virtual const DicomSeriesVolumeImage& GetVolume() const = 0; - - virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0; - }; - - - - class OrthancSeriesVolumeProgressiveLoader : - public IObserver, - public IDicomVolumeImageSource + class OrthancSeriesVolumeProgressiveLoader : + public IObserver, + public IVolumeSlicer { private: static const unsigned int LOW_QUALITY = 0; @@ -641,10 +615,10 @@ } - IOracle& oracle_; - bool active_; + IOracle& oracle_; + bool active_; DicomSeriesVolumeImage volume_; - unsigned int simultaneousDownloads_; + unsigned int simultaneousDownloads_; std::auto_ptr sorter_; std::auto_ptr strategy_; @@ -705,22 +679,33 @@ } - virtual const DicomSeriesVolumeImage& GetVolume() const + const DicomSeriesVolumeImage& GetVolume() const { return volume_; } - - virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) + + virtual IVolumeSlicer::ExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) const { - if (strategy_.get() == NULL) + if (volume_.HasGeometry() && + volume_.GetSlicesCount() != 0) { - // Should have called GetVolume().HasGeometry() before - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + std::auto_ptr slice + (new DicomSeriesVolumeImage::ExtractedSlice(volume_, cuttingPlane)); + + assert(slice.get() != NULL && + strategy_.get() != NULL); + + if (slice->GetProjection() == VolumeProjection_Axial) + { + strategy_->SetCurrent(slice->GetSliceIndex()); + } + + return slice.release(); } else { - strategy_->SetCurrent(sliceIndex); + return new InvalidExtractedSlice; } } }; @@ -751,92 +736,39 @@ #endif - /* class VolumeSlicerBase : public OLD_IVolumeSlicer - { - private: - Scene2D& scene_; - int layerDepth_; - bool first_; - CoordinateSystem3D lastPlane_; - - protected: - bool HasCuttingPlaneChanged(const CoordinateSystem3D& plane) const - { - if (first_ || - !LinearAlgebra::IsCloseToZero( - boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal()))) - { - // This is the first rendering, or the plane has not the same orientation - return false; - } - else - { - double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin()); - double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin()); - return LinearAlgebra::IsCloseToZero(offset2 - offset1); - } - } + class SceneVolumeSlicer : public boost::noncopyable + { + private: + int layerDepth_; + std::auto_ptr volume_; + bool linearInterpolation_; + std::auto_ptr lastPlane_; + uint64_t lastRevision_; - void SetLastCuttingPlane(const CoordinateSystem3D& plane) - { - first_ = false; - lastPlane_ = plane; - } - - void SetLayer(ISceneLayer* layer) - { - scene_.SetLayer(layerDepth_, layer); - } - - void DeleteLayer() - { - scene_.DeleteLayer(layerDepth_); - } - - public: - VolumeSlicerBase(Scene2D& scene, - int layerDepth) : - scene_(scene), - layerDepth_(layerDepth), - first_(true) - { - } - };*/ - - - - class OLD_IVolumeSlicer : public boost::noncopyable - { - public: - virtual ~OLD_IVolumeSlicer() + static bool IsSameCuttingPlane(const CoordinateSystem3D& a, + const CoordinateSystem3D& b) { + double distance; + return (CoordinateSystem3D::ComputeDistance(distance, a, b) && + LinearAlgebra::IsCloseToZero(distance)); } - virtual void SetCuttingPlane(Scene2D& scene, - const CoordinateSystem3D& plane) = 0; - }; - + public: + SceneVolumeSlicer(int layerDepth, + IVolumeSlicer* volume) : // Takes ownership + layerDepth_(layerDepth), + volume_(volume), + linearInterpolation_(false) + { + if (volume == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } - class DicomVolumeMPRSlicer : public OLD_IVolumeSlicer - { - private: - bool linearInterpolation_; - int layerDepth_; - IDicomVolumeImageSource& source_; - bool first_; - VolumeProjection lastProjection_; - unsigned int lastSliceIndex_; - uint64_t lastSliceRevision_; - - public: - DicomVolumeMPRSlicer(Scene2D& scene, - int layerDepth, - IDicomVolumeImageSource& source) : - linearInterpolation_(false), - layerDepth_(layerDepth), - source_(source), - first_(true) + const IVolumeSlicer& GetSlicer() const { + return *volume_; } void SetLinearInterpolation(bool enabled) @@ -848,98 +780,57 @@ { return linearInterpolation_; } - - virtual void SetCuttingPlane(Scene2D& scene, - const CoordinateSystem3D& plane) + + void Update(Scene2D& scene, + const CoordinateSystem3D& plane) { - if (!source_.GetVolume().HasGeometry() || - source_.GetVolume().GetSlicesCount() == 0) + assert(volume_.get() != NULL); + std::auto_ptr slice(volume_->ExtractSlice(plane)); + + if (slice.get() == NULL) { - scene.DeleteLayer(layerDepth_); - return; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - const VolumeImageGeometry& geometry = source_.GetVolume().GetGeometry(); - - VolumeProjection projection; - unsigned int sliceIndex; - if (!geometry.DetectSlice(projection, sliceIndex, plane)) + if (!slice->IsValid()) { - // The cutting plane is neither axial, nor coronal, nor - // sagittal. Could use "VolumeReslicer" here. + // The slicer cannot handle this cutting plane: Clear the layer scene.DeleteLayer(layerDepth_); - return; + lastPlane_.reset(NULL); } - - uint64_t sliceRevision; - if (projection == VolumeProjection_Axial) + else if (lastPlane_.get() != NULL && + IsSameCuttingPlane(*lastPlane_, plane) && + lastRevision_ == slice->GetRevision()) { - sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex); - - if (first_ || - lastSliceIndex_ != sliceIndex) - { - // Reorder the prefetching queue - source_.NotifyAxialSliceAccessed(sliceIndex); - } + // The content of the slice has not changed: Do nothing } else { - // For coronal and sagittal projections, we take the global - // revision of the volume - sliceRevision = source_.GetVolume().GetRevision(); - } - - if (first_ || - lastProjection_ != projection || - lastSliceIndex_ != sliceIndex || - lastSliceRevision_ != sliceRevision) - { - // Either the cutting plane, or the content of the slice have not - // changed since the last time the layer was set: Update is needed + // Content has changed: An update is needed + lastPlane_.reset(new CoordinateSystem3D(plane)); + lastRevision_ = slice->GetRevision(); - first_ = false; - lastProjection_ = projection; - lastSliceIndex_ = sliceIndex; - lastSliceRevision_ = sliceRevision; - - std::auto_ptr texture; - + std::auto_ptr layer(slice->CreateSceneLayer()); + if (layer.get() == NULL) { - const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters - (projection == VolumeProjection_Axial ? sliceIndex : 0); - - ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex); - texture.reset(parameters.CreateTexture(reader.GetAccessor())); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - const CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection); - - double x0, y0, x1, y1; - system.ProjectPoint(x0, y0, system.GetOrigin()); - system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX()); - texture->SetOrigin(x0, y0); - - double dx = x1 - x0; - double dy = y1 - y0; - if (!LinearAlgebra::IsCloseToZero(dx) || - !LinearAlgebra::IsCloseToZero(dy)) + if (layer->GetType() == ISceneLayer::Type_ColorTexture || + layer->GetType() == ISceneLayer::Type_FloatTexture) { - texture->SetAngle(atan2(dy, dx)); + dynamic_cast(*layer).SetLinearInterpolation(linearInterpolation_); } - Vector tmp; - geometry.GetVoxelDimensions(projection); - texture->SetPixelSpacing(tmp[0], tmp[1]); - - texture->SetLinearInterpolation(linearInterpolation_); - - scene.SetLayer(layerDepth_, texture.release()); + scene.SetLayer(layerDepth_, layer.release()); } } }; + + + class NativeApplicationContext : public IMessageEmitter { private: @@ -1015,9 +906,57 @@ class Toto : public OrthancStone::IObserver { private: + OrthancStone::IOracle& oracle_; + OrthancStone::Scene2D scene_; + std::auto_ptr slicer_; + void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message) { - printf("TIMEOUT! %d\n", dynamic_cast& >(message.GetOrigin().GetPayload()).GetValue()); + if (message.GetOrigin().HasPayload()) + { + printf("TIMEOUT! %d\n", dynamic_cast& >(message.GetOrigin().GetPayload()).GetValue()); + } + else + { + printf("TIMEOUT\n"); + + if (slicer_.get() != NULL) + { + OrthancStone::CoordinateSystem3D plane; + + const OrthancStone::OrthancSeriesVolumeProgressiveLoader& loader = + dynamic_cast(slicer_->GetSlicer()); + + if (loader.GetVolume().HasGeometry()) + { + plane = loader.GetVolume().GetGeometry().GetSagittalGeometry(); + plane.SetOrigin(loader.GetVolume().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); + } + + slicer_->Update(scene_, plane); + scene_.FitContent(1024, 768); + + { + OrthancStone::CairoCompositor compositor(scene_, 1024, 768); + compositor.Refresh(); + + Orthanc::ImageAccessor accessor; + compositor.GetCanvas().GetReadOnlyAccessor(accessor); + + Orthanc::Image tmp(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false); + Orthanc::ImageProcessing::Convert(tmp, accessor); + + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "scene-%06d.png", count++); + + Orthanc::PngWriter writer; + writer.WriteToFile(buf, tmp); + } + } + + oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay())); + } } void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) @@ -1055,29 +994,37 @@ } public: - Toto(OrthancStone::IObservable& oracle) : - IObserver(oracle.GetBroker()) + Toto(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + IObserver(oracleObservable.GetBroker()), + oracle_(oracle) { - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback (new OrthancStone::Callable (*this, &Toto::Handle)); - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback (new OrthancStone::Callable (*this, &Toto::Handle)); - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback (new OrthancStone::Callable (*this, &Toto::Handle)); - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback (new OrthancStone::Callable (*this, &Toto::Handle)); - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback (new OrthancStone::Callable (*this, &Toto::Handle)); } + + void SetVolume(int depth, + OrthancStone::IVolumeSlicer* volume) + { + slicer_.reset(new OrthancStone::SceneVolumeSlicer(0, volume)); + } }; @@ -1089,11 +1036,13 @@ { OrthancStone::NativeApplicationContext::WriterLock lock(context); - toto.reset(new Toto(lock.GetOracleObservable())); + toto.reset(new Toto(oracle, lock.GetOracleObservable())); loader1.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(oracle, lock.GetOracleObservable())); loader2.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(oracle, lock.GetOracleObservable())); } + oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); + if (0) { Json::Value v = Json::objectValue; @@ -1177,6 +1126,9 @@ //loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT loader1->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm + + toto->SetVolume(0, loader1.release()); + LOG(WARNING) << "...Waiting for Ctrl-C..."; Orthanc::SystemToolbox::ServerBarrier(); //boost::this_thread::sleep(boost::posix_time::seconds(1)); @@ -1199,6 +1151,7 @@ OrthancStone::NativeApplicationContext context; OrthancStone::ThreadedOracle oracle(context); + oracle.SetThreadsCount(1); { Orthanc::WebServiceParameters p;