Mercurial > hg > orthanc-stone
diff Samples/Sdl/Loader.cpp @ 814:aead999345e0
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 28 May 2019 21:16:39 +0200 |
parents | abc08ac721d3 |
children | df442f1ba0c6 |
line wrap: on
line diff
--- a/Samples/Sdl/Loader.cpp Tue May 28 18:31:44 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Tue May 28 21:16:39 2019 +0200 @@ -19,7 +19,10 @@ **/ -#include "../../Framework/Toolbox/DicomInstanceParameters.h" +#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" +#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" #include "../../Framework/Oracle/ThreadedOracle.h" #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" #include "../../Framework/Oracle/GetOrthancImageCommand.h" @@ -60,995 +63,6 @@ namespace OrthancStone { /** - This interface is implemented by objects able to create an ISceneLayer - suitable to display the Orthanc image supplied to the CreateTextureXX - factory methods (taking Dicom parameters into account if relevant). - - It can also refresh the style of an existing layer afterwards, to match - the configurator settings. - */ - class ILayerStyleConfigurator - { - public: - virtual ~ILayerStyleConfigurator() - { - } - - virtual uint64_t GetRevision() const = 0; - - virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const = 0; - - virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, - const DicomInstanceParameters& parameters) const = 0; - - virtual void ApplyStyle(ISceneLayer& layer) const = 0; - }; - - /** - This configurator supplies an API to set a display range and a LUT. - */ - class LookupTableStyleConfigurator : public ILayerStyleConfigurator - { - private: - uint64_t revision_; - bool hasLut_; - std::string lut_; - bool hasRange_; - float minValue_; - float maxValue_; - - public: - LookupTableStyleConfigurator() : - revision_(0), - hasLut_(false), - hasRange_(false) - { - } - - void SetLookupTable(Orthanc::EmbeddedResources::FileResourceId resource) - { - hasLut_ = true; - Orthanc::EmbeddedResources::GetFileResource(lut_, resource); - } - - void SetLookupTable(const std::string& lut) - { - hasLut_ = true; - lut_ = lut; - } - - void SetRange(float minValue, - float maxValue) - { - if (minValue > maxValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - hasRange_ = true; - minValue_ = minValue; - maxValue_ = maxValue; - } - } - - virtual uint64_t GetRevision() const - { - return revision_; - } - - virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, - const DicomInstanceParameters& parameters) const - { - return parameters.CreateLookupTableTexture(frame); - } - - virtual void ApplyStyle(ISceneLayer& layer) const - { - LookupTableTextureSceneLayer& l = dynamic_cast<LookupTableTextureSceneLayer&>(layer); - - if (hasLut_) - { - l.SetLookupTable(lut_); - } - - if (hasRange_) - { - l.SetRange(minValue_, maxValue_); - } - else - { - l.FitRange(); - } - } - }; - - /** - Creates layers to display the supplied image in grayscale. No dynamic - style is available. - */ - class GrayscaleStyleConfigurator : public ILayerStyleConfigurator - { - private: - uint64_t revision_; - - public: - GrayscaleStyleConfigurator() : - revision_(0) - { - } - - virtual uint64_t GetRevision() const - { - return revision_; - } - - virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, - const DicomInstanceParameters& parameters) const - { - return parameters.CreateTexture(frame); - } - - virtual void ApplyStyle(ISceneLayer& layer) const - { - } - }; - - /** - This interface is implemented by objects representing 3D volume data and - that are able to return an object that: - - represent a slice of their data - - are able to create the corresponding slice visual representation. - */ - class IVolumeSlicer : public boost::noncopyable - { - public: - /** - This interface is implemented by objects representing a slice of - volume data and that are able to create a 2D layer to display a this - slice. - - The CreateSceneLayer factory method is called with an optional - configurator that possibly impacts the ISceneLayer subclass that is - created (for instance, if a LUT must be applied on the texture when - displaying it) - */ - class IExtractedSlice : public boost::noncopyable - { - public: - virtual ~IExtractedSlice() - { - } - - /** - Invalid slices are created when the data is not ready yet or if the - cut is outside of the available geometry. - */ - virtual bool IsValid() = 0; - - /** - This retrieves the *revision* that gets incremented every time the - underlying object undergoes a mutable operation (that it, changes its - state). - This **must** be a cheap call. - */ - virtual uint64_t GetRevision() = 0; - - /** Creates the slice visual representation */ - virtual ISceneLayer* CreateSceneLayer( - const ILayerStyleConfigurator* configurator, // possibly absent - const CoordinateSystem3D& cuttingPlane) = 0; - }; - - /** - See IExtractedSlice.IsValid() - */ - class InvalidSlice : public IExtractedSlice - { - public: - virtual bool IsValid() - { - return false; - } - - virtual uint64_t GetRevision() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - }; - - - virtual ~IVolumeSlicer() - { - } - - /** - This method is implemented by the objects representing volumetric data - and must returns an IExtractedSlice subclass that contains all the data - needed to, later on, create its visual representation through - CreateSceneLayer. - Subclasses a.o.: - - InvalidSlice, - - DicomVolumeImageMPRSlicer::Slice, - - DicomVolumeImageReslicer::Slice - - DicomStructureSetLoader::Slice - */ - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; - }; - - /** - This class combines a 3D image buffer, a 3D volume geometry and - information about the DICOM parameters of the series. - (MPR means MultiPlanar Reconstruction) - */ - class DicomVolumeImage : public boost::noncopyable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage); - - private: - uint64_t revision_; - std::auto_ptr<VolumeImageGeometry> geometry_; - std::auto_ptr<ImageBuffer3D> image_; - std::auto_ptr<DicomInstanceParameters> parameters_; - - void CheckHasGeometry() const - { - if (!HasGeometry()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - public: - DicomVolumeImage() : - revision_(0) - { - } - - void IncrementRevision() - { - revision_ ++; - } - - void Initialize(const VolumeImageGeometry& geometry, - Orthanc::PixelFormat format) - { - geometry_.reset(new VolumeImageGeometry(geometry)); - image_.reset(new ImageBuffer3D(format, geometry_->GetWidth(), geometry_->GetHeight(), - geometry_->GetDepth(), false /* don't compute range */)); - - revision_ ++; - } - - void SetDicomParameters(const DicomInstanceParameters& parameters) - { - parameters_.reset(parameters.Clone()); - revision_ ++; - } - - uint64_t GetRevision() const - { - return revision_; - } - - bool HasGeometry() const - { - return (geometry_.get() != NULL && - image_.get() != NULL); - } - - ImageBuffer3D& GetPixelData() - { - CheckHasGeometry(); - return *image_; - } - - const ImageBuffer3D& GetPixelData() const - { - CheckHasGeometry(); - return *image_; - } - - const VolumeImageGeometry& GetGeometry() const - { - CheckHasGeometry(); - return *geometry_; - } - - bool HasDicomParameters() const - { - return parameters_.get() != NULL; - } - - const DicomInstanceParameters& GetDicomParameters() const - { - if (HasDicomParameters()) - { - return *parameters_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - }; - - /** - Implements the IVolumeSlicer on Dicom volume data when the cutting plane - that is supplied to the slicer is either axial, sagittal or coronal. - Arbitrary planes are *not* supported - */ - class DicomVolumeImageMPRSlicer : public IVolumeSlicer - { - public: - class Slice : public IExtractedSlice - { - private: - const DicomVolumeImage& volume_; - bool valid_; - VolumeProjection projection_; - unsigned int sliceIndex_; - - void CheckValid() const - { - if (!valid_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - protected: - // Can be overloaded in subclasses - virtual uint64_t GetRevisionInternal(VolumeProjection projection, - unsigned int sliceIndex) const - { - return volume_.GetRevision(); - } - - public: - /** - Represents a slice of a volume image that is parallel to the - coordinate system axis. - The constructor initializes the type of projection (axial, sagittal or - coronal) and the corresponding slice index, from the cutting plane. - */ - Slice(const DicomVolumeImage& volume, - const CoordinateSystem3D& cuttingPlane) : - volume_(volume) - { - valid_ = (volume_.HasDicomParameters() && - volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); - } - - VolumeProjection GetProjection() const - { - CheckValid(); - return projection_; - } - - unsigned int GetSliceIndex() const - { - CheckValid(); - return sliceIndex_; - } - - virtual bool IsValid() - { - return valid_; - } - - virtual uint64_t GetRevision() - { - CheckValid(); - return GetRevisionInternal(projection_, sliceIndex_); - } - - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) - { - CheckValid(); - - if (configurator == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer, - "A style configurator is mandatory for textures"); - } - - std::auto_ptr<TextureBaseSceneLayer> texture; - - { - const DicomInstanceParameters& parameters = volume_.GetDicomParameters(); - ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_); - texture.reset(dynamic_cast<TextureBaseSceneLayer*> - (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); - } - - const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); - - double x0, y0, x1, y1; - cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); - cuttingPlane.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 = volume_.GetGeometry().GetVoxelDimensions(projection_); - texture->SetPixelSpacing(tmp[0], tmp[1]); - - return texture.release(); - -#if 0 - double w = texture->GetTexture().GetWidth() * tmp[0]; - double h = texture->GetTexture().GetHeight() * tmp[1]; - printf("%.1f %.1f %.1f => %.1f %.1f => %.1f %.1f\n", - system.GetOrigin() [0], - system.GetOrigin() [1], - system.GetOrigin() [2], - x0, y0, x0 + w, y0 + h); - - std::auto_ptr<PolylineSceneLayer> toto(new PolylineSceneLayer); - - PolylineSceneLayer::Chain c; - c.push_back(ScenePoint2D(x0, y0)); - c.push_back(ScenePoint2D(x0 + w, y0)); - c.push_back(ScenePoint2D(x0 + w, y0 + h)); - c.push_back(ScenePoint2D(x0, y0 + h)); - - toto->AddChain(c, true); - - return toto.release(); -#endif - } - }; - - private: - boost::shared_ptr<DicomVolumeImage> volume_; - - public: - DicomVolumeImageMPRSlicer(const boost::shared_ptr<DicomVolumeImage>& volume) : - volume_(volume) - { - } - - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE - { - if (volume_->HasGeometry()) - { - return new Slice(*volume_, cuttingPlane); - } - else - { - return new IVolumeSlicer::InvalidSlice; - } - } - }; - - - - - /** - This class is used to manage the progressive loading of a volume that - is stored in a Dicom series. - - // TODO - Refactor using LoaderStateMachine? - // TODO: - */ - class OrthancSeriesVolumeProgressiveLoader : - public IObserver, - public IObservable, - public IVolumeSlicer - { - private: - static const unsigned int LOW_QUALITY = 0; - static const unsigned int MIDDLE_QUALITY = 1; - static const unsigned int BEST_QUALITY = 2; - - /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */ - class SeriesGeometry : public boost::noncopyable - { - private: - void CheckSlice(size_t index, - const DicomInstanceParameters& reference) const - { - const DicomInstanceParameters& slice = *slices_[index]; - - if (!GeometryToolbox::IsParallel( - reference.GetGeometry().GetNormal(), - slice.GetGeometry().GetNormal())) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "A slice in the volume image is not parallel to the others"); - } - - if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, - "The pixel format changes across the slices of the volume image"); - } - - if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() || - reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize, - "The width/height of slices are not constant in the volume image"); - } - - if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) || - !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY())) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "The pixel spacing of the slices change across the volume image"); - } - } - - - void CheckVolume() const - { - for (size_t i = 0; i < slices_.size(); i++) - { - assert(slices_[i] != NULL); - if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "This class does not support multi-frame images"); - } - } - - if (slices_.size() != 0) - { - const DicomInstanceParameters& reference = *slices_[0]; - - for (size_t i = 1; i < slices_.size(); i++) - { - CheckSlice(i, reference); - } - } - } - - - void Clear() - { - for (size_t i = 0; i < slices_.size(); i++) - { - assert(slices_[i] != NULL); - delete slices_[i]; - } - - slices_.clear(); - slicesRevision_.clear(); - } - - - void CheckSliceIndex(size_t index) const - { - if (!HasGeometry()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (index >= slices_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - assert(slices_.size() == GetImageGeometry().GetDepth() && - slices_.size() == slicesRevision_.size()); - } - } - - - std::auto_ptr<VolumeImageGeometry> geometry_; - std::vector<DicomInstanceParameters*> slices_; - std::vector<uint64_t> slicesRevision_; - - public: - ~SeriesGeometry() - { - Clear(); - } - - // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" - // (called with the slices created in LoadGeometry) - void ComputeGeometry(SlicesSorter& slices) - { - Clear(); - - if (!slices.Sort()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "Cannot sort the 3D slices of a DICOM series"); - } - - if (slices.GetSlicesCount() == 0) - { - geometry_.reset(new VolumeImageGeometry); - } - else - { - slices_.reserve(slices.GetSlicesCount()); - slicesRevision_.resize(slices.GetSlicesCount(), 0); - - for (size_t i = 0; i < slices.GetSlicesCount(); i++) - { - const DicomInstanceParameters& slice = - dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i)); - slices_.push_back(new DicomInstanceParameters(slice)); - } - - CheckVolume(); - - const double spacingZ = slices.ComputeSpacingBetweenSlices(); - LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; - - const DicomInstanceParameters& parameters = *slices_[0]; - - geometry_.reset(new VolumeImageGeometry); - geometry_->SetSize(parameters.GetImageInformation().GetWidth(), - parameters.GetImageInformation().GetHeight(), - static_cast<unsigned int>(slices.GetSlicesCount())); - geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); - geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), - parameters.GetPixelSpacingY(), spacingZ); - } - } - - bool HasGeometry() const - { - return geometry_.get() != NULL; - } - - const VolumeImageGeometry& GetImageGeometry() const - { - if (!HasGeometry()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - assert(slices_.size() == geometry_->GetDepth()); - return *geometry_; - } - } - - const DicomInstanceParameters& GetSliceParameters(size_t index) const - { - CheckSliceIndex(index); - return *slices_[index]; - } - - uint64_t GetSliceRevision(size_t index) const - { - CheckSliceIndex(index); - return slicesRevision_[index]; - } - - void IncrementSliceRevision(size_t index) - { - CheckSliceIndex(index); - slicesRevision_[index] ++; - } - }; - - - class ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice - { - private: - const OrthancSeriesVolumeProgressiveLoader& that_; - - protected: - virtual uint64_t GetRevisionInternal(VolumeProjection projection, - unsigned int sliceIndex) const - { - if (projection == VolumeProjection_Axial) - { - return that_.seriesGeometry_.GetSliceRevision(sliceIndex); - } - else - { - // For coronal and sagittal projections, we take the global - // revision of the volume because even if a single slice changes, - // this means the projection will yield a different result --> - // we must increase the revision as soon as any slice changes - return that_.volume_->GetRevision(); - } - } - - public: - ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that, - const CoordinateSystem3D& plane) : - DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), - that_(that) - { - if (that_.strategy_.get() != NULL && - IsValid() && - GetProjection() == VolumeProjection_Axial) - { - that_.strategy_->SetCurrent(GetSliceIndex()); - } - } - }; - - - - static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) - { - return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); - } - - - void ScheduleNextSliceDownload() - { - assert(strategy_.get() != NULL); - - unsigned int sliceIndex, quality; - - if (strategy_->GetNext(sliceIndex, quality)) - { - assert(quality <= BEST_QUALITY); - - const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex); - - const std::string& instance = slice.GetOrthancInstanceIdentifier(); - if (instance.empty()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - std::auto_ptr<OracleCommandWithPayload> command; - - if (quality == BEST_QUALITY) - { - std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand); - tmp->SetHttpHeader("Accept-Encoding", "gzip"); - tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); - tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); - tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); - command.reset(tmp.release()); - } - else - { - std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand); - tmp->SetHttpHeader("Accept-Encoding", "gzip"); - tmp->SetInstance(instance); - tmp->SetQuality((quality == 0 ? 50 : 90)); - tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); - command.reset(tmp.release()); - } - - command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); - oracle_.Schedule(*this, command.release()); - } - } - - /** - This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags" - */ - void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message) - { - Json::Value body; - message.ParseJsonBody(body); - - if (body.type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - { - Json::Value::Members instances = body.getMemberNames(); - - SlicesSorter slices; - - for (size_t i = 0; i < instances.size(); i++) - { - Orthanc::DicomMap dicom; - dicom.FromDicomAsJson(body[instances[i]]); - - std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom)); - instance->SetOrthancInstanceIdentifier(instances[i]); - - // the 3D plane corresponding to the slice - CoordinateSystem3D geometry = instance->GetGeometry(); - slices.AddSlice(geometry, instance.release()); - } - - seriesGeometry_.ComputeGeometry(slices); - } - - size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth(); - - if (slicesCount == 0) - { - volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8); - } - else - { - const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0); - - volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat()); - volume_->SetDicomParameters(parameters); - volume_->GetPixelData().Clear(); - - strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY)); - - assert(simultaneousDownloads_ != 0); - for (unsigned int i = 0; i < simultaneousDownloads_; i++) - { - ScheduleNextSliceDownload(); - } - } - - slicesQuality_.resize(slicesCount, 0); - - BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); - } - - - void SetSliceContent(unsigned int sliceIndex, - const Orthanc::ImageAccessor& image, - unsigned int quality) - { - assert(sliceIndex < slicesQuality_.size() && - slicesQuality_.size() == volume_->GetPixelData().GetDepth()); - - if (quality >= slicesQuality_[sliceIndex]) - { - { - ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex); - Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); - } - - volume_->IncrementRevision(); - seriesGeometry_.IncrementSliceRevision(sliceIndex); - slicesQuality_[sliceIndex] = quality; - - BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); - } - - ScheduleNextSliceDownload(); - } - - - void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message) - { - SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY); - } - - - void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) - { - unsigned int quality; - - switch (message.GetOrigin().GetQuality()) - { - case 50: - quality = LOW_QUALITY; - break; - - case 90: - quality = MIDDLE_QUALITY; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality); - } - - - IOracle& oracle_; - bool active_; - unsigned int simultaneousDownloads_; - SeriesGeometry seriesGeometry_; - - boost::shared_ptr<DicomVolumeImage> volume_; - std::auto_ptr<IFetchingItemsSorter::IFactory> sorter_; - std::auto_ptr<IFetchingStrategy> strategy_; - std::vector<unsigned int> slicesQuality_; - - - public: - OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume, - IOracle& oracle, - IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), - IObservable(oracleObservable.GetBroker()), - oracle_(oracle), - active_(false), - simultaneousDownloads_(4), - volume_(volume), - sorter_(new BasicFetchingItemsSorter::Factory) - { - oracleObservable.RegisterObserverCallback( - new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage> - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry)); - - oracleObservable.RegisterObserverCallback( - new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage> - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent)); - - oracleObservable.RegisterObserverCallback( - new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage> - (*this, &OrthancSeriesVolumeProgressiveLoader::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) - { - if (active_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - active_ = true; - - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetUri("/series/" + seriesId + "/instances-tags"); - - oracle_.Schedule(*this, command.release()); - } - } - - /** - When a slice is requested, the strategy algorithm (that defines the - sequence of resources to be loaded from the server) is modified to - take into account this request (this is done in the ExtractedSlice ctor) - */ - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) - { - if (volume_->HasGeometry()) - { - return new ExtractedSlice(*this, cuttingPlane); - } - else - { - return new IVolumeSlicer::InvalidSlice; - } - } - }; - - - /** This class is supplied with Oracle commands and will schedule up to simultaneousDownloads_ of them at the same time, then will schedule the rest once slots become available. It is used, a.o., by the