Mercurial > hg > orthanc-stone
changeset 1225:16738485e457 broker
deprecating DicomStructureSetLoader, OrthancMultiframeVolumeLoader and OrthancSeriesVolumeProgressiveLoader
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,417 @@ +/** + * 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 "DicomStructureSetLoader.h" + +#include "../../Scene2D/PolylineSceneLayer.h" +#include "../../StoneException.h" +#include "../../Toolbox/GeometryToolbox.h" + +#include <Core/Toolbox.h> + +#include <algorithm> + +#if 0 +bool logbgo233 = false; +bool logbgo115 = false; +#endif + +namespace Deprecated +{ + +#if 0 + void DumpDicomMap(std::ostream& o, const Orthanc::DicomMap& dicomMap) + { + using namespace std; + //ios_base::fmtflags state = o.flags(); + //o.flags(ios::right | ios::hex); + //o << "(" << setfill('0') << setw(4) << tag.GetGroup() + // << "," << setw(4) << tag.GetElement() << ")"; + //o.flags(state); + Json::Value val; + dicomMap.Serialize(val); + o << val; + //return o; + } +#endif + + + class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State + { + private: + std::string instanceId_; + + public: + AddReferencedInstance(DicomStructureSetLoader& that, + const std::string& instanceId) : + State(that), + instanceId_(instanceId) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value tags; + message.ParseJsonBody(tags); + + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(tags); + + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + + loader.content_->AddReferencedSlice(dicom); + + loader.countProcessedInstances_ ++; + assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_); + + if (loader.countProcessedInstances_ == loader.countReferencedInstances_) + { + // All the referenced instances have been loaded, finalize the RT-STRUCT + loader.content_->CheckReferencedSlices(); + loader.revision_++; + loader.SetStructuresReady(); + } + } + }; + + + // State that converts a "SOP Instance UID" to an Orthanc identifier + class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State + { + private: + std::string sopInstanceUid_; + + public: + LookupInstance(DicomStructureSetLoader& that, + const std::string& sopInstanceUid) : + State(that), + sopInstanceUid_(sopInstanceUid) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { +#if 0 + LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)"; +#endif + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + + Json::Value lookup; + message.ParseJsonBody(lookup); + + if (lookup.type() != Json::arrayValue || + lookup.size() != 1 || + !lookup[0].isMember("Type") || + !lookup[0].isMember("Path") || + lookup[0]["Type"].type() != Json::stringValue || + lookup[0]["ID"].type() != Json::stringValue || + lookup[0]["Type"].asString() != "Instance") + { + std::stringstream msg; + msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = "; + for (OrthancStone::OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin(); + it != message.GetAnswerHeaders().end(); ++it) + { + msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n"; + } + const std::string msgStr = msg.str(); + LOG(ERROR) << msgStr; + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + const std::string instanceId = lookup[0]["ID"].asString(); + + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + std::string uri = "/instances/" + instanceId + "/tags"; + command->SetUri(uri); + command->AcquirePayload(new AddReferencedInstance(loader, instanceId)); + Schedule(command.release()); + } + } + }; + + + class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State + { + public: + LoadStructure(DicomStructureSetLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { +#if 0 + if (logbgo115) + LOG(TRACE) << "DicomStructureSetLoader::LoadStructure::Handle() (SUCCESS)"; +#endif + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + + { + OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); + loader.content_.reset(new OrthancStone::DicomStructureSet(dicom)); + size_t structureCount = loader.content_->GetStructuresCount(); + loader.structureVisibility_.resize(structureCount); + bool everythingVisible = false; + if ((loader.initiallyVisibleStructures_.size() == 1) + && (loader.initiallyVisibleStructures_[0].size() == 1) + && (loader.initiallyVisibleStructures_[0][0] == '*')) + { + everythingVisible = true; + } + + for (size_t i = 0; i < structureCount; ++i) + { + // if a single "*" string is supplied, this means we want everything + // to be visible... + if(everythingVisible) + { + loader.structureVisibility_.at(i) = true; + } + else + { + // otherwise, we only enable visibility for those structures whose + // names are mentioned in the initiallyVisibleStructures_ array + const std::string& structureName = loader.content_->GetStructureName(i); + + std::vector<std::string>::iterator foundIt = + std::find( + loader.initiallyVisibleStructures_.begin(), + loader.initiallyVisibleStructures_.end(), + structureName); + std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end(); + if (foundIt != endIt) + loader.structureVisibility_.at(i) = true; + else + loader.structureVisibility_.at(i) = false; + } + } + } + + // Some (admittedly invalid) Dicom files have empty values in the + // 0008,1155 tag. We try our best to cope with this. + std::set<std::string> instances; + std::set<std::string> nonEmptyInstances; + loader.content_->GetReferencedInstances(instances); + for (std::set<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + std::string instance = Orthanc::Toolbox::StripSpaces(*it); + if(instance != "") + nonEmptyInstances.insert(instance); + } + + loader.countReferencedInstances_ = + static_cast<unsigned int>(nonEmptyInstances.size()); + + for (std::set<std::string>::const_iterator + it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it) + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetUri("/tools/lookup"); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetBody(*it); + command->AcquirePayload(new LookupInstance(loader, *it)); + Schedule(command.release()); + } + } + }; + + + class DicomStructureSetLoader::Slice : public IExtractedSlice + { + private: + const OrthancStone::DicomStructureSet& content_; + uint64_t revision_; + bool isValid_; + std::vector<bool> visibility_; + + public: + /** + The visibility vector must either: + - be empty + or + - contain the same number of items as the number of structures in the + structure set. + In the first case (empty vector), all the structures are displayed. + In the second case, the visibility of each structure is defined by the + content of the vector at the corresponding index. + */ + Slice(const OrthancStone::DicomStructureSet& content, + uint64_t revision, + const OrthancStone::CoordinateSystem3D& cuttingPlane, + std::vector<bool> visibility = std::vector<bool>()) + : content_(content) + , revision_(revision) + , visibility_(visibility) + { + ORTHANC_ASSERT((visibility_.size() == content_.GetStructuresCount()) + || (visibility_.size() == 0u)); + + bool opposite; + + const OrthancStone::Vector normal = content.GetNormal(); + isValid_ = ( + OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || + OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || + OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); + } + + virtual bool IsValid() + { + return isValid_; + } + + virtual uint64_t GetRevision() + { + return revision_; + } + + virtual OrthancStone::ISceneLayer* CreateSceneLayer( + const OrthancStone::ILayerStyleConfigurator* configurator, + const OrthancStone::CoordinateSystem3D& cuttingPlane) + { + assert(isValid_); + + std::auto_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer); + layer->SetThickness(2); + + for (size_t i = 0; i < content_.GetStructuresCount(); i++) + { + if ((visibility_.size() == 0) || visibility_.at(i)) + { + const OrthancStone::Color& color = content_.GetStructureColor(i); + +#ifdef USE_BOOST_UNION_FOR_POLYGONS + std::vector< std::vector<OrthancStone::Point2D> > polygons; + + if (content_.ProjectStructure(polygons, i, cuttingPlane)) + { + for (size_t j = 0; j < polygons.size(); j++) + { + PolylineSceneLayer::Chain chain; + chain.resize(polygons[j].size()); + + for (size_t k = 0; k < polygons[j].size(); k++) + { + chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y); + } + + layer->AddChain(chain, true /* closed */, color); + } + } +#else + std::vector< std::pair<OrthancStone::Point2D, OrthancStone::Point2D> > segments; + + if (content_.ProjectStructure(segments, i, cuttingPlane)) + { + for (size_t j = 0; j < segments.size(); j++) + { + OrthancStone::PolylineSceneLayer::Chain chain; + chain.resize(2); + + chain[0] = OrthancStone::ScenePoint2D(segments[j].first.x, segments[j].first.y); + chain[1] = OrthancStone::ScenePoint2D(segments[j].second.x, segments[j].second.y); + + layer->AddChain(chain, false /* NOT closed */, color); + } + } +#endif + } + } + + return layer.release(); + } + }; + + + DicomStructureSetLoader::DicomStructureSetLoader(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + LoaderStateMachine(oracle, oracleObservable), + revision_(0), + countProcessedInstances_(0), + countReferencedInstances_(0), + structuresReady_(false) + { + } + + + void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display) + { + structureVisibility_.at(structureIndex) = display; + revision_++; + } + + DicomStructureSetLoader::~DicomStructureSetLoader() + { + LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()"; + } + + void DicomStructureSetLoader::LoadInstance( + const std::string& instanceId, + const std::vector<std::string>& initiallyVisibleStructures) + { + Start(); + + instanceId_ = instanceId; + initiallyVisibleStructures_ = initiallyVisibleStructures; + + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + + std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; + + command->SetUri(uri); + command->AcquirePayload(new LoadStructure(*this)); + Schedule(command.release()); + } + } + + + OrthancStone::IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) + { + if (content_.get() == NULL) + { + // Geometry is not available yet + return new OrthancStone::IVolumeSlicer::InvalidSlice; + } + else + { + return new Slice(*content_, revision_, cuttingPlane, structureVisibility_); + } + } + + void DicomStructureSetLoader::SetStructuresReady() + { + ORTHANC_ASSERT(!structuresReady_); + structuresReady_ = true; + BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this)); + } + + bool DicomStructureSetLoader::AreStructuresReady() const + { + return structuresReady_; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader.h Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,100 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include "../../Toolbox/DicomStructureSet.h" +#include "../../Volumes/IVolumeSlicer.h" +#include "LoaderStateMachine.h" + +#include <vector> + +namespace Deprecated +{ + class DicomStructureSetLoader : + public LoaderStateMachine, + public OrthancStone::IVolumeSlicer, + public OrthancStone::IObservable + { + private: + class Slice; + + // States of LoaderStateMachine + class AddReferencedInstance; // 3rd state + class LookupInstance; // 2nd state + class LoadStructure; // 1st state + + std::auto_ptr<OrthancStone::DicomStructureSet> content_; + uint64_t revision_; + std::string instanceId_; + unsigned int countProcessedInstances_; + unsigned int countReferencedInstances_; + + // will be set to true once the loading is finished + bool structuresReady_; + + /** + At load time, these strings are used to initialize the structureVisibility_ + vector. + + As a special case, if initiallyVisibleStructures_ contains a single string + that is '*', ALL structures will be made visible. + */ + std::vector<std::string> initiallyVisibleStructures_; + + /** + Contains the "Should this structure be displayed?" flag for all structures. + Only filled when structures are loaded. + + Changing this value directly affects the rendering + */ + std::vector<bool> structureVisibility_; + + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader); + + DicomStructureSetLoader(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable); + + OrthancStone::DicomStructureSet* GetContent() + { + return content_.get(); + } + + void SetStructureDisplayState(size_t structureIndex, bool display); + + bool GetStructureDisplayState(size_t structureIndex) const + { + return structureVisibility_.at(structureIndex); + } + + ~DicomStructureSetLoader(); + + void LoadInstance(const std::string& instanceId, + const std::vector<std::string>& initiallyVisibleStructures = std::vector<std::string>()); + + virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + + void SetStructuresReady(); + + bool AreStructuresReady() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,125 @@ +/** + * 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/>. + **/ + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructureSetLoader2.h" + +#include "../Messages/IObservable.h" +#include "../Oracle/IOracle.h" +#include "../Oracle/OracleCommandExceptionMessage.h" + +namespace Deprecated +{ + + DicomStructureSetLoader2::DicomStructureSetLoader2( + DicomStructureSet2& structureSet + , IOracle& oracle + , IObservable& oracleObservable) + : IObserver(oracleObservable.GetBroker()) + , IObservable(oracleObservable.GetBroker()) + , structureSet_(structureSet) + , oracle_(oracle) + , oracleObservable_(oracleObservable) + , structuresReady_(false) + { + LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()"; + + oracleObservable.RegisterObserverCallback( + new Callable<DicomStructureSetLoader2, OrthancRestApiCommand::SuccessMessage> + (*this, &DicomStructureSetLoader2::HandleSuccessMessage)); + + oracleObservable.RegisterObserverCallback( + new Callable<DicomStructureSetLoader2, OracleCommandExceptionMessage> + (*this, &DicomStructureSetLoader2::HandleExceptionMessage)); + } + + DicomStructureSetLoader2::~DicomStructureSetLoader2() + { + LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()"; + oracleObservable_.Unregister(this); + } + + void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body) + { + OrthancPlugins::FullOrthancDataset dicom(body); + //loader.content_.reset(new DicomStructureSet(dicom)); + structureSet_.Clear(); + structureSet_.SetContents(dicom); + SetStructuresReady(); + } + + void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message) + { + const std::string& body = message.GetAnswer(); + LoadInstanceFromString(body); + } + + void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. " + << "Error: " << message.GetException().What() << " Details: " + << message.GetException().GetDetails(); + } + + void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId) + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + + std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; + + command->SetUri(uri); + oracle_.Schedule(*this, command.release()); + } + + void DicomStructureSetLoader2::SetStructuresReady() + { + structuresReady_ = true; + } + + bool DicomStructureSetLoader2::AreStructuresReady() const + { + return structuresReady_; + } + + /* + + void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; + LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << + message.GetException().GetDetails(); + Clear(); + } + + LoaderStateMachine::~LoaderStateMachine() + { + Clear(); + } + + + */ + +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader2.h Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,87 @@ +/** + * 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/>. + **/ + +#pragma once + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "../Toolbox/DicomStructureSet2.h" +#include "../Messages/IMessage.h" +#include "../Messages/IObserver.h" +#include "../Messages/IObservable.h" +#include "../Oracle/OrthancRestApiCommand.h" + +#include <boost/noncopyable.hpp> + +namespace Deprecated +{ + class IOracle; + class IObservable; + class OrthancRestApiCommand; + class OracleCommandExceptionMessage; + + class DicomStructureSetLoader2 : public IObserver, public IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2); + + /** + Warning: the structureSet, oracle and oracleObservable objects must live + at least as long as this object (TODO: shared_ptr?) + */ + DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable); + + ~DicomStructureSetLoader2(); + + void LoadInstance(const std::string& instanceId); + + /** Internal use */ + void LoadInstanceFromString(const std::string& body); + + void SetStructuresReady(); + bool AreStructuresReady() const; + + private: + /** + Called back by the oracle when data is ready! + */ + void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message); + + /** + Called back by the oracle when shit hits the fan + */ + void HandleExceptionMessage(const OracleCommandExceptionMessage& message); + + /** + The structure set that will be (cleared and) filled with data from the + loader + */ + DicomStructureSet2& structureSet_; + + IOracle& oracle_; + IObservable& oracleObservable_; + bool structuresReady_; + }; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/LoaderCache.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,412 @@ +/** + * 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 "LoaderCache.h" + +#include "../../StoneException.h" +#include "OrthancSeriesVolumeProgressiveLoader.h" +#include "OrthancMultiframeVolumeLoader.h" +#include "DicomStructureSetLoader.h" + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 +#include "DicomStructureSetLoader2.h" +#endif + //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + +#if ORTHANC_ENABLE_WASM == 1 +# include <unistd.h> +# include "../../Oracle/WebAssemblyOracle.h" +#else +# include "../../Oracle/ThreadedOracle.h" +#endif + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 +#include "../../Toolbox/DicomStructureSet2.h" +#endif +//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "../../Volumes/DicomVolumeImage.h" +#include "../../Volumes/DicomVolumeImageMPRSlicer.h" + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 +#include "../../Volumes/DicomStructureSetSlicer2.h" +#endif +//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +namespace Deprecated +{ +#if ORTHANC_ENABLE_WASM == 1 + LoaderCache::LoaderCache(OrthancStone::WebAssemblyOracle& oracle) + : oracle_(oracle) + { + + } +#else + LoaderCache::LoaderCache(OrthancStone::ThreadedOracle& oracle, + OrthancStone::Deprecated::LockingEmitter& lockingEmitter) + : oracle_(oracle) + , lockingEmitter_(lockingEmitter) + { + } +#endif + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> + LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid) + { + try + { + + // normalize keys a little + seriesUuid = Orthanc::Toolbox::StripSpaces(seriesUuid); + Orthanc::Toolbox::ToLowerCase(seriesUuid); + + // find in cache + if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end()) + { +// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : CACHEMISS --> need to load seriesUUid = " << seriesUuid; +#if ORTHANC_ENABLE_WASM == 1 +// LOG(TRACE) << "Performing request for series " << seriesUuid << " sbrk(0) = " << sbrk(0); +#else +// LOG(TRACE) << "Performing request for series " << seriesUuid; +#endif + boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage); + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader; +// LOG(TRACE) << "volumeImage = " << volumeImage.get(); + { +#if ORTHANC_ENABLE_WASM == 1 + loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_)); +#else + OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); + loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable())); +#endif +// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get(); + loader->LoadSeries(seriesUuid); +// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader->LoadSeries successful"; + } + seriesVolumeProgressiveLoaders_[seriesUuid] = loader; + } + else + { +// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid; + } + return seriesVolumeProgressiveLoaders_[seriesUuid]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in LoaderCache"; + throw; + } + } + + boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid) + { + // if the loader is not available, let's trigger its creation + if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end()) + { + GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid); + } + ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end()); + + return multiframeVolumeLoaders_[instanceUuid]; + } + + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid) + { + try + { + // normalize keys a little + instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid); + Orthanc::Toolbox::ToLowerCase(instanceUuid); + + // find in cache + if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end()) + { + boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage); + boost::shared_ptr<OrthancMultiframeVolumeLoader> loader; + + { +#if ORTHANC_ENABLE_WASM == 1 + loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, oracle_)); +#else + OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); + loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, lock.GetOracleObservable())); +#endif + loader->LoadInstance(instanceUuid); + } + multiframeVolumeLoaders_[instanceUuid] = loader; + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage)); + dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer; + } + return dicomVolumeImageMPRSlicers_[instanceUuid]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in LoaderCache"; + throw; + } + } + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + boost::shared_ptr<DicomStructureSetSlicer2> LoaderCache::GetDicomStructureSetSlicer2(std::string instanceUuid) + { + // if the loader is not available, let's trigger its creation + if (dicomStructureSetSlicers2_.find(instanceUuid) == dicomStructureSetSlicers2_.end()) + { + GetDicomStructureSetLoader2(instanceUuid); + } + ORTHANC_ASSERT(dicomStructureSetSlicers2_.find(instanceUuid) != dicomStructureSetSlicers2_.end()); + + return dicomStructureSetSlicers2_[instanceUuid]; + } +#endif +//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + + /** + This method allows to convert a list of string into a string by + sorting the strings then joining them + */ + static std::string SortAndJoin(const std::vector<std::string>& stringList) + { + if (stringList.size() == 0) + { + return ""; + } + else + { + std::vector<std::string> sortedStringList = stringList; + std::sort(sortedStringList.begin(), sortedStringList.end()); + std::stringstream s; + s << sortedStringList[0]; + for (size_t i = 1; i < sortedStringList.size(); ++i) + { + s << "-" << sortedStringList[i]; + } + return s.str(); + } + } + + boost::shared_ptr<DicomStructureSetLoader> + LoaderCache::GetDicomStructureSetLoader( + std::string inInstanceUuid, + const std::vector<std::string>& initiallyVisibleStructures) + { + try + { + // normalize keys a little + inInstanceUuid = Orthanc::Toolbox::StripSpaces(inInstanceUuid); + Orthanc::Toolbox::ToLowerCase(inInstanceUuid); + + std::string initiallyVisibleStructuresKey = + SortAndJoin(initiallyVisibleStructures); + + std::string entryKey = inInstanceUuid + "_" + initiallyVisibleStructuresKey; + + // find in cache + if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end()) + { + boost::shared_ptr<DicomStructureSetLoader> loader; + + { +#if ORTHANC_ENABLE_WASM == 1 + loader.reset(new DicomStructureSetLoader(oracle_, oracle_)); +#else + OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); + loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable())); +#endif + loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures); + } + dicomStructureSetLoaders_[entryKey] = loader; + } + return dicomStructureSetLoaders_[entryKey]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in LoaderCache"; + throw; + } + } + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + boost::shared_ptr<DicomStructureSetLoader2> LoaderCache::GetDicomStructureSetLoader2(std::string instanceUuid) + { + try + { + // normalize keys a little + instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid); + Orthanc::Toolbox::ToLowerCase(instanceUuid); + + // find in cache + if (dicomStructureSetLoaders2_.find(instanceUuid) == dicomStructureSetLoaders2_.end()) + { + boost::shared_ptr<DicomStructureSetLoader2> loader; + boost::shared_ptr<DicomStructureSet2> structureSet(new DicomStructureSet2()); + boost::shared_ptr<DicomStructureSetSlicer2> rtSlicer(new DicomStructureSetSlicer2(structureSet)); + dicomStructureSetSlicers2_[instanceUuid] = rtSlicer; + dicomStructureSets2_[instanceUuid] = structureSet; // to prevent it from being deleted + { +#if ORTHANC_ENABLE_WASM == 1 + loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, oracle_)); +#else + LockingEmitter::WriterLock lock(lockingEmitter_); + // TODO: clarify lifetimes... this is DANGEROUS! + loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, lock.GetOracleObservable())); +#endif + loader->LoadInstance(instanceUuid); + } + dicomStructureSetLoaders2_[instanceUuid] = loader; + } + return dicomStructureSetLoaders2_[instanceUuid]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in GetDicomStructureSetLoader2: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in GetDicomStructureSetLoader2"; + throw; + } + } + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + + void LoaderCache::ClearCache() + { +#if ORTHANC_ENABLE_WASM != 1 + OrthancStone::Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); +#endif + +//#ifndef NDEBUG + // ISO way of checking for debug builds + DebugDisplayObjRefCounts(); +//#endif + seriesVolumeProgressiveLoaders_.clear(); + multiframeVolumeLoaders_.clear(); + dicomVolumeImageMPRSlicers_.clear(); + dicomStructureSetLoaders_.clear(); + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + // order is important! + dicomStructureSetLoaders2_.clear(); + dicomStructureSetSlicers2_.clear(); + dicomStructureSets2_.clear(); +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + } + + template<typename T> void DebugDisplayObjRefCountsInMap( + const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap) + { + LOG(TRACE) << "Map \"" << name << "\" ref counts:"; + size_t i = 0; + for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator + it = myMap.begin(); it != myMap.end(); ++it) + { + LOG(TRACE) << " element #" << i << ": ref count = " << it->second.use_count(); + i++; + } + } + + void LoaderCache::DebugDisplayObjRefCounts() + { + DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_); + DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_); + DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_); + DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_); +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders2_", dicomStructureSetLoaders2_); + DebugDisplayObjRefCountsInMap("dicomStructureSetSlicers2_", dicomStructureSetSlicers2_); +#endif +//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/LoaderCache.h Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,109 @@ +/** + * 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/>. + **/ + +#pragma once + +#include "../../Messages/LockingEmitter.h" +#include "../../Volumes/DicomVolumeImageMPRSlicer.h" +#include "OrthancSeriesVolumeProgressiveLoader.h" +#include "OrthancMultiframeVolumeLoader.h" +#include "DicomStructureSetLoader.h" + +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace OrthancStone +{ +#if ORTHANC_ENABLE_WASM == 1 + class WebAssemblyOracle; +#else + class ThreadedOracle; +#endif +} + +namespace Deprecated +{ + class LoaderCache + { + public: +#if ORTHANC_ENABLE_WASM == 1 + LoaderCache(OrthancStone::WebAssemblyOracle& oracle); +#else + LoaderCache(OrthancStone::ThreadedOracle& oracle, OrthancStone::Deprecated::LockingEmitter& lockingEmitter); +#endif + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> + GetSeriesVolumeProgressiveLoader (std::string seriesUuid); + + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> + GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid); + + boost::shared_ptr<OrthancMultiframeVolumeLoader> + GetMultiframeVolumeLoader(std::string instanceUuid); + + boost::shared_ptr<DicomStructureSetLoader> + GetDicomStructureSetLoader( + std::string instanceUuid, + const std::vector<std::string>& initiallyVisibleStructures); + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + boost::shared_ptr<DicomStructureSetLoader2> + GetDicomStructureSetLoader2(std::string instanceUuid); + + boost::shared_ptr<DicomStructureSetSlicer2> + GetDicomStructureSetSlicer2(std::string instanceUuid); +#endif + //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + void ClearCache(); + + private: + + void DebugDisplayObjRefCounts(); +#if ORTHANC_ENABLE_WASM == 1 + OrthancStone::WebAssemblyOracle& oracle_; +#else + OrthancStone::ThreadedOracle& oracle_; + OrthancStone::Deprecated::LockingEmitter& lockingEmitter_; +#endif + + std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> > + seriesVolumeProgressiveLoaders_; + std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> > + multiframeVolumeLoaders_; + std::map<std::string, boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> > + dicomVolumeImageMPRSlicers_; + std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> > + dicomStructureSetLoaders_; +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + std::map<std::string, boost::shared_ptr<DicomStructureSetLoader2> > + dicomStructureSetLoaders2_; + std::map<std::string, boost::shared_ptr<DicomStructureSet2> > + dicomStructureSets2_; + std::map<std::string, boost::shared_ptr<DicomStructureSetSlicer2> > + dicomStructureSetSlicers2_; +#endif + //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/LoaderStateMachine.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,198 @@ +/** + * 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 "LoaderStateMachine.h" + +#include <Core/OrthancException.h> + +namespace Deprecated +{ + void LoaderStateMachine::State::Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LoaderStateMachine::State::Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LoaderStateMachine::State::Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LoaderStateMachine::Schedule(OrthancStone::OracleCommandBase* command) + { + LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()"; + + std::auto_ptr<OrthancStone::OracleCommandBase> protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (!command->HasPayload()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "The payload must contain the next state"); + } + pendingCommands_.push_back(protection.release()); + + Step(); + } + + + void LoaderStateMachine::Start() + { + LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Start()"; + + if (active_) + { + LOG(TRACE) << "LoaderStateMachine::Start() called while active_ is true"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + for (size_t i = 0; i < simultaneousDownloads_; i++) + { + Step(); + } + } + + + void LoaderStateMachine::Step() + { + if (!pendingCommands_.empty() && + activeCommands_ < simultaneousDownloads_) + { + + OrthancStone::IOracleCommand* nextCommand = pendingCommands_.front(); + + LOG(TRACE) << " LoaderStateMachine(" << std::hex << this << std::dec << + ")::Step(): activeCommands_ (" << activeCommands_ << + ") < simultaneousDownloads_ (" << simultaneousDownloads_ << + ") --> will Schedule command addr " << std::hex << nextCommand << std::dec; + + boost::shared_ptr<IObserver> observer(GetSharedObserver()); + oracle_.Schedule(observer, nextCommand); + pendingCommands_.pop_front(); + + activeCommands_++; + } + else + { + LOG(TRACE) << " LoaderStateMachine(" << std::hex << this << std::dec << + ")::Step(): activeCommands_ (" << activeCommands_ << + ") >= simultaneousDownloads_ (" << simultaneousDownloads_ << + ") --> will NOT Schedule command"; + } + } + + + void LoaderStateMachine::Clear() + { + LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Clear()"; + for (PendingCommands::iterator it = pendingCommands_.begin(); + it != pendingCommands_.end(); ++it) + { + delete *it; + } + + pendingCommands_.clear(); + } + + + void LoaderStateMachine::HandleExceptionMessage(const OrthancStone::OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; + LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << + message.GetException().GetDetails(); + Clear(); + } + + template <typename T> + void LoaderStateMachine::HandleSuccessMessage(const T& message) + { + if (activeCommands_ <= 0) { + LOG(ERROR) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage : activeCommands_ should be > 0 but is: " << activeCommands_; + } + else { + activeCommands_--; + try + { + dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message); + Step(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Error in the state machine, stopping all processing: " << + e.What() << " Details: " << e.GetDetails(); + Clear(); + } + } + } + + + LoaderStateMachine::LoaderStateMachine(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + oracle_(oracle), + active_(false), + simultaneousDownloads_(4), + activeCommands_(0) + { + LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()"; + + // TODO => Move this out of constructor + Register<OrthancStone::OrthancRestApiCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); + Register<OrthancStone::GetOrthancImageCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); + Register<OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); + Register<OrthancStone::OracleCommandExceptionMessage>(oracleObservable, &LoaderStateMachine::HandleExceptionMessage); + } + + LoaderStateMachine::~LoaderStateMachine() + { + LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()"; + Clear(); + } + + void LoaderStateMachine::SetSimultaneousDownloads(unsigned int count) + { + if (active_) + { + LOG(ERROR) << "LoaderStateMachine::SetSimultaneousDownloads called while active_ is true"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (count == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + simultaneousDownloads_ = count; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/LoaderStateMachine.h Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,116 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include "../../Messages/IObservable.h" +#include "../../Messages/ObserverBase.h" +#include "../../Oracle/GetOrthancImageCommand.h" +#include "../../Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Oracle/IOracle.h" +#include "../../Oracle/OracleCommandExceptionMessage.h" +#include "../../Oracle/OrthancRestApiCommand.h" + +#include <Core/IDynamicObject.h> + +#include <list> + +namespace Deprecated +{ + /** + 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 + OrtancMultiframeVolumeLoader class. + */ + class LoaderStateMachine : public OrthancStone::ObserverBase<LoaderStateMachine> + { + protected: + class State : public Orthanc::IDynamicObject + { + private: + LoaderStateMachine& that_; + + public: + State(LoaderStateMachine& that) : + that_(that) + { + } + + State(const State& currentState) : + that_(currentState.that_) + { + } + + void Schedule(OrthancStone::OracleCommandBase* command) const + { + that_.Schedule(command); + } + + template <typename T> + T& GetLoader() const + { + return dynamic_cast<T&>(that_); + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message); + + virtual void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message); + + virtual void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message); + }; + + void Schedule(OrthancStone::OracleCommandBase* command); + + void Start(); + + private: + void Step(); + + void Clear(); + + void HandleExceptionMessage(const OrthancStone::OracleCommandExceptionMessage& message); + + template <typename T> + void HandleSuccessMessage(const T& message); + + typedef std::list<OrthancStone::IOracleCommand*> PendingCommands; + + OrthancStone::IOracle& oracle_; + bool active_; + unsigned int simultaneousDownloads_; + PendingCommands pendingCommands_; + unsigned int activeCommands_; + + public: + LoaderStateMachine(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable); + + virtual ~LoaderStateMachine(); + + bool IsActive() const + { + return active_; + } + + void SetSimultaneousDownloads(unsigned int count); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,383 @@ +/** + * 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 "OrthancMultiframeVolumeLoader.h" + +#include <Core/Endianness.h> +#include <Core/Toolbox.h> + +namespace Deprecated +{ + class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::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 OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + // 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); + + GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_); + } + }; + + + static std::string GetSopClassUid(const Orthanc::DicomMap& dicom) + { + std::string s; + if (!dicom.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "DICOM file without SOP class UID"); + } + else + { + return s; + } + } + + + class OrthancMultiframeVolumeLoader::LoadGeometry : public State + { + public: + LoadGeometry(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>(); + + 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 (OrthancStone::StringToSopClassUid(GetSopClassUid(*dicom)) == OrthancStone::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<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); + command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release())); + + Schedule(command.release()); + } + else + { + loader.SetGeometry(*dicom); + } + } + }; + + class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State + { + public: + LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer()); + } + }; + + class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State + { + public: + LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) + { + GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer()); + } + }; + + const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const + { + if (IsActive()) + { + return instanceId_; + } + else + { + LOG(ERROR) << "OrthancMultiframeVolumeLoader::GetInstanceId(): (!IsActive())"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads() + { + if (transferSyntaxUid_.empty() || + !volume_->HasGeometry()) + { + return; + } + /* + 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM + 1.2.840.10008.1.2.1 Explicit VR Little Endian + 1.2.840.10008.1.2.2 Explicit VR Big Endian + + See https://www.dicomlibrary.com/dicom/transfer-syntax/ + */ + 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<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId_ + "/content/" + + Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); + command->AcquirePayload(new LoadUncompressedPixelData(*this)); + Schedule(command.release()); + } + else + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_NotImplemented, + "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_); + } + } + + void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax) + { + transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax); + ScheduleFrameDownloads(); + } + + void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom) + { + OrthancStone::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 OrthancStone::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(); + + { + OrthancStone::VolumeImageGeometry geometry; + geometry.SetSizeInVoxels(width, height, depth); + geometry.SetAxialGeometry(parameters.GetGeometry()); + geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacingZ); + volume_->Initialize(geometry, format, true /* Do compute range */); + } + + volume_->GetPixelData().Clear(); + + ScheduleFrameDownloads(); + + + + BroadcastMessage(OrthancStone::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)); + } + + ORTHANC_FORCE_INLINE + static void CopyPixel(uint16_t& target, const void* source) + { + // TODO - check alignement? + target = le16toh(*reinterpret_cast<const uint16_t*>(source)); + } + + ORTHANC_FORCE_INLINE + static void CopyPixel(int16_t& target, const void* source) + { + // byte swapping is the same for unsigned and signed integers + // (the sign bit is always stored with the MSByte) + uint16_t* targetUp = reinterpret_cast<uint16_t*>(&target); + CopyPixel(*targetUp, source); + } + + template <typename T> + void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData) + { + OrthancStone::ImageBuffer3D& target = volume_->GetPixelData(); + + 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++) + { + OrthancStone::ImageBuffer3D::SliceWriter writer(target, OrthancStone::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(target.GetFormat())); + + T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + CopyPixel(*target, source); + target ++; + source += bpp; + } + } + } + } + + void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData) + { + switch (volume_->GetPixelData().GetFormat()) + { + case Orthanc::PixelFormat_Grayscale32: + CopyPixelData<uint32_t>(pixelData); + break; + case Orthanc::PixelFormat_Grayscale16: + CopyPixelData<uint16_t>(pixelData); + break; + case Orthanc::PixelFormat_SignedGrayscale16: + CopyPixelData<int16_t>(pixelData); + break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + volume_->IncrementRevision(); + + pixelDataLoaded_ = true; + BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_)); + } + + bool OrthancMultiframeVolumeLoader::HasGeometry() const + { + return volume_->HasGeometry(); + } + + const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const + { + return volume_->GetGeometry(); + } + + OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume, + OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + LoaderStateMachine(oracle, oracleObservable), + volume_(volume), + pixelDataLoaded_(false) + { + if (volume.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader() + { + LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()"; + } + + void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId) + { + Start(); + + instanceId_ = instanceId; + + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId + "/tags"); + command->AcquirePayload(new LoadGeometry(*this)); + Schedule(command.release()); + } + + { + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); + command->AcquirePayload(new LoadTransferSyntax(*this)); + Schedule(command.release()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.h Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,76 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include "LoaderStateMachine.h" +#include "../../Volumes/DicomVolumeImage.h" + +#include <boost/shared_ptr.hpp> + +namespace Deprecated +{ + class OrthancMultiframeVolumeLoader : + public LoaderStateMachine, + public OrthancStone::IObservable + { + private: + class LoadRTDoseGeometry; + class LoadGeometry; + class LoadTransferSyntax; + class LoadUncompressedPixelData; + + boost::shared_ptr<OrthancStone::DicomVolumeImage> volume_; + std::string instanceId_; + std::string transferSyntaxUid_; + bool pixelDataLoaded_; + + const std::string& GetInstanceId() const; + + void ScheduleFrameDownloads(); + + void SetTransferSyntax(const std::string& transferSyntax); + + void SetGeometry(const Orthanc::DicomMap& dicom); + + template <typename T> + void CopyPixelData(const std::string& pixelData); + + void SetUncompressedPixelData(const std::string& pixelData); + + bool HasGeometry() const; + const OrthancStone::VolumeImageGeometry& GetImageGeometry() const; + + public: + OrthancMultiframeVolumeLoader(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume, + OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable); + + virtual ~OrthancMultiframeVolumeLoader(); + + bool IsPixelDataLoaded() const + { + return pixelDataLoaded_; + } + + void LoadInstance(const std::string& instanceId); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,513 @@ +/** + * 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 "OrthancSeriesVolumeProgressiveLoader.h" + +#include "../../Loaders/BasicFetchingItemsSorter.h" +#include "../../Loaders/BasicFetchingStrategy.h" +#include "../../Toolbox/GeometryToolbox.h" +#include "../../Volumes/DicomVolumeImageMPRSlicer.h" + +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public OrthancStone::DicomVolumeImageMPRSlicer::Slice + { + private: + const OrthancSeriesVolumeProgressiveLoader& that_; + + public: + ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that, + const OrthancStone::CoordinateSystem3D& plane) : + OrthancStone::DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), + that_(that) + { + if (IsValid()) + { + if (GetProjection() == OrthancStone::VolumeProjection_Axial) + { + // 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 + SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex())); + } + + if (that_.strategy_.get() != NULL && + GetProjection() == OrthancStone::VolumeProjection_Axial) + { + that_.strategy_->SetCurrent(GetSliceIndex()); + } + } + } + }; + + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index, + const OrthancStone::DicomInstanceParameters& reference) const + { + const OrthancStone::DicomInstanceParameters& slice = *slices_[index]; + + if (!OrthancStone::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 (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) || + !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "The pixel spacing of the slices change across the volume image"); + } + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::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 OrthancStone::DicomInstanceParameters& reference = *slices_[0]; + + for (size_t i = 1; i < slices_.size(); i++) + { + CheckSlice(i, reference); + } + } + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear() + { + for (size_t i = 0; i < slices_.size(); i++) + { + assert(slices_[i] != NULL); + delete slices_[i]; + } + + slices_.clear(); + slicesRevision_.clear(); + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const + { + if (!HasGeometry()) + { + LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index): (!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()); + } + } + + + // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" + // (called with the slices created in LoadGeometry) + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(OrthancStone::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 OrthancStone::VolumeImageGeometry); + } + else + { + slices_.reserve(slices.GetSlicesCount()); + slicesRevision_.resize(slices.GetSlicesCount(), 0); + + for (size_t i = 0; i < slices.GetSlicesCount(); i++) + { + const OrthancStone::DicomInstanceParameters& slice = + dynamic_cast<const OrthancStone::DicomInstanceParameters&>(slices.GetSlicePayload(i)); + slices_.push_back(new OrthancStone::DicomInstanceParameters(slice)); + } + + CheckVolume(); + + double spacingZ; + + if (slices.ComputeSpacingBetweenSlices(spacingZ)) + { + LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; + + const OrthancStone::DicomInstanceParameters& parameters = *slices_[0]; + + geometry_.reset(new OrthancStone::VolumeImageGeometry); + geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), + parameters.GetImageInformation().GetHeight(), + static_cast<unsigned int>(slices.GetSlicesCount())); + geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); + geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacingZ); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "The origins of the slices of a volume image are not regularly spaced"); + } + } + } + + + const OrthancStone::VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const + { + if (!HasGeometry()) + { + LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry(): (!HasGeometry())"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + assert(slices_.size() == geometry_->GetDepth()); + return *geometry_; + } + } + + + const OrthancStone::DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const + { + CheckSliceIndex(index); + return *slices_[index]; + } + + + uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const + { + CheckSliceIndex(index); + return slicesRevision_[index]; + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index) + { + CheckSliceIndex(index); + slicesRevision_[index] ++; + } + + + static unsigned int GetSliceIndexPayload(const OrthancStone::OracleCommandBase& command) + { + assert(command.HasPayload()); + return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); + } + + + void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload() + { + assert(strategy_.get() != NULL); + + unsigned int sliceIndex, quality; + + if (strategy_->GetNext(sliceIndex, quality)) + { + assert(quality <= BEST_QUALITY); + + const OrthancStone::DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex); + + const std::string& instance = slice.GetOrthancInstanceIdentifier(); + if (instance.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<OrthancStone::OracleCommandBase> command; + + if (quality == BEST_QUALITY) + { + std::auto_ptr<OrthancStone::GetOrthancImageCommand> tmp(new OrthancStone::GetOrthancImageCommand); + // TODO: review the following comment. + // - Commented out by bgo on 2019-07-19 | reason: Alain has seen cases + // where gzipping the uint16 image took 11 sec to produce 5mb. + // The unzipped request was much much faster. + // - Re-enabled on 2019-07-30. Reason: in Web Assembly, the browser + // does not use the Accept-Encoding header and always requests + // compression. Furthermore, NOT + 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<OrthancStone::GetOrthancWebViewerJpegCommand> tmp(new OrthancStone::GetOrthancWebViewerJpegCommand); + // TODO: review the following comment. Commented out by bgo on 2019-07-19 + // (gzip for jpeg seems overkill) + //tmp->SetHttpHeader("Accept-Encoding", "gzip"); + tmp->SetInstance(instance); + tmp->SetQuality((quality == 0 ? 50 : 90)); + tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); + command.reset(tmp.release()); + } + + command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); + + boost::shared_ptr<IObserver> observer(GetSharedObserver()); + oracle_.Schedule(observer, command.release()); + } + else + { + // loading is finished! + volumeImageReadyInHighQuality_ = true; + BroadcastMessage(OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality(*this)); + } + } + +/** + This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags" +*/ + void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancStone::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(); + + OrthancStone::SlicesSorter slices; + + for (size_t i = 0; i < instances.size(); i++) + { + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(body[instances[i]]); + + std::auto_ptr<OrthancStone::DicomInstanceParameters> instance(new OrthancStone::DicomInstanceParameters(dicom)); + instance->SetOrthancInstanceIdentifier(instances[i]); + + // the 3D plane corresponding to the slice + OrthancStone::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 OrthancStone::DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0); + + volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat()); + volume_->SetDicomParameters(parameters); + volume_->GetPixelData().Clear(); + + strategy_.reset(new OrthancStone::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(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_)); + } + + + void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex, + const Orthanc::ImageAccessor& image, + unsigned int quality) + { + assert(sliceIndex < slicesQuality_.size() && + slicesQuality_.size() == volume_->GetPixelData().GetDepth()); + + if (quality >= slicesQuality_[sliceIndex]) + { + { + OrthancStone::ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), OrthancStone::VolumeProjection_Axial, sliceIndex); + Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); + } + + volume_->IncrementRevision(); + seriesGeometry_.IncrementSliceRevision(sliceIndex); + slicesQuality_[sliceIndex] = quality; + + BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_)); + } + + ScheduleNextSliceDownload(); + } + + + void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) + { + SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY); + } + + + void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + unsigned int quality; + + switch (dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>(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); + } + + + OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<OrthancStone::DicomVolumeImage>& volume, + OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + oracle_(oracle), + active_(false), + simultaneousDownloads_(4), + volume_(volume), + sorter_(new OrthancStone::BasicFetchingItemsSorter::Factory), + volumeImageReadyInHighQuality_(false) + { + // TODO => Move this out of constructor + Register<OrthancStone::OrthancRestApiCommand::SuccessMessage> + (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry); + + Register<OrthancStone::GetOrthancImageCommand::SuccessMessage> + (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent); + + Register<OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage> + (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent); + } + + OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader() + { + LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()"; + } + + void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count) + { + if (active_) + { + LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(): (active_)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (count == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + simultaneousDownloads_ = count; + } + } + + + void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId) + { +// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries seriesId=" << seriesId; + if (active_) + { +// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries NOT ACTIVE! --> ERROR"; + LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId): (active_)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + active_ = true; + + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); + command->SetUri("/series/" + seriesId + "/instances-tags"); + +// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule"; + boost::shared_ptr<IObserver> observer(GetSharedObserver()); + oracle_.Schedule(observer, command.release()); +// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule"; + } + } + + + OrthancStone::IVolumeSlicer::IExtractedSlice* + OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) + { + if (volume_->HasGeometry()) + { + return new ExtractedSlice(*this, cuttingPlane); + } + else + { + return new IVolumeSlicer::InvalidSlice; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.h Sun Dec 08 11:45:09 2019 +0100 @@ -0,0 +1,163 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include "../../Loaders/IFetchingItemsSorter.h" +#include "../../Loaders/IFetchingStrategy.h" +#include "../../Messages/IObservable.h" +#include "../../Messages/ObserverBase.h" +#include "../../Oracle/GetOrthancImageCommand.h" +#include "../../Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Oracle/IOracle.h" +#include "../../Oracle/OrthancRestApiCommand.h" +#include "../../Toolbox/SlicesSorter.h" +#include "../../Volumes/DicomVolumeImage.h" +#include "../../Volumes/IVolumeSlicer.h" + +#include <boost/shared_ptr.hpp> + +namespace Deprecated +{ + /** + This class is used to manage the progressive loading of a volume that + is stored in a Dicom series. + */ + class OrthancSeriesVolumeProgressiveLoader : + public OrthancStone::ObserverBase<OrthancSeriesVolumeProgressiveLoader>, + public OrthancStone::IObservable, + public OrthancStone::IVolumeSlicer + { + private: + static const unsigned int LOW_QUALITY = 0; + static const unsigned int MIDDLE_QUALITY = 1; + static const unsigned int BEST_QUALITY = 2; + + class ExtractedSlice; + + /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */ + class SeriesGeometry : public boost::noncopyable + { + private: + void CheckSlice(size_t index, + const OrthancStone::DicomInstanceParameters& reference) const; + + void CheckVolume() const; + + void Clear(); + + void CheckSliceIndex(size_t index) const; + + std::auto_ptr<OrthancStone::VolumeImageGeometry> geometry_; + std::vector<OrthancStone::DicomInstanceParameters*> slices_; + std::vector<uint64_t> slicesRevision_; + + public: + ~SeriesGeometry() + { + Clear(); + } + + void ComputeGeometry(OrthancStone::SlicesSorter& slices); + + virtual bool HasGeometry() const + { + return geometry_.get() != NULL; + } + + virtual const OrthancStone::VolumeImageGeometry& GetImageGeometry() const; + + const OrthancStone::DicomInstanceParameters& GetSliceParameters(size_t index) const; + + uint64_t GetSliceRevision(size_t index) const; + + void IncrementSliceRevision(size_t index); + }; + + void ScheduleNextSliceDownload(); + + void LoadGeometry(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message); + + void SetSliceContent(unsigned int sliceIndex, + const Orthanc::ImageAccessor& image, + unsigned int quality); + + void LoadBestQualitySliceContent(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message); + + void LoadJpegSliceContent(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message); + + OrthancStone::IOracle& oracle_; + bool active_; + unsigned int simultaneousDownloads_; + SeriesGeometry seriesGeometry_; + boost::shared_ptr<OrthancStone::DicomVolumeImage> volume_; + std::auto_ptr<OrthancStone::IFetchingItemsSorter::IFactory> sorter_; + std::auto_ptr<OrthancStone::IFetchingStrategy> strategy_; + std::vector<unsigned int> slicesQuality_; + bool volumeImageReadyInHighQuality_; + + + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeImageReadyInHighQuality, OrthancSeriesVolumeProgressiveLoader); + + + OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<OrthancStone::DicomVolumeImage>& volume, + OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable); + + virtual ~OrthancSeriesVolumeProgressiveLoader(); + + void SetSimultaneousDownloads(unsigned int count); + + bool IsVolumeImageReadyInHighQuality() const + { + return volumeImageReadyInHighQuality_; + } + + void LoadSeries(const std::string& seriesId); + + /** + This getter is used by clients that do not receive the geometry through + subscribing, for instance if they are created or listening only AFTER the + "geometry loaded" message is broadcast + */ + bool HasGeometry() const + { + return seriesGeometry_.HasGeometry(); + } + + /** + Same remark as HasGeometry + */ + const OrthancStone::VolumeImageGeometry& GetImageGeometry() const + { + return seriesGeometry_.GetImageGeometry(); + } + + /** + 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 OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + }; +}
--- a/Framework/Loaders/DicomStructureSetLoader.cpp Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,416 +0,0 @@ -/** - * 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 "DicomStructureSetLoader.h" - -#include "../Scene2D/PolylineSceneLayer.h" -#include "../StoneException.h" -#include "../Toolbox/GeometryToolbox.h" - -#include <Core/Toolbox.h> - -#include <algorithm> - -#if 0 -bool logbgo233 = false; -bool logbgo115 = false; -#endif - -namespace OrthancStone -{ - -#if 0 - void DumpDicomMap(std::ostream& o, const Orthanc::DicomMap& dicomMap) - { - using namespace std; - //ios_base::fmtflags state = o.flags(); - //o.flags(ios::right | ios::hex); - //o << "(" << setfill('0') << setw(4) << tag.GetGroup() - // << "," << setw(4) << tag.GetElement() << ")"; - //o.flags(state); - Json::Value val; - dicomMap.Serialize(val); - o << val; - //return o; - } -#endif - - - class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State - { - private: - std::string instanceId_; - - public: - AddReferencedInstance(DicomStructureSetLoader& that, - const std::string& instanceId) : - State(that), - instanceId_(instanceId) - { - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) - { - Json::Value tags; - message.ParseJsonBody(tags); - - Orthanc::DicomMap dicom; - dicom.FromDicomAsJson(tags); - - DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); - - loader.content_->AddReferencedSlice(dicom); - - loader.countProcessedInstances_ ++; - assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_); - - if (loader.countProcessedInstances_ == loader.countReferencedInstances_) - { - // All the referenced instances have been loaded, finalize the RT-STRUCT - loader.content_->CheckReferencedSlices(); - loader.revision_++; - loader.SetStructuresReady(); - } - } - }; - - - // State that converts a "SOP Instance UID" to an Orthanc identifier - class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State - { - private: - std::string sopInstanceUid_; - - public: - LookupInstance(DicomStructureSetLoader& that, - const std::string& sopInstanceUid) : - State(that), - sopInstanceUid_(sopInstanceUid) - { - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) - { -#if 0 - LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)"; -#endif - DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); - - Json::Value lookup; - message.ParseJsonBody(lookup); - - if (lookup.type() != Json::arrayValue || - lookup.size() != 1 || - !lookup[0].isMember("Type") || - !lookup[0].isMember("Path") || - lookup[0]["Type"].type() != Json::stringValue || - lookup[0]["ID"].type() != Json::stringValue || - lookup[0]["Type"].asString() != "Instance") - { - std::stringstream msg; - msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = "; - for (OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin(); - it != message.GetAnswerHeaders().end(); ++it) - { - msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n"; - } - const std::string msgStr = msg.str(); - LOG(ERROR) << msgStr; - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - - const std::string instanceId = lookup[0]["ID"].asString(); - - { - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - std::string uri = "/instances/" + instanceId + "/tags"; - command->SetUri(uri); - command->AcquirePayload(new AddReferencedInstance(loader, instanceId)); - Schedule(command.release()); - } - } - }; - - - class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State - { - public: - LoadStructure(DicomStructureSetLoader& that) : - State(that) - { - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) - { -#if 0 - if (logbgo115) - LOG(TRACE) << "DicomStructureSetLoader::LoadStructure::Handle() (SUCCESS)"; -#endif - DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); - - { - OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); - loader.content_.reset(new DicomStructureSet(dicom)); - size_t structureCount = loader.content_->GetStructuresCount(); - loader.structureVisibility_.resize(structureCount); - bool everythingVisible = false; - if ((loader.initiallyVisibleStructures_.size() == 1) - && (loader.initiallyVisibleStructures_[0].size() == 1) - && (loader.initiallyVisibleStructures_[0][0] == '*')) - { - everythingVisible = true; - } - - for (size_t i = 0; i < structureCount; ++i) - { - // if a single "*" string is supplied, this means we want everything - // to be visible... - if(everythingVisible) - { - loader.structureVisibility_.at(i) = true; - } - else - { - // otherwise, we only enable visibility for those structures whose - // names are mentioned in the initiallyVisibleStructures_ array - const std::string& structureName = loader.content_->GetStructureName(i); - - std::vector<std::string>::iterator foundIt = - std::find( - loader.initiallyVisibleStructures_.begin(), - loader.initiallyVisibleStructures_.end(), - structureName); - std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end(); - if (foundIt != endIt) - loader.structureVisibility_.at(i) = true; - else - loader.structureVisibility_.at(i) = false; - } - } - } - - // Some (admittedly invalid) Dicom files have empty values in the - // 0008,1155 tag. We try our best to cope with this. - std::set<std::string> instances; - std::set<std::string> nonEmptyInstances; - loader.content_->GetReferencedInstances(instances); - for (std::set<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) - { - std::string instance = Orthanc::Toolbox::StripSpaces(*it); - if(instance != "") - nonEmptyInstances.insert(instance); - } - - loader.countReferencedInstances_ = - static_cast<unsigned int>(nonEmptyInstances.size()); - - for (std::set<std::string>::const_iterator - it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it) - { - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetUri("/tools/lookup"); - command->SetMethod(Orthanc::HttpMethod_Post); - command->SetBody(*it); - command->AcquirePayload(new LookupInstance(loader, *it)); - Schedule(command.release()); - } - } - }; - - - class DicomStructureSetLoader::Slice : public IExtractedSlice - { - private: - const DicomStructureSet& content_; - uint64_t revision_; - bool isValid_; - std::vector<bool> visibility_; - - public: - /** - The visibility vector must either: - - be empty - or - - contain the same number of items as the number of structures in the - structure set. - In the first case (empty vector), all the structures are displayed. - In the second case, the visibility of each structure is defined by the - content of the vector at the corresponding index. - */ - Slice(const DicomStructureSet& content, - uint64_t revision, - const CoordinateSystem3D& cuttingPlane, - std::vector<bool> visibility = std::vector<bool>()) - : content_(content) - , revision_(revision) - , visibility_(visibility) - { - ORTHANC_ASSERT((visibility_.size() == content_.GetStructuresCount()) - || (visibility_.size() == 0u)); - - bool opposite; - - const Vector normal = content.GetNormal(); - isValid_ = ( - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); - } - - virtual bool IsValid() - { - return isValid_; - } - - virtual uint64_t GetRevision() - { - return revision_; - } - - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) - { - assert(isValid_); - - std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer); - layer->SetThickness(2); - - for (size_t i = 0; i < content_.GetStructuresCount(); i++) - { - if ((visibility_.size() == 0) || visibility_.at(i)) - { - const Color& color = content_.GetStructureColor(i); - -#ifdef USE_BOOST_UNION_FOR_POLYGONS - std::vector< std::vector<Point2D> > polygons; - - if (content_.ProjectStructure(polygons, i, cuttingPlane)) - { - for (size_t j = 0; j < polygons.size(); j++) - { - PolylineSceneLayer::Chain chain; - chain.resize(polygons[j].size()); - - for (size_t k = 0; k < polygons[j].size(); k++) - { - chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y); - } - - layer->AddChain(chain, true /* closed */, color); - } - } -#else - std::vector< std::pair<Point2D, Point2D> > segments; - - if (content_.ProjectStructure(segments, i, cuttingPlane)) - { - for (size_t j = 0; j < segments.size(); j++) - { - PolylineSceneLayer::Chain chain; - chain.resize(2); - - chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y); - chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y); - - layer->AddChain(chain, false /* NOT closed */, color); - } - } -#endif - } - } - - return layer.release(); - } - }; - - - DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle, - IObservable& oracleObservable) : - LoaderStateMachine(oracle, oracleObservable), - revision_(0), - countProcessedInstances_(0), - countReferencedInstances_(0), - structuresReady_(false) - { - } - - - void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display) - { - structureVisibility_.at(structureIndex) = display; - revision_++; - } - - DicomStructureSetLoader::~DicomStructureSetLoader() - { - LOG(TRACE) << "DicomStructureSetLoader::~DicomStructureSetLoader()"; - } - - void DicomStructureSetLoader::LoadInstance( - const std::string& instanceId, - const std::vector<std::string>& initiallyVisibleStructures) - { - Start(); - - instanceId_ = instanceId; - initiallyVisibleStructures_ = initiallyVisibleStructures; - - { - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - - std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; - - command->SetUri(uri); - command->AcquirePayload(new LoadStructure(*this)); - Schedule(command.release()); - } - } - - - IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane) - { - if (content_.get() == NULL) - { - // Geometry is not available yet - return new IVolumeSlicer::InvalidSlice; - } - else - { - return new Slice(*content_, revision_, cuttingPlane, structureVisibility_); - } - } - - void DicomStructureSetLoader::SetStructuresReady() - { - ORTHANC_ASSERT(!structuresReady_); - structuresReady_ = true; - BroadcastMessage(DicomStructureSetLoader::StructuresReady(*this)); - } - - bool DicomStructureSetLoader::AreStructuresReady() const - { - return structuresReady_; - } - -}
--- a/Framework/Loaders/DicomStructureSetLoader.h Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include "../Toolbox/DicomStructureSet.h" -#include "../Volumes/IVolumeSlicer.h" -#include "LoaderStateMachine.h" - -#include <vector> - -namespace OrthancStone -{ - class DicomStructureSetLoader : - public LoaderStateMachine, - public IVolumeSlicer, - public IObservable - { - private: - class Slice; - - // States of LoaderStateMachine - class AddReferencedInstance; // 3rd state - class LookupInstance; // 2nd state - class LoadStructure; // 1st state - - std::auto_ptr<DicomStructureSet> content_; - uint64_t revision_; - std::string instanceId_; - unsigned int countProcessedInstances_; - unsigned int countReferencedInstances_; - - // will be set to true once the loading is finished - bool structuresReady_; - - /** - At load time, these strings are used to initialize the structureVisibility_ - vector. - - As a special case, if initiallyVisibleStructures_ contains a single string - that is '*', ALL structures will be made visible. - */ - std::vector<std::string> initiallyVisibleStructures_; - - /** - Contains the "Should this structure be displayed?" flag for all structures. - Only filled when structures are loaded. - - Changing this value directly affects the rendering - */ - std::vector<bool> structureVisibility_; - - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader); - - DicomStructureSetLoader(IOracle& oracle, - IObservable& oracleObservable); - - DicomStructureSet* GetContent() - { - return content_.get(); - } - - void SetStructureDisplayState(size_t structureIndex, bool display); - - bool GetStructureDisplayState(size_t structureIndex) const - { - return structureVisibility_.at(structureIndex); - } - - ~DicomStructureSetLoader(); - - void LoadInstance(const std::string& instanceId, - const std::vector<std::string>& initiallyVisibleStructures = std::vector<std::string>()); - - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; - - void SetStructuresReady(); - - bool AreStructuresReady() const; - }; -}
--- a/Framework/Loaders/DicomStructureSetLoader2.cpp Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/** - * 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/>. - **/ - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructureSetLoader2.h" - -#include "../Messages/IObservable.h" -#include "../Oracle/IOracle.h" -#include "../Oracle/OracleCommandExceptionMessage.h" - -namespace OrthancStone -{ - - DicomStructureSetLoader2::DicomStructureSetLoader2( - DicomStructureSet2& structureSet - , IOracle& oracle - , IObservable& oracleObservable) - : IObserver(oracleObservable.GetBroker()) - , IObservable(oracleObservable.GetBroker()) - , structureSet_(structureSet) - , oracle_(oracle) - , oracleObservable_(oracleObservable) - , structuresReady_(false) - { - LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()"; - - oracleObservable.RegisterObserverCallback( - new Callable<DicomStructureSetLoader2, OrthancRestApiCommand::SuccessMessage> - (*this, &DicomStructureSetLoader2::HandleSuccessMessage)); - - oracleObservable.RegisterObserverCallback( - new Callable<DicomStructureSetLoader2, OracleCommandExceptionMessage> - (*this, &DicomStructureSetLoader2::HandleExceptionMessage)); - } - - DicomStructureSetLoader2::~DicomStructureSetLoader2() - { - LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()"; - oracleObservable_.Unregister(this); - } - - void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body) - { - OrthancPlugins::FullOrthancDataset dicom(body); - //loader.content_.reset(new DicomStructureSet(dicom)); - structureSet_.Clear(); - structureSet_.SetContents(dicom); - SetStructuresReady(); - } - - void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message) - { - const std::string& body = message.GetAnswer(); - LoadInstanceFromString(body); - } - - void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message) - { - LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. " - << "Error: " << message.GetException().What() << " Details: " - << message.GetException().GetDetails(); - } - - void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId) - { - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - - std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; - - command->SetUri(uri); - oracle_.Schedule(*this, command.release()); - } - - void DicomStructureSetLoader2::SetStructuresReady() - { - structuresReady_ = true; - } - - bool DicomStructureSetLoader2::AreStructuresReady() const - { - return structuresReady_; - } - - /* - - void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) - { - LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; - LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << - message.GetException().GetDetails(); - Clear(); - } - - LoaderStateMachine::~LoaderStateMachine() - { - Clear(); - } - - - */ - -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -
--- a/Framework/Loaders/DicomStructureSetLoader2.h Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * 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/>. - **/ - -#pragma once - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "../Toolbox/DicomStructureSet2.h" -#include "../Messages/IMessage.h" -#include "../Messages/IObserver.h" -#include "../Messages/IObservable.h" -#include "../Oracle/OrthancRestApiCommand.h" - -#include <boost/noncopyable.hpp> - -namespace OrthancStone -{ - class IOracle; - class IObservable; - class OrthancRestApiCommand; - class OracleCommandExceptionMessage; - - class DicomStructureSetLoader2 : public IObserver, public IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2); - - /** - Warning: the structureSet, oracle and oracleObservable objects must live - at least as long as this object (TODO: shared_ptr?) - */ - DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable); - - ~DicomStructureSetLoader2(); - - void LoadInstance(const std::string& instanceId); - - /** Internal use */ - void LoadInstanceFromString(const std::string& body); - - void SetStructuresReady(); - bool AreStructuresReady() const; - - private: - /** - Called back by the oracle when data is ready! - */ - void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message); - - /** - Called back by the oracle when shit hits the fan - */ - void HandleExceptionMessage(const OracleCommandExceptionMessage& message); - - /** - The structure set that will be (cleared and) filled with data from the - loader - */ - DicomStructureSet2& structureSet_; - - IOracle& oracle_; - IObservable& oracleObservable_; - bool structuresReady_; - }; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -
--- a/Framework/Loaders/LoaderCache.cpp Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,413 +0,0 @@ -/** - * 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 "LoaderCache.h" - -#include "../StoneException.h" -#include "OrthancSeriesVolumeProgressiveLoader.h" -#include "OrthancMultiframeVolumeLoader.h" -#include "DicomStructureSetLoader.h" - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -#include "DicomStructureSetLoader2.h" -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - -#if ORTHANC_ENABLE_WASM == 1 -# include <unistd.h> -# include "../Oracle/WebAssemblyOracle.h" -#else -# include "../Oracle/ThreadedOracle.h" -#endif - -#include "../Messages/LockingEmitter.h" - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -#include "../Toolbox/DicomStructureSet2.h" -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "../Volumes/DicomVolumeImage.h" -#include "../Volumes/DicomVolumeImageMPRSlicer.h" - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -#include "../Volumes/DicomStructureSetSlicer2.h" -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include <Core/OrthancException.h> -#include <Core/Toolbox.h> - -namespace OrthancStone -{ -#if ORTHANC_ENABLE_WASM == 1 - LoaderCache::LoaderCache(WebAssemblyOracle& oracle) - : oracle_(oracle) - { - - } -#else - LoaderCache::LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter) - : oracle_(oracle) - , lockingEmitter_(lockingEmitter) - { - } -#endif - - boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> - LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid) - { - try - { - - // normalize keys a little - seriesUuid = Orthanc::Toolbox::StripSpaces(seriesUuid); - Orthanc::Toolbox::ToLowerCase(seriesUuid); - - // find in cache - if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end()) - { -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : CACHEMISS --> need to load seriesUUid = " << seriesUuid; -#if ORTHANC_ENABLE_WASM == 1 -// LOG(TRACE) << "Performing request for series " << seriesUuid << " sbrk(0) = " << sbrk(0); -#else -// LOG(TRACE) << "Performing request for series " << seriesUuid; -#endif - boost::shared_ptr<DicomVolumeImage> volumeImage(new DicomVolumeImage); - boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader; -// LOG(TRACE) << "volumeImage = " << volumeImage.get(); - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_)); -#else - Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); - loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable())); -#endif -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get(); - loader->LoadSeries(seriesUuid); -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader->LoadSeries successful"; - } - seriesVolumeProgressiveLoaders_[seriesUuid] = loader; - } - else - { -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid; - } - return seriesVolumeProgressiveLoaders_[seriesUuid]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in LoaderCache"; - throw; - } - } - - boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid) - { - // if the loader is not available, let's trigger its creation - if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end()) - { - GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid); - } - ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end()); - - return multiframeVolumeLoaders_[instanceUuid]; - } - - boost::shared_ptr<DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid) - { - try - { - // normalize keys a little - instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid); - Orthanc::Toolbox::ToLowerCase(instanceUuid); - - // find in cache - if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end()) - { - boost::shared_ptr<DicomVolumeImage> volumeImage(new DicomVolumeImage); - boost::shared_ptr<OrthancMultiframeVolumeLoader> loader; - - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, oracle_)); -#else - Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); - loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, lock.GetOracleObservable())); -#endif - loader->LoadInstance(instanceUuid); - } - multiframeVolumeLoaders_[instanceUuid] = loader; - boost::shared_ptr<DicomVolumeImageMPRSlicer> mprSlicer(new DicomVolumeImageMPRSlicer(volumeImage)); - dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer; - } - return dicomVolumeImageMPRSlicers_[instanceUuid]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in LoaderCache"; - throw; - } - } - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - boost::shared_ptr<DicomStructureSetSlicer2> LoaderCache::GetDicomStructureSetSlicer2(std::string instanceUuid) - { - // if the loader is not available, let's trigger its creation - if (dicomStructureSetSlicers2_.find(instanceUuid) == dicomStructureSetSlicers2_.end()) - { - GetDicomStructureSetLoader2(instanceUuid); - } - ORTHANC_ASSERT(dicomStructureSetSlicers2_.find(instanceUuid) != dicomStructureSetSlicers2_.end()); - - return dicomStructureSetSlicers2_[instanceUuid]; - } -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - - /** - This method allows to convert a list of string into a string by - sorting the strings then joining them - */ - static std::string SortAndJoin(const std::vector<std::string>& stringList) - { - if (stringList.size() == 0) - { - return ""; - } - else - { - std::vector<std::string> sortedStringList = stringList; - std::sort(sortedStringList.begin(), sortedStringList.end()); - std::stringstream s; - s << sortedStringList[0]; - for (size_t i = 1; i < sortedStringList.size(); ++i) - { - s << "-" << sortedStringList[i]; - } - return s.str(); - } - } - - boost::shared_ptr<DicomStructureSetLoader> - LoaderCache::GetDicomStructureSetLoader( - std::string inInstanceUuid, - const std::vector<std::string>& initiallyVisibleStructures) - { - try - { - // normalize keys a little - inInstanceUuid = Orthanc::Toolbox::StripSpaces(inInstanceUuid); - Orthanc::Toolbox::ToLowerCase(inInstanceUuid); - - std::string initiallyVisibleStructuresKey = - SortAndJoin(initiallyVisibleStructures); - - std::string entryKey = inInstanceUuid + "_" + initiallyVisibleStructuresKey; - - // find in cache - if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end()) - { - boost::shared_ptr<DicomStructureSetLoader> loader; - - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new DicomStructureSetLoader(oracle_, oracle_)); -#else - Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); - loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable())); -#endif - loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures); - } - dicomStructureSetLoaders_[entryKey] = loader; - } - return dicomStructureSetLoaders_[entryKey]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in LoaderCache"; - throw; - } - } - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - boost::shared_ptr<DicomStructureSetLoader2> LoaderCache::GetDicomStructureSetLoader2(std::string instanceUuid) - { - try - { - // normalize keys a little - instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid); - Orthanc::Toolbox::ToLowerCase(instanceUuid); - - // find in cache - if (dicomStructureSetLoaders2_.find(instanceUuid) == dicomStructureSetLoaders2_.end()) - { - boost::shared_ptr<DicomStructureSetLoader2> loader; - boost::shared_ptr<DicomStructureSet2> structureSet(new DicomStructureSet2()); - boost::shared_ptr<DicomStructureSetSlicer2> rtSlicer(new DicomStructureSetSlicer2(structureSet)); - dicomStructureSetSlicers2_[instanceUuid] = rtSlicer; - dicomStructureSets2_[instanceUuid] = structureSet; // to prevent it from being deleted - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, oracle_)); -#else - LockingEmitter::WriterLock lock(lockingEmitter_); - // TODO: clarify lifetimes... this is DANGEROUS! - loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, lock.GetOracleObservable())); -#endif - loader->LoadInstance(instanceUuid); - } - dicomStructureSetLoaders2_[instanceUuid] = loader; - } - return dicomStructureSetLoaders2_[instanceUuid]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in GetDicomStructureSetLoader2: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in GetDicomStructureSetLoader2"; - throw; - } - } - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - - void LoaderCache::ClearCache() - { -#if ORTHANC_ENABLE_WASM != 1 - Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); -#endif - -//#ifndef NDEBUG - // ISO way of checking for debug builds - DebugDisplayObjRefCounts(); -//#endif - seriesVolumeProgressiveLoaders_.clear(); - multiframeVolumeLoaders_.clear(); - dicomVolumeImageMPRSlicers_.clear(); - dicomStructureSetLoaders_.clear(); - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - // order is important! - dicomStructureSetLoaders2_.clear(); - dicomStructureSetSlicers2_.clear(); - dicomStructureSets2_.clear(); -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - } - - template<typename T> void DebugDisplayObjRefCountsInMap( - const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap) - { - LOG(TRACE) << "Map \"" << name << "\" ref counts:"; - size_t i = 0; - for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator - it = myMap.begin(); it != myMap.end(); ++it) - { - LOG(TRACE) << " element #" << i << ": ref count = " << it->second.use_count(); - i++; - } - } - - void LoaderCache::DebugDisplayObjRefCounts() - { - DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_); - DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_); - DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_); - DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_); -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders2_", dicomStructureSetLoaders2_); - DebugDisplayObjRefCountsInMap("dicomStructureSetSlicers2_", dicomStructureSetSlicers2_); -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - } -}
--- a/Framework/Loaders/LoaderCache.h Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * 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/>. - **/ - -#pragma once - -#include <boost/shared_ptr.hpp> - -#include <map> -#include <string> -#include <vector> - -namespace OrthancStone -{ - class OrthancSeriesVolumeProgressiveLoader; - class DicomVolumeImageMPRSlicer; - class DicomStructureSetLoader; -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - class DicomStructureSetLoader2; - class DicomStructureSetSlicer2; - class DicomStructureSet2; -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - class OrthancMultiframeVolumeLoader; - -#if ORTHANC_ENABLE_WASM == 1 - class WebAssemblyOracle; -#else - class ThreadedOracle; - namespace Deprecated - { - class LockingEmitter; - } -#endif - - class LoaderCache - { - public: -#if ORTHANC_ENABLE_WASM == 1 - LoaderCache(WebAssemblyOracle& oracle); -#else - LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter); -#endif - - boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> - GetSeriesVolumeProgressiveLoader (std::string seriesUuid); - - boost::shared_ptr<DicomVolumeImageMPRSlicer> - GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid); - - boost::shared_ptr<OrthancMultiframeVolumeLoader> - GetMultiframeVolumeLoader(std::string instanceUuid); - - boost::shared_ptr<DicomStructureSetLoader> - GetDicomStructureSetLoader( - std::string instanceUuid, - const std::vector<std::string>& initiallyVisibleStructures); - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - boost::shared_ptr<DicomStructureSetLoader2> - GetDicomStructureSetLoader2(std::string instanceUuid); - - boost::shared_ptr<DicomStructureSetSlicer2> - GetDicomStructureSetSlicer2(std::string instanceUuid); -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - void ClearCache(); - - private: - - void DebugDisplayObjRefCounts(); -#if ORTHANC_ENABLE_WASM == 1 - WebAssemblyOracle& oracle_; -#else - ThreadedOracle& oracle_; - Deprecated::LockingEmitter& lockingEmitter_; -#endif - - std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> > - seriesVolumeProgressiveLoaders_; - std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> > - multiframeVolumeLoaders_; - std::map<std::string, boost::shared_ptr<DicomVolumeImageMPRSlicer> > - dicomVolumeImageMPRSlicers_; - std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> > - dicomStructureSetLoaders_; -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - std::map<std::string, boost::shared_ptr<DicomStructureSetLoader2> > - dicomStructureSetLoaders2_; - std::map<std::string, boost::shared_ptr<DicomStructureSet2> > - dicomStructureSets2_; - std::map<std::string, boost::shared_ptr<DicomStructureSetSlicer2> > - dicomStructureSetSlicers2_; -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - }; -} -
--- a/Framework/Loaders/LoaderStateMachine.cpp Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,198 +0,0 @@ -/** - * 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 "LoaderStateMachine.h" - -#include <Core/OrthancException.h> - -namespace OrthancStone -{ - void LoaderStateMachine::State::Handle(const OrthancRestApiCommand::SuccessMessage& message) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - - void LoaderStateMachine::State::Handle(const GetOrthancImageCommand::SuccessMessage& message) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - - void LoaderStateMachine::State::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - - void LoaderStateMachine::Schedule(OracleCommandBase* command) - { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()"; - - std::auto_ptr<OracleCommandBase> protection(command); - - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - if (!command->HasPayload()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "The payload must contain the next state"); - } - pendingCommands_.push_back(protection.release()); - - Step(); - } - - - void LoaderStateMachine::Start() - { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Start()"; - - if (active_) - { - LOG(TRACE) << "LoaderStateMachine::Start() called while active_ is true"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - active_ = true; - - for (size_t i = 0; i < simultaneousDownloads_; i++) - { - Step(); - } - } - - - void LoaderStateMachine::Step() - { - if (!pendingCommands_.empty() && - activeCommands_ < simultaneousDownloads_) - { - - IOracleCommand* nextCommand = pendingCommands_.front(); - - LOG(TRACE) << " LoaderStateMachine(" << std::hex << this << std::dec << - ")::Step(): activeCommands_ (" << activeCommands_ << - ") < simultaneousDownloads_ (" << simultaneousDownloads_ << - ") --> will Schedule command addr " << std::hex << nextCommand << std::dec; - - boost::shared_ptr<IObserver> observer(GetSharedObserver()); - oracle_.Schedule(observer, nextCommand); - pendingCommands_.pop_front(); - - activeCommands_++; - } - else - { - LOG(TRACE) << " LoaderStateMachine(" << std::hex << this << std::dec << - ")::Step(): activeCommands_ (" << activeCommands_ << - ") >= simultaneousDownloads_ (" << simultaneousDownloads_ << - ") --> will NOT Schedule command"; - } - } - - - void LoaderStateMachine::Clear() - { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Clear()"; - for (PendingCommands::iterator it = pendingCommands_.begin(); - it != pendingCommands_.end(); ++it) - { - delete *it; - } - - pendingCommands_.clear(); - } - - - void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) - { - LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; - LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << - message.GetException().GetDetails(); - Clear(); - } - - template <typename T> - void LoaderStateMachine::HandleSuccessMessage(const T& message) - { - if (activeCommands_ <= 0) { - LOG(ERROR) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage : activeCommands_ should be > 0 but is: " << activeCommands_; - } - else { - activeCommands_--; - try - { - dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message); - Step(); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Error in the state machine, stopping all processing: " << - e.What() << " Details: " << e.GetDetails(); - Clear(); - } - } - } - - - LoaderStateMachine::LoaderStateMachine(IOracle& oracle, - IObservable& oracleObservable) : - oracle_(oracle), - active_(false), - simultaneousDownloads_(4), - activeCommands_(0) - { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()"; - - // TODO => Move this out of constructor - Register<OrthancRestApiCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); - Register<GetOrthancImageCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); - Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); - Register<OracleCommandExceptionMessage>(oracleObservable, &LoaderStateMachine::HandleExceptionMessage); - } - - LoaderStateMachine::~LoaderStateMachine() - { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()"; - Clear(); - } - - void LoaderStateMachine::SetSimultaneousDownloads(unsigned int count) - { - if (active_) - { - LOG(ERROR) << "LoaderStateMachine::SetSimultaneousDownloads called while active_ is true"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (count == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - simultaneousDownloads_ = count; - } - } -}
--- a/Framework/Loaders/LoaderStateMachine.h Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include "../Messages/IObservable.h" -#include "../Messages/ObserverBase.h" -#include "../Oracle/GetOrthancImageCommand.h" -#include "../Oracle/GetOrthancWebViewerJpegCommand.h" -#include "../Oracle/IOracle.h" -#include "../Oracle/OracleCommandExceptionMessage.h" -#include "../Oracle/OrthancRestApiCommand.h" - -#include <Core/IDynamicObject.h> - -#include <list> - -namespace OrthancStone -{ - /** - 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 - OrtancMultiframeVolumeLoader class. - */ - class LoaderStateMachine : public ObserverBase<LoaderStateMachine> - { - protected: - class State : public Orthanc::IDynamicObject - { - private: - LoaderStateMachine& that_; - - public: - State(LoaderStateMachine& that) : - that_(that) - { - } - - State(const State& currentState) : - that_(currentState.that_) - { - } - - void Schedule(OracleCommandBase* command) const - { - that_.Schedule(command); - } - - template <typename T> - T& GetLoader() const - { - return dynamic_cast<T&>(that_); - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message); - - virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message); - - virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); - }; - - void Schedule(OracleCommandBase* command); - - void Start(); - - private: - void Step(); - - void Clear(); - - void HandleExceptionMessage(const OracleCommandExceptionMessage& message); - - template <typename T> - void HandleSuccessMessage(const T& message); - - typedef std::list<IOracleCommand*> PendingCommands; - - IOracle& oracle_; - bool active_; - unsigned int simultaneousDownloads_; - PendingCommands pendingCommands_; - unsigned int activeCommands_; - - public: - LoaderStateMachine(IOracle& oracle, - IObservable& oracleObservable); - - virtual ~LoaderStateMachine(); - - bool IsActive() const - { - return active_; - } - - void SetSimultaneousDownloads(unsigned int count); - }; -}
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,383 +0,0 @@ -/** - * 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 "OrthancMultiframeVolumeLoader.h" - -#include <Core/Endianness.h> -#include <Core/Toolbox.h> - -namespace OrthancStone -{ - class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::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) - { - // 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); - - GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_); - } - }; - - - static std::string GetSopClassUid(const Orthanc::DicomMap& dicom) - { - std::string s; - if (!dicom.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "DICOM file without SOP class UID"); - } - else - { - return s; - } - } - - - class OrthancMultiframeVolumeLoader::LoadGeometry : public State - { - public: - LoadGeometry(OrthancMultiframeVolumeLoader& that) : - State(that) - { - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) - { - OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>(); - - 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/" + loader.GetInstanceId() + "/content/" + - Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); - command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release())); - - Schedule(command.release()); - } - else - { - loader.SetGeometry(*dicom); - } - } - }; - - class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State - { - public: - LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) : - State(that) - { - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) - { - GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer()); - } - }; - - class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State - { - public: - LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) : - State(that) - { - } - - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) - { - GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer()); - } - }; - - const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const - { - if (IsActive()) - { - return instanceId_; - } - else - { - LOG(ERROR) << "OrthancMultiframeVolumeLoader::GetInstanceId(): (!IsActive())"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads() - { - if (transferSyntaxUid_.empty() || - !volume_->HasGeometry()) - { - return; - } - /* - 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM - 1.2.840.10008.1.2.1 Explicit VR Little Endian - 1.2.840.10008.1.2.2 Explicit VR Big Endian - - See https://www.dicomlibrary.com/dicom/transfer-syntax/ - */ - 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->AcquirePayload(new LoadUncompressedPixelData(*this)); - Schedule(command.release()); - } - else - { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_NotImplemented, - "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_); - } - } - - void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax) - { - transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax); - ScheduleFrameDownloads(); - } - - void OrthancMultiframeVolumeLoader::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.SetSizeInVoxels(width, height, depth); - geometry.SetAxialGeometry(parameters.GetGeometry()); - geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), - parameters.GetPixelSpacingY(), spacingZ); - volume_->Initialize(geometry, format, true /* Do compute range */); - } - - 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)); - } - - ORTHANC_FORCE_INLINE - static void CopyPixel(uint16_t& target, const void* source) - { - // TODO - check alignement? - target = le16toh(*reinterpret_cast<const uint16_t*>(source)); - } - - ORTHANC_FORCE_INLINE - static void CopyPixel(int16_t& target, const void* source) - { - // byte swapping is the same for unsigned and signed integers - // (the sign bit is always stored with the MSByte) - uint16_t* targetUp = reinterpret_cast<uint16_t*>(&target); - CopyPixel(*targetUp, source); - } - - template <typename T> - void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData) - { - ImageBuffer3D& target = volume_->GetPixelData(); - - 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(target.GetFormat())); - - T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - CopyPixel(*target, source); - target ++; - source += bpp; - } - } - } - } - - void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData) - { - switch (volume_->GetPixelData().GetFormat()) - { - case Orthanc::PixelFormat_Grayscale32: - CopyPixelData<uint32_t>(pixelData); - break; - case Orthanc::PixelFormat_Grayscale16: - CopyPixelData<uint16_t>(pixelData); - break; - case Orthanc::PixelFormat_SignedGrayscale16: - CopyPixelData<int16_t>(pixelData); - break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - volume_->IncrementRevision(); - - pixelDataLoaded_ = true; - BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); - } - - bool OrthancMultiframeVolumeLoader::HasGeometry() const - { - return volume_->HasGeometry(); - } - - const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const - { - return volume_->GetGeometry(); - } - - OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume, - IOracle& oracle, - IObservable& oracleObservable) : - LoaderStateMachine(oracle, oracleObservable), - volume_(volume), - pixelDataLoaded_(false) - { - if (volume.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader() - { - LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()"; - } - - void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId) - { - Start(); - - instanceId_ = instanceId; - - { - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - command->SetUri("/instances/" + instanceId + "/tags"); - command->AcquirePayload(new LoadGeometry(*this)); - Schedule(command.release()); - } - - { - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); - command->AcquirePayload(new LoadTransferSyntax(*this)); - Schedule(command.release()); - } - } -}
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include "LoaderStateMachine.h" -#include "../Volumes/DicomVolumeImage.h" - -#include <boost/shared_ptr.hpp> - -namespace OrthancStone -{ - class OrthancMultiframeVolumeLoader : - public LoaderStateMachine, - public IObservable - { - private: - class LoadRTDoseGeometry; - class LoadGeometry; - class LoadTransferSyntax; - class LoadUncompressedPixelData; - - boost::shared_ptr<DicomVolumeImage> volume_; - std::string instanceId_; - std::string transferSyntaxUid_; - bool pixelDataLoaded_; - - const std::string& GetInstanceId() const; - - void ScheduleFrameDownloads(); - - void SetTransferSyntax(const std::string& transferSyntax); - - void SetGeometry(const Orthanc::DicomMap& dicom); - - template <typename T> - void CopyPixelData(const std::string& pixelData); - - void SetUncompressedPixelData(const std::string& pixelData); - - bool HasGeometry() const; - const VolumeImageGeometry& GetImageGeometry() const; - - public: - OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume, - IOracle& oracle, - IObservable& oracleObservable); - - virtual ~OrthancMultiframeVolumeLoader(); - - bool IsPixelDataLoaded() const - { - return pixelDataLoaded_; - } - - void LoadInstance(const std::string& instanceId); - }; -}
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,513 +0,0 @@ -/** - * 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 "OrthancSeriesVolumeProgressiveLoader.h" - -#include "../Toolbox/GeometryToolbox.h" -#include "../Volumes/DicomVolumeImageMPRSlicer.h" -#include "BasicFetchingItemsSorter.h" -#include "BasicFetchingStrategy.h" - -#include <Core/Images/ImageProcessing.h> -#include <Core/OrthancException.h> - -namespace OrthancStone -{ - class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice - { - private: - const OrthancSeriesVolumeProgressiveLoader& that_; - - public: - ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that, - const CoordinateSystem3D& plane) : - DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), - that_(that) - { - if (IsValid()) - { - if (GetProjection() == VolumeProjection_Axial) - { - // 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 - SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex())); - } - - if (that_.strategy_.get() != NULL && - GetProjection() == VolumeProjection_Axial) - { - that_.strategy_->SetCurrent(GetSliceIndex()); - } - } - } - }; - - - - void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::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 OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::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 OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear() - { - for (size_t i = 0; i < slices_.size(); i++) - { - assert(slices_[i] != NULL); - delete slices_[i]; - } - - slices_.clear(); - slicesRevision_.clear(); - } - - - void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const - { - if (!HasGeometry()) - { - LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index): (!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()); - } - } - - - // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" - // (called with the slices created in LoadGeometry) - void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::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(); - - double spacingZ; - - if (slices.ComputeSpacingBetweenSlices(spacingZ)) - { - LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; - - const DicomInstanceParameters& parameters = *slices_[0]; - - geometry_.reset(new VolumeImageGeometry); - geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), - parameters.GetImageInformation().GetHeight(), - static_cast<unsigned int>(slices.GetSlicesCount())); - geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); - geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), - parameters.GetPixelSpacingY(), spacingZ); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "The origins of the slices of a volume image are not regularly spaced"); - } - } - } - - - const VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const - { - if (!HasGeometry()) - { - LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry(): (!HasGeometry())"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - assert(slices_.size() == geometry_->GetDepth()); - return *geometry_; - } - } - - - const DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const - { - CheckSliceIndex(index); - return *slices_[index]; - } - - - uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const - { - CheckSliceIndex(index); - return slicesRevision_[index]; - } - - - void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index) - { - CheckSliceIndex(index); - slicesRevision_[index] ++; - } - - - static unsigned int GetSliceIndexPayload(const OracleCommandBase& command) - { - assert(command.HasPayload()); - return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); - } - - - void OrthancSeriesVolumeProgressiveLoader::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<OracleCommandBase> command; - - if (quality == BEST_QUALITY) - { - std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand); - // TODO: review the following comment. - // - Commented out by bgo on 2019-07-19 | reason: Alain has seen cases - // where gzipping the uint16 image took 11 sec to produce 5mb. - // The unzipped request was much much faster. - // - Re-enabled on 2019-07-30. Reason: in Web Assembly, the browser - // does not use the Accept-Encoding header and always requests - // compression. Furthermore, NOT - 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); - // TODO: review the following comment. Commented out by bgo on 2019-07-19 - // (gzip for jpeg seems overkill) - //tmp->SetHttpHeader("Accept-Encoding", "gzip"); - tmp->SetInstance(instance); - tmp->SetQuality((quality == 0 ? 50 : 90)); - tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); - command.reset(tmp.release()); - } - - command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); - - boost::shared_ptr<IObserver> observer(GetSharedObserver()); - oracle_.Schedule(observer, command.release()); - } - else - { - // loading is finished! - volumeImageReadyInHighQuality_ = true; - BroadcastMessage(OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality(*this)); - } - } - -/** - This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags" -*/ - void OrthancSeriesVolumeProgressiveLoader::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 OrthancSeriesVolumeProgressiveLoader::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 OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message) - { - SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY); - } - - - void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) - { - unsigned int quality; - - switch (dynamic_cast<const GetOrthancWebViewerJpegCommand&>(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); - } - - - OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume, - IOracle& oracle, - IObservable& oracleObservable) : - oracle_(oracle), - active_(false), - simultaneousDownloads_(4), - volume_(volume), - sorter_(new BasicFetchingItemsSorter::Factory), - volumeImageReadyInHighQuality_(false) - { - // TODO => Move this out of constructor - Register<OrthancRestApiCommand::SuccessMessage> - (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry); - - Register<GetOrthancImageCommand::SuccessMessage> - (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent); - - Register<GetOrthancWebViewerJpegCommand::SuccessMessage> - (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent); - } - - OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader() - { - LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()"; - } - - void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count) - { - if (active_) - { - LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(): (active_)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (count == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - simultaneousDownloads_ = count; - } - } - - - void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId) - { -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries seriesId=" << seriesId; - if (active_) - { -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries NOT ACTIVE! --> ERROR"; - LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId): (active_)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - active_ = true; - - std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); - command->SetUri("/series/" + seriesId + "/instances-tags"); - -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule"; - boost::shared_ptr<IObserver> observer(GetSharedObserver()); - oracle_.Schedule(observer, command.release()); -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule"; - } - } - - - IVolumeSlicer::IExtractedSlice* - OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane) - { - if (volume_->HasGeometry()) - { - return new ExtractedSlice(*this, cuttingPlane); - } - else - { - return new IVolumeSlicer::InvalidSlice; - } - } -}
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Sat Dec 07 18:45:37 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include "../Messages/IObservable.h" -#include "../Messages/ObserverBase.h" -#include "../Oracle/GetOrthancImageCommand.h" -#include "../Oracle/GetOrthancWebViewerJpegCommand.h" -#include "../Oracle/IOracle.h" -#include "../Oracle/OrthancRestApiCommand.h" -#include "../Toolbox/SlicesSorter.h" -#include "../Volumes/DicomVolumeImage.h" -#include "../Volumes/IVolumeSlicer.h" -#include "IFetchingItemsSorter.h" -#include "IFetchingStrategy.h" - -#include <boost/shared_ptr.hpp> - -namespace OrthancStone -{ - /** - This class is used to manage the progressive loading of a volume that - is stored in a Dicom series. - */ - class OrthancSeriesVolumeProgressiveLoader : - public ObserverBase<OrthancSeriesVolumeProgressiveLoader>, - 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; - - class ExtractedSlice; - - /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */ - class SeriesGeometry : public boost::noncopyable - { - private: - void CheckSlice(size_t index, - const DicomInstanceParameters& reference) const; - - void CheckVolume() const; - - void Clear(); - - void CheckSliceIndex(size_t index) const; - - std::auto_ptr<VolumeImageGeometry> geometry_; - std::vector<DicomInstanceParameters*> slices_; - std::vector<uint64_t> slicesRevision_; - - public: - ~SeriesGeometry() - { - Clear(); - } - - void ComputeGeometry(SlicesSorter& slices); - - virtual bool HasGeometry() const - { - return geometry_.get() != NULL; - } - - virtual const VolumeImageGeometry& GetImageGeometry() const; - - const DicomInstanceParameters& GetSliceParameters(size_t index) const; - - uint64_t GetSliceRevision(size_t index) const; - - void IncrementSliceRevision(size_t index); - }; - - void ScheduleNextSliceDownload(); - - void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message); - - void SetSliceContent(unsigned int sliceIndex, - const Orthanc::ImageAccessor& image, - unsigned int quality); - - void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message); - - void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); - - 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_; - bool volumeImageReadyInHighQuality_; - - - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeImageReadyInHighQuality, OrthancSeriesVolumeProgressiveLoader); - - - OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume, - IOracle& oracle, - IObservable& oracleObservable); - - virtual ~OrthancSeriesVolumeProgressiveLoader(); - - void SetSimultaneousDownloads(unsigned int count); - - bool IsVolumeImageReadyInHighQuality() const - { - return volumeImageReadyInHighQuality_; - } - - void LoadSeries(const std::string& seriesId); - - /** - This getter is used by clients that do not receive the geometry through - subscribing, for instance if they are created or listening only AFTER the - "geometry loaded" message is broadcast - */ - bool HasGeometry() const - { - return seriesGeometry_.HasGeometry(); - } - - /** - Same remark as HasGeometry - */ - const VolumeImageGeometry& GetImageGeometry() const - { - return seriesGeometry_.GetImageGeometry(); - } - - /** - 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) ORTHANC_OVERRIDE; - }; -}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Sat Dec 07 18:45:37 2019 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Sun Dec 08 11:45:09 2019 +0100 @@ -353,6 +353,18 @@ ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/RenderStyle.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader2.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderCache.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderCache.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderStateMachine.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/LoaderStateMachine.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancMultiframeVolumeLoader.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/OrthancSeriesVolumeProgressiveLoader.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/BaseWebService.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp @@ -452,20 +464,8 @@ ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingItemsSorter.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader2.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader2.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/IFetchingItemsSorter.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/IFetchingStrategy.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderCache.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderCache.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h