Mercurial > hg > orthanc-stone
view Samples/Sdl/Loader.cpp @ 789:c83a45f864b2
Merge
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Tue, 28 May 2019 08:15:38 +0200 |
parents | e76c4eef1054 9f3b2027a4a9 |
children | 4fe4b221a31f |
line wrap: on
line source
/** * Stone of Orthanc * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2019 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "../../Framework/Toolbox/DicomInstanceParameters.h" #include "../../Framework/Oracle/ThreadedOracle.h" #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" #include "../../Framework/Oracle/GetOrthancImageCommand.h" #include "../../Framework/Oracle/OrthancRestApiCommand.h" #include "../../Framework/Oracle/SleepOracleCommand.h" #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" // From Stone #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" #include "../../Framework/Loaders/BasicFetchingStrategy.h" #include "../../Framework/Scene2D/CairoCompositor.h" #include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/Scene2D/PolylineSceneLayer.h" #include "../../Framework/Scene2D/LookupTableTextureSceneLayer.h" #include "../../Framework/StoneInitialization.h" #include "../../Framework/Toolbox/GeometryToolbox.h" #include "../../Framework/Toolbox/SlicesSorter.h" #include "../../Framework/Toolbox/DicomStructureSet.h" #include "../../Framework/Volumes/ImageBuffer3D.h" #include "../../Framework/Volumes/VolumeImageGeometry.h" #include "../../Framework/Volumes/VolumeReslicer.h" // From Orthanc framework #include <Core/DicomFormat/DicomArray.h> #include <Core/Images/Image.h> #include <Core/Images/ImageProcessing.h> #include <Core/Images/PngWriter.h> #include <Core/Endianness.h> #include <Core/Logging.h> #include <Core/OrthancException.h> #include <Core/SystemToolbox.h> #include <Core/Toolbox.h> #include <EmbeddedResources.h> namespace OrthancStone { // Application-configurable, can be shared between 3D/2D 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; }; 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(); } } }; 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 { } }; class IVolumeSlicer : public boost::noncopyable { public: class IExtractedSlice : public boost::noncopyable { public: virtual ~IExtractedSlice() { } virtual bool IsValid() = 0; // Must be a cheap call virtual uint64_t GetRevision() = 0; // This call can take some time virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, // possibly absent const CoordinateSystem3D& cuttingPlane) = 0; }; 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() { } 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. 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); } } }; class DicomVolumeImageOrthogonalSlice : public IVolumeSlicer::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: DicomVolumeImageOrthogonalSlice(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 } }; 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" 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 Slice : public DicomVolumeImageOrthogonalSlice { 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 return that_.volume_->GetRevision(); } } public: Slice(const OrthancSeriesVolumeProgressiveLoader& that, const CoordinateSystem3D& plane) : DicomVolumeImageOrthogonalSlice(*that.volume_, plane), that_(that) { } }; 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()); } } 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]); 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(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()); } } virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) { if (volume_->HasGeometry()) { std::auto_ptr<Slice> slice(new Slice(*this, cuttingPlane)); if (strategy_.get() != NULL && slice->IsValid() && slice->GetProjection() == VolumeProjection_Axial) { strategy_->SetCurrent(slice->GetSliceIndex()); } return slice.release(); } else { return new IVolumeSlicer::InvalidSlice; } } }; class OrthancMultiframeVolumeLoader : public IObserver, public IObservable, public IVolumeSlicer { private: class State : public Orthanc::IDynamicObject { private: OrthancMultiframeVolumeLoader& that_; protected: void Schedule(OrthancRestApiCommand* command) const { that_.oracle_.Schedule(that_, command); } OrthancMultiframeVolumeLoader& GetTarget() const { return that_; } public: State(OrthancMultiframeVolumeLoader& that) : that_(that) { } virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const = 0; }; void Handle(const OrthancRestApiCommand::SuccessMessage& message) { dynamic_cast<const State&>(message.GetOrigin().GetPayload()).Handle(message); } class LoadRTDoseGeometry : public State { private: std::auto_ptr<Orthanc::DicomMap> dicom_; public: LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that, Orthanc::DicomMap* dicom) : State(that), dicom_(dicom) { if (dicom == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } } virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { // Complete the DICOM tags with just-received "Grid Frame Offset Vector" std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer()); dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false); GetTarget().SetGeometry(*dicom_); } }; static std::string GetSopClassUid(const Orthanc::DicomMap& dicom) { std::string s; if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "DICOM file without SOP class UID"); } else { return s; } } class LoadGeometry : public State { public: LoadGeometry(OrthancMultiframeVolumeLoader& that) : State(that) { } virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { Json::Value body; message.ParseJsonBody(body); if (body.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); } std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap); dicom->FromDicomAsJson(body); if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose) { // Download the "Grid Frame Offset Vector" DICOM tag, that is // mandatory for RT-DOSE, but is too long to be returned by default std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetUri("/instances/" + GetTarget().GetInstanceId() + "/content/" + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); command->SetPayload(new LoadRTDoseGeometry(GetTarget(), dicom.release())); Schedule(command.release()); } else { GetTarget().SetGeometry(*dicom); } } }; class LoadTransferSyntax : public State { public: LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) : State(that) { } virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { GetTarget().SetTransferSyntax(message.GetAnswer()); } }; class LoadUncompressedPixelData : public State { public: LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) : State(that) { } virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { GetTarget().SetUncompressedPixelData(message.GetAnswer()); } }; boost::shared_ptr<DicomVolumeImage> volume_; IOracle& oracle_; bool active_; std::string instanceId_; std::string transferSyntaxUid_; const std::string& GetInstanceId() const { if (active_) { return instanceId_; } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } } void ScheduleFrameDownloads() { if (transferSyntaxUid_.empty() || !volume_->HasGeometry()) { return; } if (transferSyntaxUid_ == "1.2.840.10008.1.2" || transferSyntaxUid_ == "1.2.840.10008.1.2.1" || transferSyntaxUid_ == "1.2.840.10008.1.2.2") { std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetUri("/instances/" + instanceId_ + "/content/" + Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); command->SetPayload(new LoadUncompressedPixelData(*this)); oracle_.Schedule(*this, command.release()); } else { throw Orthanc::OrthancException( Orthanc::ErrorCode_NotImplemented, "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_); } } void SetTransferSyntax(const std::string& transferSyntax) { transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax); ScheduleFrameDownloads(); } void SetGeometry(const Orthanc::DicomMap& dicom) { DicomInstanceParameters parameters(dicom); volume_->SetDicomParameters(parameters); Orthanc::PixelFormat format; if (!parameters.GetImageInformation().ExtractPixelFormat(format, true)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } double spacingZ; switch (parameters.GetSopClassUid()) { case SopClassUid_RTDose: spacingZ = parameters.GetThickness(); break; default: throw Orthanc::OrthancException( Orthanc::ErrorCode_NotImplemented, "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom)); } const unsigned int width = parameters.GetImageInformation().GetWidth(); const unsigned int height = parameters.GetImageInformation().GetHeight(); const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames(); { VolumeImageGeometry geometry; geometry.SetSize(width, height, depth); geometry.SetAxialGeometry(parameters.GetGeometry()); geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), parameters.GetPixelSpacingY(), spacingZ); volume_->Initialize(geometry, format); } volume_->GetPixelData().Clear(); ScheduleFrameDownloads(); BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); } ORTHANC_FORCE_INLINE static void CopyPixel(uint32_t& target, const void* source) { // TODO - check alignement? target = le32toh(*reinterpret_cast<const uint32_t*>(source)); } template <typename T> void CopyPixelData(const std::string& pixelData) { ImageBuffer3D& target = volume_->GetPixelData(); const Orthanc::PixelFormat format = target.GetFormat(); const unsigned int bpp = target.GetBytesPerPixel(); const unsigned int width = target.GetWidth(); const unsigned int height = target.GetHeight(); const unsigned int depth = target.GetDepth(); if (pixelData.size() != bpp * width * height * depth) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "The pixel data has not the proper size"); } if (pixelData.empty()) { return; } const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str()); for (unsigned int z = 0; z < depth; z++) { ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z); assert (writer.GetAccessor().GetWidth() == width && writer.GetAccessor().GetHeight() == height); for (unsigned int y = 0; y < height; y++) { assert(sizeof(T) == Orthanc::GetBytesPerPixel(format)); T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y)); for (unsigned int x = 0; x < width; x++) { CopyPixel(*target, source); target ++; source += bpp; } } } } void SetUncompressedPixelData(const std::string& pixelData) { switch (volume_->GetPixelData().GetFormat()) { case Orthanc::PixelFormat_Grayscale32: CopyPixelData<uint32_t>(pixelData); break; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } volume_->IncrementRevision(); BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); } public: OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume, IOracle& oracle, IObservable& oracleObservable) : IObserver(oracleObservable.GetBroker()), IObservable(oracleObservable.GetBroker()), volume_(volume), oracle_(oracle), active_(false) { if (volume.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } oracleObservable.RegisterObserverCallback( new Callable<OrthancMultiframeVolumeLoader, OrthancRestApiCommand::SuccessMessage> (*this, &OrthancMultiframeVolumeLoader::Handle)); } void LoadInstance(const std::string& instanceId) { if (active_) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { active_ = true; instanceId_ = instanceId; { std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetUri("/instances/" + instanceId + "/tags"); command->SetPayload(new LoadGeometry(*this)); oracle_.Schedule(*this, command.release()); } { std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); command->SetPayload(new LoadTransferSyntax(*this)); oracle_.Schedule(*this, command.release()); } } } virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) { if (volume_->HasGeometry()) { return new DicomVolumeImageOrthogonalSlice(*volume_, cuttingPlane); } else { return new IVolumeSlicer::InvalidSlice; } } }; class VolumeImageReslicer : public IVolumeSlicer { private: class Slice : public IExtractedSlice { private: VolumeImageReslicer& that_; CoordinateSystem3D cuttingPlane_; public: Slice(VolumeImageReslicer& that, const CoordinateSystem3D& cuttingPlane) : that_(that), cuttingPlane_(cuttingPlane) { } virtual bool IsValid() { return true; } virtual uint64_t GetRevision() { return that_.volume_->GetRevision(); } virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, // possibly absent const CoordinateSystem3D& cuttingPlane) { VolumeReslicer& reslicer = that_.reslicer_; if (configurator == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Must provide a layer style configurator"); } reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat()); reslicer.Apply(that_.volume_->GetPixelData(), that_.volume_->GetGeometry(), cuttingPlane); if (reslicer.IsSuccess()) { std::auto_ptr<TextureBaseSceneLayer> layer (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(), that_.volume_->GetDicomParameters())); if (layer.get() == NULL) { return NULL; } double s = reslicer.GetPixelSpacing(); layer->SetPixelSpacing(s, s); layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s, reslicer.GetOutputExtent().GetY1() + 0.5 * s); // TODO - Angle!! return layer.release(); } else { return NULL; } } }; boost::shared_ptr<DicomVolumeImage> volume_; VolumeReslicer reslicer_; public: VolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) : volume_(volume) { if (volume.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } } ImageInterpolation GetInterpolation() const { return reslicer_.GetInterpolation(); } void SetInterpolation(ImageInterpolation interpolation) { reslicer_.SetInterpolation(interpolation); } bool IsFastMode() const { return reslicer_.IsFastMode(); } void SetFastMode(bool fast) { reslicer_.EnableFastMode(fast); } virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) { if (volume_->HasGeometry()) { return new Slice(*this, cuttingPlane); } else { return new IVolumeSlicer::InvalidSlice; } } }; class DicomStructureSetLoader : public IObserver, public IVolumeSlicer { private: enum State { State_Setup, State_Loading, State_Ready }; std::auto_ptr<DicomStructureSet> content_; IOracle& oracle_; State state_; std::string instanceId_; void Handle(const OrthancRestApiCommand::SuccessMessage& message) { if (state_ != State_Loading) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } const boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); { OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); content_.reset(new DicomStructureSet(dicom)); } const boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); printf("LOADED: %d\n", (end - start).total_milliseconds()); state_ = State_Ready; } public: DicomStructureSetLoader(IOracle& oracle, IObservable& oracleObservable) : IObserver(oracleObservable.GetBroker()), oracle_(oracle), state_(State_Setup) { oracleObservable.RegisterObserverCallback( new Callable<DicomStructureSetLoader, OrthancRestApiCommand::SuccessMessage> (*this, &DicomStructureSetLoader::Handle)); } void LoadInstance(const std::string& instanceId) { if (state_ != State_Setup) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { state_ = State_Loading; instanceId_ = instanceId; { std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3006-0050"); oracle_.Schedule(*this, command.release()); } } } virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) { return NULL; } }; class VolumeSceneLayerSource : public boost::noncopyable { private: Scene2D& scene_; int layerDepth_; boost::shared_ptr<IVolumeSlicer> slicer_; std::auto_ptr<ILayerStyleConfigurator> configurator_; std::auto_ptr<CoordinateSystem3D> lastPlane_; uint64_t lastRevision_; uint64_t lastConfiguratorRevision_; static bool IsSameCuttingPlane(const CoordinateSystem3D& a, const CoordinateSystem3D& b) { // TODO - What if the normal is reversed? double distance; return (CoordinateSystem3D::ComputeDistance(distance, a, b) && LinearAlgebra::IsCloseToZero(distance)); } void ClearLayer() { scene_.DeleteLayer(layerDepth_); lastPlane_.reset(NULL); } public: VolumeSceneLayerSource(Scene2D& scene, int layerDepth, const boost::shared_ptr<IVolumeSlicer>& slicer) : scene_(scene), layerDepth_(layerDepth), slicer_(slicer) { if (slicer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } } const IVolumeSlicer& GetSlicer() const { return *slicer_; } void RemoveConfigurator() { configurator_.reset(); lastPlane_.reset(); } void SetConfigurator(ILayerStyleConfigurator* configurator) // Takes ownership { if (configurator == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } configurator_.reset(configurator); // Invalidate the layer lastPlane_.reset(NULL); } bool HasConfigurator() const { return configurator_.get() != NULL; } ILayerStyleConfigurator& GetConfigurator() const { if (configurator_.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } return *configurator_; } void Update(const CoordinateSystem3D& plane) { assert(slicer_.get() != NULL); std::auto_ptr<IVolumeSlicer::IExtractedSlice> slice(slicer_->ExtractSlice(plane)); if (slice.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } if (!slice->IsValid()) { // The slicer cannot handle this cutting plane: Clear the layer ClearLayer(); } else if (lastPlane_.get() != NULL && IsSameCuttingPlane(*lastPlane_, plane) && lastRevision_ == slice->GetRevision()) { // The content of the slice has not changed: Don't update the // layer content, but possibly update its style if (configurator_.get() != NULL && configurator_->GetRevision() != lastConfiguratorRevision_ && scene_.HasLayer(layerDepth_)) { configurator_->ApplyStyle(scene_.GetLayer(layerDepth_)); } } else { // Content has changed: An update is needed lastPlane_.reset(new CoordinateSystem3D(plane)); lastRevision_ = slice->GetRevision(); std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane)); if (layer.get() == NULL) { ClearLayer(); } else { if (configurator_.get() != NULL) { lastConfiguratorRevision_ = configurator_->GetRevision(); configurator_->ApplyStyle(*layer); } scene_.SetLayer(layerDepth_, layer.release()); } } } }; class NativeApplicationContext : public IMessageEmitter { private: boost::shared_mutex mutex_; MessageBroker broker_; IObservable oracleObservable_; public: NativeApplicationContext() : oracleObservable_(broker_) { } virtual void EmitMessage(const IObserver& observer, const IMessage& message) { try { boost::unique_lock<boost::shared_mutex> lock(mutex_); oracleObservable_.EmitMessage(observer, message); } catch (Orthanc::OrthancException& e) { LOG(ERROR) << "Exception while emitting a message: " << e.What(); } } class ReaderLock : public boost::noncopyable { private: NativeApplicationContext& that_; boost::shared_lock<boost::shared_mutex> lock_; public: ReaderLock(NativeApplicationContext& that) : that_(that), lock_(that.mutex_) { } }; class WriterLock : public boost::noncopyable { private: NativeApplicationContext& that_; boost::unique_lock<boost::shared_mutex> lock_; public: WriterLock(NativeApplicationContext& that) : that_(that), lock_(that.mutex_) { } MessageBroker& GetBroker() { return that_.broker_; } IObservable& GetOracleObservable() { return that_.oracleObservable_; } }; }; } class Toto : public OrthancStone::IObserver { private: OrthancStone::CoordinateSystem3D plane_; OrthancStone::IOracle& oracle_; OrthancStone::Scene2D scene_; std::auto_ptr<OrthancStone::VolumeSceneLayerSource> source1_, source2_; void Refresh() { if (source1_.get() != NULL) { source1_->Update(plane_); } if (source2_.get() != NULL) { source2_->Update(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); } } void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message) { printf("Geometry ready\n"); //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); Refresh(); } void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message) { if (message.GetOrigin().HasPayload()) { printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue()); } else { printf("TIMEOUT\n"); Refresh(); /** * The sleep() leads to a crash if the oracle is still running, * while this object is destroyed. Always stop the oracle before * destroying active objects. (*) **/ // boost::this_thread::sleep(boost::posix_time::seconds(2)); oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay())); } } void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { Json::Value v; message.ParseJsonBody(v); printf("ICI [%s]\n", v.toStyledString().c_str()); } void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) { printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); } void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) { printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); } void Handle(const OrthancStone::OracleCommandExceptionMessage& message) { printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); switch (message.GetCommand().GetType()) { case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg: printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&> (message.GetCommand()).GetUri().c_str()); break; default: break; } } public: Toto(OrthancStone::IOracle& oracle, OrthancStone::IObservable& oracleObservable) : IObserver(oracleObservable.GetBroker()), oracle_(oracle) { oracleObservable.RegisterObserverCallback (new OrthancStone::Callable <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle)); oracleObservable.RegisterObserverCallback (new OrthancStone::Callable <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle)); oracleObservable.RegisterObserverCallback (new OrthancStone::Callable <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle)); oracleObservable.RegisterObserverCallback (new OrthancStone::Callable <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle)); oracleObservable.RegisterObserverCallback (new OrthancStone::Callable <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle)); } void SetReferenceLoader(OrthancStone::IObservable& loader) { loader.RegisterObserverCallback (new OrthancStone::Callable <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle)); } void SetVolume1(int depth, const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, OrthancStone::ILayerStyleConfigurator* style) { source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); if (style != NULL) { source1_->SetConfigurator(style); } } void SetVolume2(int depth, const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, OrthancStone::ILayerStyleConfigurator* style) { source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); if (style != NULL) { source2_->SetConfigurator(style); } } }; void Run(OrthancStone::NativeApplicationContext& context, OrthancStone::ThreadedOracle& oracle) { boost::shared_ptr<OrthancStone::DicomVolumeImage> ct(new OrthancStone::DicomVolumeImage); boost::shared_ptr<OrthancStone::DicomVolumeImage> dose(new OrthancStone::DicomVolumeImage); boost::shared_ptr<Toto> toto; boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader; boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader; boost::shared_ptr<OrthancStone::DicomStructureSetLoader> rtstructLoader; { OrthancStone::NativeApplicationContext::WriterLock lock(context); toto.reset(new Toto(oracle, lock.GetOracleObservable())); ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable())); doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable())); rtstructLoader.reset(new OrthancStone::DicomStructureSetLoader(oracle, lock.GetOracleObservable())); } toto->SetReferenceLoader(*ctLoader); #if 1 toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator); #else { boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::VolumeImageReslicer(ct)); toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator); } #endif { std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator); config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); toto->SetVolume2(1, doseLoader, config.release()); } //oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); if (0) { Json::Value v = Json::objectValue; v["Level"] = "Series"; v["Query"] = Json::objectValue; std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); command->SetMethod(Orthanc::HttpMethod_Post); command->SetUri("/tools/find"); command->SetBody(v); oracle.Schedule(*toto, command.release()); } if (0) { std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); oracle.Schedule(*toto, command.release()); } if (0) { std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); oracle.Schedule(*toto, command.release()); } if (0) { std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); oracle.Schedule(*toto, command.release()); } if (0) { std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); oracle.Schedule(*toto, command.release()); } if (0) { std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); oracle.Schedule(*toto, command.release()); } if (0) { std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand> command(new OrthancStone::GetOrthancWebViewerJpegCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); command->SetQuality(90); oracle.Schedule(*toto, command.release()); } if (0) { for (unsigned int i = 0; i < 10; i++) { std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000)); command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i)); oracle.Schedule(*toto, command.release()); } } // 2017-11-17-Anonymized //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT // 2015-01-28-Multiframe //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279"); // Multiframe CT // Delphine //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm { LOG(WARNING) << "...Waiting for Ctrl-C..."; oracle.Start(); Orthanc::SystemToolbox::ServerBarrier(); /** * WARNING => The oracle must be stopped BEFORE the objects using * it are destroyed!!! This forces to wait for the completion of * the running callback methods. Otherwise, the callbacks methods * might still be running while their parent object is destroyed, * resulting in crashes. This is very visible if adding a sleep(), * as in (*). **/ oracle.Stop(); } } /** * IMPORTANT: The full arguments to "main()" are needed for SDL on * Windows. Otherwise, one gets the linking error "undefined reference * to `SDL_main'". https://wiki.libsdl.org/FAQWindows **/ int main(int argc, char* argv[]) { OrthancStone::StoneInitialize(); //Orthanc::Logging::EnableInfoLevel(true); try { OrthancStone::NativeApplicationContext context; OrthancStone::ThreadedOracle oracle(context); //oracle.SetThreadsCount(1); { Orthanc::WebServiceParameters p; //p.SetUrl("http://localhost:8043/"); p.SetCredentials("orthanc", "orthanc"); oracle.SetOrthancParameters(p); } //oracle.Start(); Run(context, oracle); //oracle.Stop(); } catch (Orthanc::OrthancException& e) { LOG(ERROR) << "EXCEPTION: " << e.What(); } OrthancStone::StoneFinalize(); return 0; }