# HG changeset patch # User Sebastien Jodogne # Date 1723824593 -7200 # Node ID 743fa9c74443cacbee2e42d698c0ba072faefad3 # Parent 9d77c935f2c38d93c1a5df4e9617439a11f0367b# Parent 5f0529b2ce652ea03af395e1df9f9fd7a39123fa integration mainline->dicom-sr diff -r 5f0529b2ce65 -r 743fa9c74443 Applications/StoneWebViewer/WebApplication/configuration.json --- a/Applications/StoneWebViewer/WebApplication/configuration.json Fri Aug 16 18:08:00 2024 +0200 +++ b/Applications/StoneWebViewer/WebApplication/configuration.json Fri Aug 16 18:09:53 2024 +0200 @@ -120,7 +120,7 @@ /** * Define a list of modality type that the viewer will ignore. **/ - "SkipSeriesFromModalities": ["SR", "SEG", "PR"], + "SkipSeriesFromModalities": [ "SEG", "PR" ], /** * Whether to display the info panel at startup. Allowed values: diff -r 5f0529b2ce65 -r 743fa9c74443 Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Fri Aug 16 18:08:00 2024 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Fri Aug 16 18:09:53 2024 +0200 @@ -82,6 +82,7 @@ #include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" #include "../../../OrthancStone/Sources/StoneException.h" #include "../../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h" +#include "../../../OrthancStone/Sources/Toolbox/DicomStructuredReport.h" #include "../../../OrthancStone/Sources/Toolbox/GeometryToolbox.h" #include "../../../OrthancStone/Sources/Toolbox/OsiriX/AngleAnnotation.h" #include "../../../OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.h" @@ -154,6 +155,12 @@ }; +enum FramesCollectionType +{ + FramesCollectionType_None, + FramesCollectionType_DicomSR +}; + static OrthancStone::MouseAction ConvertWebViewerAction(int action) { @@ -225,6 +232,13 @@ const OrthancStone::Vector& point, double maximumDistance) const = 0; + virtual OrthancStone::ISceneLayer* ExtractAnnotations(const std::string& sopInstanceUid, + unsigned int frameNumber, + double originX, + double originY, + double pixelSpacingX, + double pixelSpacingY) const = 0; + static OrthancStone::CoordinateSystem3D GetFrameGeometry(const IFramesCollection& frames, size_t frameIndex) { @@ -278,7 +292,239 @@ double maximumDistance) const ORTHANC_OVERRIDE { return frames_->FindClosestFrame(frameIndex, point, maximumDistance); + } + + virtual OrthancStone::ISceneLayer* ExtractAnnotations(const std::string& sopInstanceUid, + unsigned int frameNumber, + double originX, + double originY, + double pixelSpacingX, + double pixelSpacingY) const ORTHANC_OVERRIDE + { + return NULL; + } +}; + + +class DicomStructuredReportFrames : public IFramesCollection +{ +private: + class Frame : public boost::noncopyable + { + private: + OrthancStone::DicomStructuredReport::ReferencedFrame info_; + Orthanc::DicomMap tags_; + std::unique_ptr parameters_; + + public: + Frame(const OrthancStone::DicomStructuredReport::ReferencedFrame& info, + const OrthancStone::LoadedDicomResources& instances) : + info_(info) + { + if (!instances.LookupResource(tags_, info.GetSopInstanceUid())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); + } + + parameters_.reset(new OrthancStone::DicomInstanceParameters(tags_)); + } + + const OrthancStone::DicomStructuredReport::ReferencedFrame& GetInformation() const + { + return info_; + } + + const Orthanc::DicomMap& GetTags() const + { + return tags_; + } + + const OrthancStone::DicomInstanceParameters& GetParameters() const + { + return *parameters_; + } }; + + std::unique_ptr sr_; + std::vector frames_; + + void Finalize() + { + for (size_t i = 0; i < frames_.size(); i++) + { + if (frames_[i] != NULL) + { + delete frames_[i]; + } + } + + frames_.clear(); + } + + const Frame& GetFrame(size_t index) const + { + if (index >= frames_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(frames_[index] != NULL); + return *frames_[index]; + } + } + +public: + DicomStructuredReportFrames(const OrthancStone::DicomStructuredReport& sr, + const OrthancStone::LoadedDicomResources& instances) : + sr_(new OrthancStone::DicomStructuredReport(sr)) + { + std::list tmp; + sr_->ExportReferencedFrames(tmp); + + frames_.reserve(tmp.size()); + for (std::list::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + try + { + frames_.push_back(new Frame(*it, instances)); + } + catch (Orthanc::OrthancException&) + { + // An instance is not loaded yet + Finalize(); + throw; + } + } + } + + virtual ~DicomStructuredReportFrames() + { + Finalize(); + } + + virtual size_t GetFramesCount() const ORTHANC_OVERRIDE + { + return frames_.size(); + } + + virtual const OrthancStone::DicomInstanceParameters& GetInstanceOfFrame(size_t frameIndex) const ORTHANC_OVERRIDE + { + return GetFrame(frameIndex).GetParameters(); + } + + virtual unsigned int GetFrameNumberInInstance(size_t frameIndex) const ORTHANC_OVERRIDE + { + return GetFrame(frameIndex).GetInformation().GetFrameNumber(); + } + + virtual bool LookupFrame(size_t& frameIndex, + const std::string& sopInstanceUid, + unsigned int frameNumber) const ORTHANC_OVERRIDE + { + // TODO - Could be speeded up with an additional index + for (size_t i = 0; i < frames_.size(); i++) + { + if (frames_[i]->GetInformation().GetSopInstanceUid() == sopInstanceUid && + frames_[i]->GetInformation().GetFrameNumber() == frameNumber) + { + frameIndex = i; + return true; + } + } + + return false; + } + + virtual bool FindClosestFrame(size_t& frameIndex, + const OrthancStone::Vector& point, + double maximumDistance) const ORTHANC_OVERRIDE + { + bool found = false; + + for (size_t i = 0; i < GetFramesCount(); i++) + { + double distance = GetFrameGeometry(*this, i).ComputeDistance(point); + if (distance <= maximumDistance) + { + found = true; + frameIndex = i; + } + } + + return found; + } + + virtual OrthancStone::ISceneLayer* ExtractAnnotations(const std::string& sopInstanceUid, + unsigned int frameNumber, + double originX, + double originY, + double pixelSpacingX, + double pixelSpacingY) const ORTHANC_OVERRIDE + { + std::unique_ptr layer(new OrthancStone::MacroSceneLayer); + + const double x = originX - pixelSpacingX / 2.0; + const double y = originY - pixelSpacingY / 2.0; + + for (size_t i = 0; i < sr_->GetStructuresCount(); i++) + { + const OrthancStone::DicomStructuredReport::Structure& structure = sr_->GetStructure(i); + if (structure.GetSopInstanceUid() == sopInstanceUid && + (!structure.HasFrameNumber() || + structure.GetFrameNumber() == frameNumber)) + { + OrthancStone::Color color(0, 0, 255); + + if (structure.HasProbabilityOfCancer()) + { + if (structure.GetProbabilityOfCancer() > 50.0f) + { + color = OrthancStone::Color(255, 0, 0); + } + else + { + color = OrthancStone::Color(0, 255, 0); + } + } + + switch (structure.GetType()) + { + case OrthancStone::DicomStructuredReport::StructureType_Point: + // TODO + break; + + case OrthancStone::DicomStructuredReport::StructureType_Polyline: + { + const OrthancStone::DicomStructuredReport::Polyline& source = dynamic_cast(structure); + + if (source.GetSize() > 1) + { + std::unique_ptr target(new OrthancStone::PolylineSceneLayer); + + OrthancStone::PolylineSceneLayer::Chain chain; + chain.resize(source.GetSize()); + for (size_t i = 0; i < source.GetSize(); i++) + { + chain[i] = OrthancStone::ScenePoint2D(x + source.GetPoint(i).GetX() * pixelSpacingX, + y + source.GetPoint(i).GetY() * pixelSpacingY); + } + + target->AddChain(chain, false, color.GetRed(), color.GetGreen(), color.GetBlue()); + layer->AddLayer(target.release()); + } + break; + } + + default: + break; + } + } + } + + return layer.release(); + } }; @@ -401,6 +647,10 @@ virtual void SignalVirtualSeriesThumbnailLoaded(const std::string& virtualSeriesId, const std::string& jpeg) = 0; + + virtual void SignalDicomSRLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) = 0; }; private: @@ -410,6 +660,7 @@ size_t pending_; boost::shared_ptr studies_; boost::shared_ptr series_; + boost::shared_ptr instances_; boost::shared_ptr resourcesLoader_; boost::shared_ptr thumbnailsLoader_; boost::shared_ptr metadataLoader_; @@ -417,13 +668,17 @@ VirtualSeries virtualSeries_; std::vector skipSeriesFromModalities_; + typedef std::map > StructuredReports; + StructuredReports structuredReports_; + explicit ResourcesLoader(OrthancStone::ILoadersContext& context, const OrthancStone::DicomSource& source) : context_(context), source_(source), pending_(0), studies_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID)), - series_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID)) + series_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID)), + instances_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID)) { } @@ -437,12 +692,12 @@ LOG(INFO) << "resources loaded: " << dicom.GetSize() << ", " << Orthanc::EnumerationToString(payload.GetValue()); - std::vector seriesIdsToRemove; - if (payload.GetValue() == Orthanc::ResourceType_Series) { // the 'dicom' var is actually equivalent to the 'series_' member in this case + std::vector seriesIdsToRemove; + for (size_t i = 0; i < dicom.GetSize(); i++) { std::string studyInstanceUid, seriesInstanceUid, modality; @@ -459,7 +714,6 @@ thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid); metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid); } - else { seriesIdsToRemove.push_back(seriesInstanceUid); @@ -473,18 +727,64 @@ dicom.RemoveResource(seriesIdsToRemove[i]); } } - - if (pending_ == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - pending_ --; - if (pending_ == 0 && - observer_.get() != NULL) + else if (payload.GetValue() == Orthanc::ResourceType_Instance) + { + // This occurs if loading DICOM-SR: Show the DICOM-SR once all its referenced instances are loaded + + for (size_t i = 0; i < dicom.GetSize(); i++) { - observer_->SignalResourcesLoaded(); + std::string studyInstanceUid, seriesInstanceUid, sopInstanceUid; + if (dicom.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + dicom.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) && + dicom.GetResource(i).LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + for (StructuredReports::const_iterator it = structuredReports_.begin(); it != structuredReports_.end(); ++it) + { + if (it->second->IsReferencedInstance(studyInstanceUid, seriesInstanceUid, sopInstanceUid)) + { + bool complete = true; + for (size_t j = 0; j < it->second->GetReferencedInstancesCount(); j++) + { + std::string referencedStudyInstanceUid, referencedSeriesInstanceUid, referencedInstanceInstanceUid, sopClassUid; + it->second->GetReferencedInstance(referencedStudyInstanceUid, referencedSeriesInstanceUid, referencedInstanceInstanceUid, sopClassUid, j); + if (!instances_->HasResource(referencedInstanceInstanceUid)) + { + complete = false; + break; + } + } + + if (complete) + { + LOG(INFO) << "Loaded all the instances referred by DICOM-SR instance: " << sopInstanceUid; + if (observer_ != NULL) + { + observer_->SignalDicomSRLoaded(it->second->GetStudyInstanceUid(), + it->second->GetSeriesInstanceUid(), + it->second->GetSopInstanceUid()); + } + } + } + } + } + } + } + + if (payload.GetValue() == Orthanc::ResourceType_Study || + payload.GetValue() == Orthanc::ResourceType_Series) + { + if (pending_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + pending_ --; + if (pending_ == 0 && + observer_.get() != NULL) + { + observer_->SignalResourcesLoaded(); + } } } } @@ -500,6 +800,23 @@ void Handle(const OrthancStone::SeriesMetadataLoader::SuccessMessage& message) { + for (size_t i = 0; i < message.GetInstancesCount(); i++) + { + std::string sopInstanceUid, sopClassUid; + if (message.GetInstance(i).LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) && + message.GetInstance(i).LookupStringValue(sopClassUid, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) && + OrthancStone::StringToSopClassUid(sopClassUid) == OrthancStone::SopClassUid_ComprehensiveSR) + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule( + GetSharedObserver(), PRIORITY_NORMAL, OrthancStone::ParseDicomFromWadoCommand::Create( + source_, message.GetStudyInstanceUid(), message.GetSeriesInstanceUid(), sopInstanceUid, + false /* no transcoding */, Orthanc::DicomTransferSyntax_LittleEndianExplicit /* dummy value */, + new InstanceInfo(message.GetStudyInstanceUid(), message.GetSeriesInstanceUid(), sopInstanceUid, Action_ComprehensiveSR))); + return; + } + } + if (observer_.get() != NULL) { observer_->SignalSeriesMetadataLoaded( @@ -545,19 +862,33 @@ pending_ += 2; } - - - class PdfInfo : public Orthanc::IDynamicObject + + + enum Action + { + Action_Pdf, + Action_ComprehensiveSR, + Action_ReferencedInstance + }; + + + class InstanceInfo : public Orthanc::IDynamicObject { private: std::string studyInstanceUid_; std::string seriesInstanceUid_; + std::string sopInstanceUid_; + Action action_; public: - PdfInfo(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) : + InstanceInfo(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + Action action) : studyInstanceUid_(studyInstanceUid), - seriesInstanceUid_(seriesInstanceUid) + seriesInstanceUid_(seriesInstanceUid), + sopInstanceUid_(sopInstanceUid), + action_(action) { } @@ -570,23 +901,85 @@ { return seriesInstanceUid_; } + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + Action GetAction() const + { + return action_; + } }; void Handle(const OrthancStone::ParseDicomSuccessMessage& message) { - const PdfInfo& info = dynamic_cast(message.GetOrigin().GetPayload()); + const InstanceInfo& info = dynamic_cast(message.GetOrigin().GetPayload()); if (observer_.get() != NULL) { - std::string pdf; - if (message.GetDicom().ExtractPdf(pdf)) + switch (info.GetAction()) { - observer_->SignalSeriesPdfLoaded(info.GetStudyInstanceUid(), info.GetSeriesInstanceUid(), pdf); - } - else - { - LOG(ERROR) << "Unable to extract PDF from series: " << info.GetSeriesInstanceUid(); + case Action_Pdf: + { + std::string pdf; + if (message.GetDicom().ExtractPdf(pdf)) + { + observer_->SignalSeriesPdfLoaded(info.GetStudyInstanceUid(), info.GetSeriesInstanceUid(), pdf); + } + else + { + LOG(ERROR) << "Unable to extract PDF from series: " << info.GetSeriesInstanceUid(); + } + + break; + } + + case Action_ComprehensiveSR: + { + try + { + boost::shared_ptr sr(new OrthancStone::DicomStructuredReport(message.GetDicom())); + + for (size_t i = 0; i < sr->GetReferencedInstancesCount(); i++) + { + std::string studyInstanceUid; + std::string seriesInstanceUid; + std::string sopInstanceUid; + std::string sopClassUid; + sr->GetReferencedInstance(studyInstanceUid, seriesInstanceUid, sopInstanceUid, sopClassUid, i); + + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); + filter.SetValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid, false); + + std::set tags; + + resourcesLoader_->ScheduleQido( + instances_, PRIORITY_NORMAL, source_, Orthanc::ResourceType_Instance, filter, tags, + new Orthanc::SingleValueObject(Orthanc::ResourceType_Instance)); + } + + structuredReports_[info.GetSeriesInstanceUid()] = sr; + + if (observer_.get() != NULL) + { + observer_->SignalSeriesMetadataLoaded( + info.GetStudyInstanceUid(), info.GetSeriesInstanceUid()); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Cannot decode DICOM-SR: " << e.What(); + } + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } } @@ -819,12 +1212,33 @@ } } - IFramesCollection* GetSeriesFrames(const std::string& seriesInstanceUid) const - { + IFramesCollection* GetSeriesFrames(FramesCollectionType& type, + const std::string& seriesInstanceUid) const + { + type = FramesCollectionType_None; + OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); if (accessor.IsComplete()) { + StructuredReports::const_iterator sr = structuredReports_.find(seriesInstanceUid); + if (sr != structuredReports_.end()) + { + assert(sr->second != NULL); + type = FramesCollectionType_DicomSR; + + try + { + return new DicomStructuredReportFrames(*sr->second, *instances_); + } + catch (Orthanc::OrthancException&) + { + LOG(INFO) << "All the instances referenced by the DICOM-SR series \"" + << seriesInstanceUid << "\" are not available yet"; + return NULL; + } + } + std::unique_ptr target(new OrthancStone::SortedFrames); target->Clear(); @@ -939,8 +1353,7 @@ GetSharedObserver(), PRIORITY_NORMAL, OrthancStone::ParseDicomFromWadoCommand::Create( source_, studyInstanceUid, seriesInstanceUid, sopInstanceUid, false /* no transcoding */, Orthanc::DicomTransferSyntax_LittleEndianExplicit /* dummy value */, - new PdfInfo(studyInstanceUid, seriesInstanceUid))); - + new InstanceInfo(studyInstanceUid, seriesInstanceUid, sopInstanceUid, Action_Pdf))); return; } } @@ -1688,6 +2101,7 @@ static const int LAYER_REFERENCE_LINES = 3; static const int LAYER_ANNOTATIONS_OSIRIX = 4; static const int LAYER_ANNOTATIONS_STONE = 5; + static const int LAYER_STRUCTURED_REPORT = 6; class ICommand : public Orthanc::IDynamicObject @@ -2129,6 +2543,7 @@ boost::shared_ptr stoneAnnotations_; bool linearInterpolation_; + std::string pendingSeriesInstanceUid_; void ScheduleNextPrefetch() @@ -2390,6 +2805,16 @@ } + std::unique_ptr structuredReportAnnotations; + + if (frames_.get() != NULL) + { + structuredReportAnnotations.reset(frames_->ExtractAnnotations(instance.GetSopInstanceUid(), frameIndex, + layer->GetOriginX(), layer->GetOriginY(), + layer->GetPixelSpacingX(), layer->GetPixelSpacingY())); + } + + { std::unique_ptr lock(viewport_->Lock()); @@ -2424,6 +2849,24 @@ scene.DeleteLayer(LAYER_ORIENTATION_MARKERS); } + if (orientationMarkers.get() != NULL) + { + scene.SetLayer(LAYER_ORIENTATION_MARKERS, orientationMarkers.release()); + } + else + { + scene.DeleteLayer(LAYER_ORIENTATION_MARKERS); + } + + if (structuredReportAnnotations.get() != NULL) + { + scene.SetLayer(LAYER_STRUCTURED_REPORT, structuredReportAnnotations.release()); + } + else + { + scene.DeleteLayer(LAYER_STRUCTURED_REPORT); + } + stoneAnnotations_->Render(scene); // Necessary for "FitContent()" to work if (fitNextContent_) @@ -3607,6 +4050,24 @@ *this, current.GetOrigin() + synchronizationOffset_, current.GetNormal()); } } + + + void SetPendingSeriesInstanceUid(const std::string& seriesInstanceUid) + { + pendingSeriesInstanceUid_ = seriesInstanceUid; + } + + + void ClearPendingSeriesInstanceUid() + { + pendingSeriesInstanceUid_.clear(); + } + + + const std::string& GetPendingSeriesInstanceUid() const + { + return pendingSeriesInstanceUid_; + } }; @@ -3888,6 +4349,10 @@ labelPosition.GetX(), labelPosition.GetY() ); } + + virtual void SignalDicomSRLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) ORTHANC_OVERRIDE; }; @@ -3958,6 +4423,28 @@ } +void WebAssemblyObserver::SignalDicomSRLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) +{ + for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) + { + if (it->second->GetPendingSeriesInstanceUid() == seriesInstanceUid) + { + it->second->ClearPendingSeriesInstanceUid(); + + FramesCollectionType type; + std::unique_ptr frames(GetResourcesLoader().GetSeriesFrames(type, seriesInstanceUid)); + + if (frames.get() != NULL) + { + it->second->SetFrames(frames.release()); + } + } + } +} + + extern "C" { int main(int argc, char const *argv[]) @@ -4252,15 +4739,23 @@ { try { - std::unique_ptr frames(GetResourcesLoader().GetSeriesFrames(seriesInstanceUid)); + FramesCollectionType type; + std::unique_ptr frames(GetResourcesLoader().GetSeriesFrames(type, seriesInstanceUid)); if (frames.get() != NULL) { + GetViewport(canvas)->ClearPendingSeriesInstanceUid(); GetViewport(canvas)->SetFrames(frames.release()); return 1; } + else if (type == FramesCollectionType_DicomSR) + { + GetViewport(canvas)->SetPendingSeriesInstanceUid(seriesInstanceUid); + return 0; + } else { + GetViewport(canvas)->ClearPendingSeriesInstanceUid(); return 0; } } diff -r 5f0529b2ce65 -r 743fa9c74443 OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp Fri Aug 16 18:08:00 2024 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp Fri Aug 16 18:09:53 2024 +0200 @@ -607,4 +607,23 @@ return *structures_[index]; } } + + + bool DicomStructuredReport::IsReferencedInstance(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) const + { + std::map::const_iterator found = instancesInformation_.find(sopInstanceUid); + + if (found == instancesInformation_.end()) + { + return false; + } + else + { + assert(found->second != NULL); + return (found->second->GetStudyInstanceUid() == studyInstanceUid && + found->second->GetSeriesInstanceUid() == seriesInstanceUid); + } + } } diff -r 5f0529b2ce65 -r 743fa9c74443 OrthancStone/Sources/Toolbox/DicomStructuredReport.h --- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.h Fri Aug 16 18:08:00 2024 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.h Fri Aug 16 18:09:53 2024 +0200 @@ -302,5 +302,9 @@ } const Structure& GetStructure(size_t index) const; + + bool IsReferencedInstance(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) const; }; }