# HG changeset patch # User Benjamin Golinvaux # Date 1589211450 -7200 # Node ID d959bc8f6c1be367b8403afc5c639642142231d2 # Parent 998697c5ec7486eb56900a1dae1f01c106fe7f44 Instance lookup is now performed in a separate class through an interface. Another implementation can be injected (SetInstanceLookupHandler) diff -r 998697c5ec74 -r d959bc8f6c1b Framework/Loaders/DicomStructureSetLoader.cpp --- a/Framework/Loaders/DicomStructureSetLoader.cpp Mon May 11 09:50:02 2020 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Mon May 11 17:37:30 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 Create(DicomStructureSetLoader& loader) + { + boost::shared_ptr 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& 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(); - - 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(); Json::Value lookup; @@ -147,6 +160,21 @@ } }; + void DicomStructureSetLoader::RestInstanceLookupHandler::RetrieveReferencedSlices( + const std::set& nonEmptyInstances) + { + for (std::set::const_iterator it = nonEmptyInstances.begin(); + it != nonEmptyInstances.end(); + ++it) + { + std::unique_ptr 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(); - + + // 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::iterator foundIt = - std::find( - loader.initiallyVisibleStructures_.begin(), - loader.initiallyVisibleStructures_.end(), - structureName); - std::vector::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 instances; std::set nonEmptyInstances; + + // this traverses the polygon collection for all structures and retrieve the SOPInstanceUID of + // the referenced instances loader.content_->GetReferencedInstances(instances); + for (std::set::const_iterator it = instances.begin(); it != instances.end(); ++it) { @@ -214,20 +217,55 @@ nonEmptyInstances.insert(instance); } - loader.countReferencedInstances_ = - static_cast(nonEmptyInstances.size()); + loader.RetrieveReferencedSlices(nonEmptyInstances); + } + + void SetDefaultStructureVisibility() + { + DicomStructureSetLoader& loader = GetLoader(); + + size_t structureCount = loader.content_->GetStructuresCount(); - for (std::set::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 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::iterator foundIt = + std::find( + loader.initiallyVisibleStructures_.begin(), + loader.initiallyVisibleStructures_.end(), + structureName); + std::vector::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 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& nonEmptyInstances) + { + // we set the number of referenced instances. This allows to know, in the method above, when we're done + countReferencedInstances_ = static_cast(nonEmptyInstances.size()); + instanceLookupHandler_->RetrieveReferencedSlices(nonEmptyInstances); } void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display) diff -r 998697c5ec74 -r d959bc8f6c1b Framework/Loaders/DicomStructureSetLoader.h --- a/Framework/Loaders/DicomStructureSetLoader.h Mon May 11 09:50:02 2020 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.h Mon May 11 17:37:30 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& instances) = 0; + }; + + // predeclaration of the default IInstanceLookupHandler implementation + class RestInstanceLookupHandler; + + static boost::shared_ptr Create( + OrthancStone::ILoadersContext& loadersContext); + + void SetInstanceLookupHandler(boost::shared_ptr 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& initiallyVisibleStructures = std::vector()); + + 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 structureVisibility_; + + boost::shared_ptr instanceLookupHandler_; + + private: + void RetrieveReferencedSlices(const std::set& 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 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& initiallyVisibleStructures = std::vector()); - - void LoadInstanceFullVisibility(const std::string& instanceId); - - - virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; - - void SetStructuresReady(); - void SetStructuresUpdated(); - - bool AreStructuresReady() const; }; }