Mercurial > hg > orthanc-stone
changeset 1432:758fb6958c20 loader-injection-feature
Merge from default
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Tue, 19 May 2020 07:39:03 +0200 |
parents | 4e7751a4b603 (diff) 1eaf19af15bf (current diff) |
children | 49f31fa332b3 |
files | Samples/build-wasm-RtViewer.sh Samples/build-wasm-SingleFrameViewer.sh UnitTestsSources/CMakeLists.txt |
diffstat | 5 files changed, 303 insertions(+), 108 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Loaders/DicomStructureSetLoader.cpp Fri May 15 21:14:35 2020 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Tue May 19 07:39:03 2020 +0200 @@ -48,8 +48,37 @@ } #endif + // implementation of IInstanceLookupHandler that uses Orthanc REST API calls to retrive the + // geometry of referenced instances + class DicomStructureSetLoader::RestInstanceLookupHandler : public DicomStructureSetLoader::IInstanceLookupHandler, + public LoaderStateMachine + { + public: + static boost::shared_ptr<RestInstanceLookupHandler > Create(DicomStructureSetLoader& loader) + { + boost::shared_ptr<RestInstanceLookupHandler> obj(new RestInstanceLookupHandler(loader)); + obj->LoaderStateMachine::PostConstructor(); + return obj; + } - class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State + protected: + RestInstanceLookupHandler(DicomStructureSetLoader& loader) + : LoaderStateMachine(loader.loadersContext_) + , loader_(loader) + { + } + + virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE; + + private: + // these subclasses hold the loading state + class AddReferencedInstance; // 2nd state + class LookupInstance; // 1st state + + DicomStructureSetLoader& loader_; + }; + + class DicomStructureSetLoader::RestInstanceLookupHandler::AddReferencedInstance : public LoaderStateMachine::State { private: std::string instanceId_; @@ -71,27 +100,14 @@ dicom.FromDicomAsJson(tags); DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); - - loader.content_->AddReferencedSlice(dicom); - loader.countProcessedInstances_ ++; - assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_); - - loader.revision_++; - loader.SetStructuresUpdated(); - - if (loader.countProcessedInstances_ == loader.countReferencedInstances_) - { - // All the referenced instances have been loaded, finalize the RT-STRUCT - loader.content_->CheckReferencedSlices(); - loader.revision_++; - loader.SetStructuresReady(); - } + + loader.AddReferencedSlice(dicom); } }; // State that converts a "SOP Instance UID" to an Orthanc identifier - class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State + class DicomStructureSetLoader::RestInstanceLookupHandler::LookupInstance : public LoaderStateMachine::State { private: std::string sopInstanceUid_; @@ -106,9 +122,6 @@ virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { -#if 0 - LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)"; -#endif DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); Json::Value lookup; @@ -147,6 +160,21 @@ } }; + void DicomStructureSetLoader::RestInstanceLookupHandler::RetrieveReferencedSlices( + const std::set<std::string>& nonEmptyInstances) + { + for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin(); + it != nonEmptyInstances.end(); + ++it) + { + std::unique_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::LoadStructure : public LoaderStateMachine::State { @@ -159,53 +187,28 @@ virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); - + + // Set the actual structure set content { 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; - } - } } + // initialize visibility flags + SetDefaultStructureVisibility(); + + // retrieve the (non-empty) referenced instances (the CT slices containing the corresponding structures) // Some (admittedly invalid) Dicom files have empty values in the // 0008,1155 tag. We try our best to cope with this. + // this is why we use `nonEmptyInstances` and not `instances` std::set<std::string> instances; std::set<std::string> nonEmptyInstances; + + // this traverses the polygon collection for all structures and retrieve the SOPInstanceUID of + // the referenced instances loader.content_->GetReferencedInstances(instances); + for (std::set<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it) { @@ -214,20 +217,55 @@ nonEmptyInstances.insert(instance); } - loader.countReferencedInstances_ = - static_cast<unsigned int>(nonEmptyInstances.size()); + loader.RetrieveReferencedSlices(nonEmptyInstances); + } + + void SetDefaultStructureVisibility() + { + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + + size_t structureCount = loader.content_->GetStructuresCount(); - for (std::set<std::string>::const_iterator - it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it) + 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) { - std::unique_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()); + // 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; + } } } + + private: + + }; @@ -347,8 +385,9 @@ , countReferencedInstances_(0) , structuresReady_(false) { + // the default handler to retrieve slice geometry is RestInstanceLookupHandler + instanceLookupHandler_ = RestInstanceLookupHandler::Create(*this); } - boost::shared_ptr<OrthancStone::DicomStructureSetLoader> DicomStructureSetLoader::Create(OrthancStone::ILoadersContext& loadersContext) { @@ -357,7 +396,31 @@ loadersContext)); obj->LoaderStateMachine::PostConstructor(); return obj; + } + void DicomStructureSetLoader::AddReferencedSlice(const Orthanc::DicomMap& dicom) + { + content_->AddReferencedSlice(dicom); + countProcessedInstances_ ++; + assert(countProcessedInstances_ <= countReferencedInstances_); + + revision_++; + SetStructuresUpdated(); + + if (countProcessedInstances_ == countReferencedInstances_) + { + // All the referenced instances have been loaded, finalize the RT-STRUCT + content_->CheckReferencedSlices(); + revision_++; + SetStructuresReady(); + } + } + + void DicomStructureSetLoader::RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) + { + // we set the number of referenced instances. This allows to know, in the method above, when we're done + countReferencedInstances_ = static_cast<unsigned int>(nonEmptyInstances.size()); + instanceLookupHandler_->RetrieveReferencedSlices(nonEmptyInstances); } void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
--- a/Framework/Loaders/DicomStructureSetLoader.h Fri May 15 21:14:35 2020 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.h Tue May 19 07:39:03 2020 +0200 @@ -35,12 +35,76 @@ public OrthancStone::IVolumeSlicer, public OrthancStone::IObservable { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresUpdated, DicomStructureSetLoader); + + /** + + Once the structure set has been loaded (the LoadStructure state), we need to fill it with geometry information + from the referenced slices (tag (0008,1155) described here: + https://dicom.innolitics.com/ciods/rt-structure-set/general-reference/00081140/00081155 + + This interface allows to customize how this information can be gathered. By default, the RestInstanceLookupHandler + will perform a REST call to the Orthanc API to retrieve this information. + + Injecting another implementation of this interface is useful when where this information can be supplied in + another (faster) way (for instance, if a separate loader for the CT series can be used to supply the slice geometry) + */ + class IInstanceLookupHandler + { + public: + virtual void RetrieveReferencedSlices(const std::set<std::string>& instances) = 0; + }; + + // predeclaration of the default IInstanceLookupHandler implementation + class RestInstanceLookupHandler; + + static boost::shared_ptr<DicomStructureSetLoader> Create( + OrthancStone::ILoadersContext& loadersContext); + + void SetInstanceLookupHandler(boost::shared_ptr<IInstanceLookupHandler> instanceLookupHandler) + { + instanceLookupHandler_ = instanceLookupHandler; + } + + 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>()); + + void LoadInstanceFullVisibility(const std::string& instanceId); + + + virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + + void SetStructuresReady(); + void SetStructuresUpdated(); + + bool AreStructuresReady() const; + + /** + Called by the IInstanceLookupHandler when slice referenced instance information is available. + When the last referenced slice is received, this method will perform a final check and will warn observers + */ + void AddReferencedSlice(const Orthanc::DicomMap& dicom); + private: class Slice; - // States of LoaderStateMachine - class AddReferencedInstance; // 3rd state - class LookupInstance; // 2nd state + // Only state of LoaderStateMachine class LoadStructure; // 1st state OrthancStone::ILoadersContext& loadersContext_; @@ -70,41 +134,13 @@ */ std::vector<bool> structureVisibility_; + + boost::shared_ptr<IInstanceLookupHandler> instanceLookupHandler_; + + private: + void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances); + protected: DicomStructureSetLoader(OrthancStone::ILoadersContext& loadersContext); - - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresUpdated, DicomStructureSetLoader); - - static boost::shared_ptr<DicomStructureSetLoader> Create( - OrthancStone::ILoadersContext& loadersContext); - - 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>()); - - void LoadInstanceFullVisibility(const std::string& instanceId); - - - virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; - - void SetStructuresReady(); - void SetStructuresUpdated(); - - bool AreStructuresReady() const; }; }
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Fri May 15 21:14:35 2020 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Tue May 19 07:39:03 2020 +0200 @@ -362,6 +362,9 @@ // the 3D plane corresponding to the slice OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); slices.AddSlice(geometry, instance.release()); + + if (slicePostProcessor_) + slicePostProcessor_->ProcessCTDicomSlice(dicom); } seriesGeometry_.ComputeGeometry(slices);
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Fri May 15 21:14:35 2020 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Tue May 19 07:39:03 2020 +0200 @@ -57,6 +57,12 @@ static const unsigned int QUALITY_02 = 2; class ExtractedSlice; + + class ISlicePostProcessor + { + public: + virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& dicom) = 0; + }; /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */ class SeriesGeometry : public boost::noncopyable @@ -121,6 +127,8 @@ std::vector<unsigned int> slicesQuality_; bool volumeImageReadyInHighQuality_; + boost::shared_ptr<ISlicePostProcessor> slicePostProcessor_; + OrthancSeriesVolumeProgressiveLoader( OrthancStone::ILoadersContext& loadersContext, boost::shared_ptr<OrthancStone::DicomVolumeImage> volume, @@ -141,6 +149,12 @@ void SetSimultaneousDownloads(unsigned int count); + void SetDicomSlicePostProcessor(boost::shared_ptr<ISlicePostProcessor> slicePostProcessor) + { + // this will delete the previously stored slice processor, if any + slicePostProcessor_ = slicePostProcessor; + } + bool IsVolumeImageReadyInHighQuality() const { return volumeImageReadyInHighQuality_;
--- a/UnitTestsSources/TestStructureSet.cpp Fri May 15 21:14:35 2020 +0200 +++ b/UnitTestsSources/TestStructureSet.cpp Tue May 19 07:39:03 2020 +0200 @@ -32,6 +32,11 @@ #include "Framework/Toolbox/DicomStructureSet2.h" #include "Framework/Toolbox/DisjointDataSet.h" +#include "Framework/Loaders/GenericLoadersContext.h" +#include "Framework/Loaders/DicomStructureSetLoader.h" + +#include "boost/date_time/posix_time/posix_time.hpp" + #include <Core/SystemToolbox.h> #include "gtest/gtest.h" @@ -5406,9 +5411,83 @@ const std::vector<DicomStructure2>& structures = structureSet.structures_; } - #endif // BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - +namespace +{ + void Initialize(const char* orthancApiUrl, OrthancStone::ILoadersContext& loadersContext) + { + Orthanc::WebServiceParameters p; + + OrthancStone::GenericLoadersContext& typedLoadersContext = + dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext); + // Default is http://localhost:8042 + // Here's how you may change it + p.SetUrl(orthancApiUrl); + p.SetCredentials("orthanc", "orthanc"); + typedLoadersContext.SetOrthancParameters(p); + + typedLoadersContext.StartOracle(); + } + + void Exitialize(OrthancStone::ILoadersContext& loadersContext) + { + OrthancStone::GenericLoadersContext& typedLoadersContext = + dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext); + + typedLoadersContext.StopOracle(); + } + + +#if 0 + class TestObserver : public ObserverBase<TestObserver> + { + public: + TestObserver() {}; + + virtual void Handle + + }; +#endif + +} + + +TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10) +{ + namespace pt = boost::posix_time; + + std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1)); + Initialize("http://localhost:8042/", *loadersContext); + + boost::shared_ptr<DicomStructureSetLoader> loader = DicomStructureSetLoader::Create(*loadersContext); + + // replace with Orthanc ID of an uploaded RTSTRUCT instance! + loader->LoadInstanceFullVisibility("72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661"); + + bool bContinue(true); + + pt::ptime initialTime = pt::second_clock::local_time(); + + while (bContinue) + { + bContinue = !loader->AreStructuresReady(); + boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); + + { + pt::ptime nowTime = pt::second_clock::local_time(); + pt::time_duration diff = nowTime - initialTime; + double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001; + std::cout << seconds << " seconds elapsed...\n"; + if (seconds > 30) + { + std::cout << "More than 30 seconds elapsed... Aborting test :(\n"; + //GTEST_FATAL_FAILURE_("More than 30 seconds elapsed... Aborting test :("); + //bContinue = false; + } + } + } +} + +