# HG changeset patch # User Sebastien Jodogne # Date 1699457509 -3600 # Node ID a9e23ef9ee098f88c2dd7c0dc13790ca42c9e6df # Parent 79e984a89a38111c900678a018ba61f477d2b681 preparing to extract dicom-sr annotations diff -r 79e984a89a38 -r a9e23ef9ee09 Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Nov 08 15:15:48 2023 +0100 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Nov 08 16:31:49 2023 +0100 @@ -226,6 +226,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) { @@ -279,7 +286,17 @@ 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; + } }; @@ -321,10 +338,9 @@ return *parameters_; } }; - - std::string studyInstanceUid_; - std::string seriesInstanceUid_; - std::vector frames_; + + std::unique_ptr sr_; + std::vector frames_; void Finalize() { @@ -353,13 +369,12 @@ } public: - DicomStructuredReportFrames(OrthancStone::DicomStructuredReport& sr, + DicomStructuredReportFrames(const OrthancStone::DicomStructuredReport& sr, const OrthancStone::LoadedDicomResources& instances) : - studyInstanceUid_(sr.GetStudyInstanceUid()), - seriesInstanceUid_(sr.GetSeriesInstanceUid()) + sr_(new OrthancStone::DicomStructuredReport(sr)) { std::list tmp; - sr.ExportReferencedFrames(tmp); + sr_->ExportReferencedFrames(tmp); frames_.reserve(tmp.size()); for (std::list::const_iterator @@ -434,6 +449,61 @@ return found; } + + virtual OrthancStone::ISceneLayer* ExtractAnnotations(const std::string& sopInstanceUid, + unsigned int frameNumber, + double originX, + double originY, + double pixelSpacingX, + double pixelSpacingY) const ORTHANC_OVERRIDE + { + size_t frameIndex; + if (!LookupFrame(frameIndex, sopInstanceUid, frameNumber)) + { + return NULL; + } + + const OrthancStone::DicomInstanceParameters& parameters = GetInstanceOfFrame(frameIndex); + + const double x = originX - pixelSpacingX / 2.0; + const double y = originY - pixelSpacingY / 2.0; + const double w = parameters.GetWidth() * pixelSpacingX; + const double h = parameters.GetHeight() * pixelSpacingY; + + std::unique_ptr layer(new OrthancStone::MacroSceneLayer); + + { + std::unique_ptr polyline(new OrthancStone::PolylineSceneLayer); + { + OrthancStone::PolylineSceneLayer::Chain chain; + chain.push_back(OrthancStone::ScenePoint2D(x, y)); + chain.push_back(OrthancStone::ScenePoint2D(x + pixelSpacingX, y)); + chain.push_back(OrthancStone::ScenePoint2D(x + pixelSpacingX, y + pixelSpacingY)); + chain.push_back(OrthancStone::ScenePoint2D(x, y + pixelSpacingY)); + + polyline->AddChain(chain, true, 255, 0, 0); + } + + layer->AddLayer(polyline.release()); + } + + { + std::unique_ptr polyline(new OrthancStone::PolylineSceneLayer); + { + OrthancStone::PolylineSceneLayer::Chain chain; + chain.push_back(OrthancStone::ScenePoint2D(x, y)); + chain.push_back(OrthancStone::ScenePoint2D(x + w, y)); + chain.push_back(OrthancStone::ScenePoint2D(x + w, y + h)); + chain.push_back(OrthancStone::ScenePoint2D(x, y + h)); + + polyline->AddChain(chain, true, 255, 0, 0); + } + + layer->AddLayer(polyline.release()); + } + + return layer.release(); + } }; @@ -1966,6 +2036,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 @@ -2650,6 +2721,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()); @@ -2684,6 +2765,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_) diff -r 79e984a89a38 -r a9e23ef9ee09 OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp Wed Nov 08 15:15:48 2023 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp Wed Nov 08 16:31:49 2023 +0100 @@ -120,14 +120,7 @@ { void DicomStructuredReport::ReferencedInstance::AddFrame(unsigned int frame) { - if (frame == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - frames_.insert(frame - 1); - } + frames_.insert(frame); } @@ -140,6 +133,20 @@ bool hasProbabilityOfCancer_; float probabilityOfCancer_; + protected: + void Copy(const Structure& other) + { + if (other.HasFrameNumber()) + { + SetFrameNumber(other.GetFrameNumber()); + } + + if (other.HasProbabilityOfCancer()) + { + SetProbabilityOfCancer(other.GetProbabilityOfCancer()); + } + } + public: Structure(const std::string& sopInstanceUid) : sopInstanceUid_(sopInstanceUid), @@ -152,17 +159,17 @@ { } + virtual Structure* Clone() const = 0; + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + void SetFrameNumber(unsigned int frame) { - if (frame <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - hasFrameNumber_ = true; - frameNumber_ = frame - 1; - } + hasFrameNumber_ = true; + frameNumber_ = frame; } void SetProbabilityOfCancer(float probability) @@ -229,6 +236,13 @@ { } + virtual Structure* Clone() const + { + std::unique_ptr cloned(new Point(GetSopInstanceUid(), point_.GetX(), point_.GetY())); + cloned->Copy(*this); + return cloned.release(); + } + const ScenePoint2D& GetPoint() const { return point_; @@ -260,6 +274,20 @@ } } + Polyline(const std::string& sopInstanceUid, + const std::vector& points) : + Structure(sopInstanceUid), + points_(points) + { + } + + virtual Structure* Clone() const + { + std::unique_ptr cloned(new Polyline(GetSopInstanceUid(), points_)); + cloned->Copy(*this); + return cloned.release(); + } + size_t GetSize() const { return points_.size(); @@ -504,21 +532,22 @@ for (size_t m = 0; m < tokens.size(); m++) { uint32_t frame; - if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(frame, tokens[m])) + if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(frame, tokens[m]) || + frame <= 0) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } else { - AddStructure(sopInstanceUid, group, true, frame, hasProbabilityOfCancer, probabilityOfCancer); - instanceInformation->second->AddFrame(frame); + AddStructure(sopInstanceUid, group, true, frame - 1, hasProbabilityOfCancer, probabilityOfCancer); + instanceInformation->second->AddFrame(frame - 1); } } } else { AddStructure(sopInstanceUid, group, false, 0, hasProbabilityOfCancer, probabilityOfCancer); - instanceInformation->second->AddFrame(1); + instanceInformation->second->AddFrame(0); } } } @@ -531,6 +560,27 @@ } + DicomStructuredReport::DicomStructuredReport(const DicomStructuredReport& other) : + studyInstanceUid_(other.studyInstanceUid_), + seriesInstanceUid_(other.seriesInstanceUid_), + sopInstanceUid_(other.sopInstanceUid_), + orderedInstances_(other.orderedInstances_) + { + for (std::map::const_iterator + it = other.instancesInformation_.begin(); it != other.instancesInformation_.end(); ++it) + { + assert(it->second != NULL); + instancesInformation_[it->first] = new ReferencedInstance(*it->second); + } + + for (std::list::const_iterator it = other.structures_.begin(); it != other.structures_.end(); ++it) + { + assert(*it != NULL); + structures_.push_back((*it)->Clone()); + } + } + + DicomStructuredReport::~DicomStructuredReport() { for (std::list::iterator it = structures_.begin(); it != structures_.end(); ++it) diff -r 79e984a89a38 -r a9e23ef9ee09 OrthancStone/Sources/Toolbox/DicomStructuredReport.h --- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.h Wed Nov 08 15:15:48 2023 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.h Wed Nov 08 16:31:49 2023 +0100 @@ -46,7 +46,7 @@ class Point; class Polyline; - class ReferencedInstance : public boost::noncopyable + class ReferencedInstance { private: std::string studyInstanceUid_; @@ -154,6 +154,8 @@ DicomStructuredReport(Orthanc::ParsedDicomFile& dicom); + DicomStructuredReport(const DicomStructuredReport& other); // Copy constructor + ~DicomStructuredReport(); const std::string& GetStudyInstanceUid() const