# HG changeset patch # User Sebastien Jodogne # Date 1507303508 -7200 # Node ID e3433dabfb8dc4bd8dcb0bce7b8840104b1ac5f4 # Parent e66b2c757790282483723e357642192fa7d6dfea refactoring DicomStructureSet diff -r e66b2c757790 -r e3433dabfb8d Applications/IBasicApplication.cpp --- a/Applications/IBasicApplication.cpp Wed Oct 04 17:53:47 2017 +0200 +++ b/Applications/IBasicApplication.cpp Fri Oct 06 17:25:08 2017 +0200 @@ -204,7 +204,7 @@ OrthancPlugins::OrthancHttpConnection orthanc(webService); if (!MessagingToolbox::CheckOrthancVersion(orthanc)) { - LOG(ERROR) << "Your version of Orthanc is incompatible with Orthanc Stone, please upgrade"; + LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade"; throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); } } diff -r e66b2c757790 -r e3433dabfb8d Applications/Samples/SingleVolumeApplication.h --- a/Applications/Samples/SingleVolumeApplication.h Wed Oct 04 17:53:47 2017 +0200 +++ b/Applications/Samples/SingleVolumeApplication.h Fri Oct 06 17:25:08 2017 +0200 @@ -32,6 +32,7 @@ #include // TODO REMOVE #include "../../Framework/Layers/DicomStructureSetRendererFactory.h" // TODO REMOVE +#include "../../Framework/Toolbox/MessagingToolbox.h" // TODO REMOVE namespace OrthancStone { @@ -176,7 +177,7 @@ std::auto_ptr widget(new LayerWidget); -#if 1 +#if 0 std::auto_ptr volume(new OrthancVolumeImage(context.GetWebService(), true)); if (series.empty()) { @@ -202,9 +203,11 @@ } #else { + const std::string s = "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"; // IBA + //const std::string s = "17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"; // 0522c0001 TCIA + OrthancPlugins::OrthancHttpConnection orthanc; - struct_.reset(new DicomStructureSet(orthanc, "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9")); // IBA - //struct_.reset(new DicomStructureSet(orthanc, "17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20")); // 0522c0001 TCIA + struct_.reset(DicomStructureSet::SynchronousLoad(orthanc, s)); } std::auto_ptr ct(new OrthancVolumeImage(context.GetWebService(), false)); diff -r e66b2c757790 -r e3433dabfb8d Framework/Layers/DicomStructureSetRendererFactory.cpp --- a/Framework/Layers/DicomStructureSetRendererFactory.cpp Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Layers/DicomStructureSetRendererFactory.cpp Fri Oct 06 17:25:08 2017 +0200 @@ -28,12 +28,12 @@ class DicomStructureSetRendererFactory::Renderer : public ILayerRenderer { private: - const DicomStructureSet& structureSet_; - CoordinateSystem3D slice_; - bool visible_; + DicomStructureSet& structureSet_; + CoordinateSystem3D slice_; + bool visible_; public: - Renderer(const DicomStructureSet& structureSet, + Renderer(DicomStructureSet& structureSet, const CoordinateSystem3D& slice) : structureSet_(structureSet), slice_(slice), diff -r e66b2c757790 -r e3433dabfb8d Framework/Layers/DicomStructureSetRendererFactory.h --- a/Framework/Layers/DicomStructureSetRendererFactory.h Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Layers/DicomStructureSetRendererFactory.h Fri Oct 06 17:25:08 2017 +0200 @@ -31,10 +31,10 @@ private: class Renderer; - const DicomStructureSet& structureSet_; + DicomStructureSet& structureSet_; public: - DicomStructureSetRendererFactory(const DicomStructureSet& structureSet) : + DicomStructureSetRendererFactory(DicomStructureSet& structureSet) : structureSet_(structureSet) { } diff -r e66b2c757790 -r e3433dabfb8d Framework/Toolbox/CoordinateSystem3D.cpp --- a/Framework/Toolbox/CoordinateSystem3D.cpp Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Fri Oct 06 17:25:08 2017 +0200 @@ -121,7 +121,23 @@ { SetupCanonical(); } - } + } + + + CoordinateSystem3D::CoordinateSystem3D(const Orthanc::DicomMap& dicom) + { + std::string a, b; + + if (dicom.CopyToString(a, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dicom.CopyToString(b, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + Setup(a, b); + } + else + { + SetupCanonical(); + } + } Vector CoordinateSystem3D::MapSliceToWorldCoordinates(double x, diff -r e66b2c757790 -r e3433dabfb8d Framework/Toolbox/CoordinateSystem3D.h --- a/Framework/Toolbox/CoordinateSystem3D.h Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Fri Oct 06 17:25:08 2017 +0200 @@ -61,6 +61,8 @@ Setup(imagePositionPatient, imageOrientationPatient); } + CoordinateSystem3D(const Orthanc::DicomMap& dicom); + const Vector& GetNormal() const { return normal_; diff -r e66b2c757790 -r e3433dabfb8d Framework/Toolbox/DicomStructureSet.cpp --- a/Framework/Toolbox/DicomStructureSet.cpp Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Fri Oct 06 17:25:08 2017 +0200 @@ -36,6 +36,7 @@ static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); + static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); @@ -72,96 +73,81 @@ GeometryToolbox::ParseVector(target, value)); } - CoordinateSystem3D DicomStructureSet:: - ExtractSliceGeometry(double& sliceThickness, - OrthancPlugins::IOrthancConnection& orthanc, - const OrthancPlugins::IDicomDataset& tags, - size_t contourIndex, - size_t sliceIndex) - { - using namespace OrthancPlugins; - - size_t size; - if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex, - DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex, - DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) || - size != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - DicomDatasetReader reader(tags); - std::string parentUid = reader.GetMandatoryStringValue - (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex, - DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex, - DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, - DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)); - - Json::Value parentLookup; - MessagingToolbox::RestApiPost(parentLookup, orthanc, "/tools/lookup", parentUid); - if (parentLookup.type() != Json::arrayValue || - parentLookup.size() != 1 || - !parentLookup[0].isMember("Type") || - !parentLookup[0].isMember("Path") || - parentLookup[0]["Type"].type() != Json::stringValue || - parentLookup[0]["ID"].type() != Json::stringValue || - parentLookup[0]["Type"].asString() != "Instance") + void DicomStructureSet::Polygon::CheckPoint(const Vector& v) + { + if (hasSlice_) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + if (!GeometryToolbox::IsNear(GeometryToolbox::ProjectAlongNormal(v, normal_), + projectionAlongNormal_, + sliceThickness_ / 2.0 /* in mm */)) + { + LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } } - - Json::Value parentInstance; - MessagingToolbox::RestApiGet(parentInstance, orthanc, "/instances/" + parentLookup[0]["ID"].asString()); - - if (parentInstance.type() != Json::objectValue || - !parentInstance.isMember("ParentSeries") || - parentInstance["ParentSeries"].type() != Json::stringValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } + } - std::string parentSeriesId = parentInstance["ParentSeries"].asString(); - bool isFirst = parentSeriesId_.empty(); - if (isFirst) - { - parentSeriesId_ = parentSeriesId; - } - else if (parentSeriesId_ != parentSeriesId) + void DicomStructureSet::Polygon::AddPoint(const Vector& v) + { + CheckPoint(v); + points_.push_back(v); + } + + + bool DicomStructureSet::Polygon::UpdateReferencedSlice(const ReferencedSlices& slices) + { + if (hasSlice_) { - LOG(ERROR) << "This RT-STRUCT refers to several different series"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - FullOrthancDataset parentTags(orthanc, "/instances/" + parentLookup[0]["ID"].asString() + "/tags"); - CoordinateSystem3D slice(parentTags); - - Vector v; - if (ParseVector(v, parentTags, DICOM_TAG_SLICE_THICKNESS) && - v.size() > 0) - { - sliceThickness = v[0]; + return true; } else { - sliceThickness = 1; // 1 mm by default - } + ReferencedSlices::const_iterator it = slices.find(sopInstanceUid_); + + if (it == slices.end()) + { + return false; + } + else + { + const CoordinateSystem3D& geometry = it->second.geometry_; + + hasSlice_ = true; + normal_ = geometry.GetNormal(); + projectionAlongNormal_ = GeometryToolbox::ProjectAlongNormal(geometry.GetOrigin(), normal_); + sliceThickness_ = it->second.thickness_; - if (isFirst) - { - normal_ = slice.GetNormal(); + for (Points::const_iterator it = points_.begin(); it != points_.end(); ++it) + { + CheckPoint(*it); + } + + return true; + } } - else if (!GeometryToolbox::IsParallel(normal_, slice.GetNormal())) - { - LOG(ERROR) << "Incompatible orientation of slices in this RT-STRUCT"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - return slice; } + bool DicomStructureSet::Polygon::IsOnSlice(const CoordinateSystem3D& slice) const + { + bool isOpposite; + + if (points_.empty() || + !hasSlice_ || + !GeometryToolbox::IsParallelOrOpposite(isOpposite, slice.GetNormal(), normal_)) + { + return false; + } + + double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), normal_); + + return (GeometryToolbox::IsNear(d, projectionAlongNormal_, + sliceThickness_ / 2.0)); + } + + const DicomStructureSet::Structure& DicomStructureSet::GetStructure(size_t index) const { if (index >= structures_.size()) @@ -173,22 +159,10 @@ } - bool DicomStructureSet::IsPolygonOnSlice(const Polygon& polygon, - const CoordinateSystem3D& geometry) const - { - double d = boost::numeric::ublas::inner_prod(geometry.GetOrigin(), normal_); - - return (GeometryToolbox::IsNear(d, polygon.projectionAlongNormal_, polygon.sliceThickness_ / 2.0) && - !polygon.points_.empty()); - } - - - DicomStructureSet::DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId) + DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags) { using namespace OrthancPlugins; - FullOrthancDataset tags(orthanc, "/instances/" + instanceId + "/tags"); DicomDatasetReader reader(tags); size_t count, tmp; @@ -204,13 +178,15 @@ structures_.resize(count); for (size_t i = 0; i < count; i++) { - structures_[i].interpretation_ = reader.GetStringValue(DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, - DICOM_TAG_RT_ROI_INTERPRETED_TYPE), - "No interpretation"); + structures_[i].interpretation_ = reader.GetStringValue + (DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, + DICOM_TAG_RT_ROI_INTERPRETED_TYPE), + "No interpretation"); - structures_[i].name_ = reader.GetStringValue(DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, - DICOM_TAG_ROI_NAME), - "No interpretation"); + structures_[i].name_ = reader.GetStringValue + (DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, + DICOM_TAG_ROI_NAME), + "No interpretation"); Vector color; if (ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, @@ -246,31 +222,45 @@ { unsigned int countPoints; - if (!reader.GetUnsignedIntegerValue(countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_NUMBER_OF_CONTOUR_POINTS))) + if (!reader.GetUnsignedIntegerValue + (countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, j, + DICOM_TAG_NUMBER_OF_CONTOUR_POINTS))) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; - std::string type = reader.GetMandatoryStringValue(DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_CONTOUR_GEOMETRIC_TYPE)); + std::string type = reader.GetMandatoryStringValue + (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, j, + DICOM_TAG_CONTOUR_GEOMETRIC_TYPE)); if (type != "CLOSED_PLANAR") { LOG(ERROR) << "Cannot handle contour with geometry type: " << type; throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - // The "CountourData" tag (3006,0050) is too large to be - // returned by the "/instances/{id}/tags" URI: Access it using - // the raw "/instances/{id}/content/{...}" endpoint - std::string slicesData; - orthanc.RestApiGet(slicesData, "/instances/" + instanceId + "/content/3006-0039/" + - boost::lexical_cast(i) + "/3006-0040/" + - boost::lexical_cast(j) + "/3006-0050"); + size_t size; + if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, j, + DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) || + size != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + std::string sopInstanceUid = reader.GetMandatoryStringValue + (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, j, + DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, + DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)); + + std::string slicesData = reader.GetMandatoryStringValue + (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, j, + DICOM_TAG_CONTOUR_DATA)); Vector points; if (!GeometryToolbox::ParseVector(points, slicesData) || @@ -279,9 +269,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } - Polygon polygon; - CoordinateSystem3D geometry = ExtractSliceGeometry(polygon.sliceThickness_, orthanc, tags, i, j); - polygon.projectionAlongNormal_ = geometry.ProjectAlongNormal(geometry.GetOrigin()); + Polygon polygon(sopInstanceUid); for (size_t k = 0; k < countPoints; k++) { @@ -289,27 +277,12 @@ v[0] = points[3 * k]; v[1] = points[3 * k + 1]; v[2] = points[3 * k + 2]; - - if (!GeometryToolbox::IsNear(geometry.ProjectAlongNormal(v), - polygon.projectionAlongNormal_, - polygon.sliceThickness_ / 2.0 /* in mm */)) - { - LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - polygon.points_.push_back(v); + polygon.AddPoint(v); } structures_[i].polygons_.push_back(polygon); } } - - if (parentSeriesId_.empty() || - normal_.size() != 3) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } } @@ -329,9 +302,9 @@ for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { - if (!polygon->points_.empty()) + if (!polygon->GetPoints().empty()) { - center += polygon->points_.front() / n; + center += polygon->GetPoints().front() / n; } } @@ -364,48 +337,37 @@ void DicomStructureSet::Render(CairoContext& context, - const CoordinateSystem3D& slice) const + const CoordinateSystem3D& slice) { cairo_t* cr = context.GetObject(); - for (Structures::const_iterator structure = structures_.begin(); + for (Structures::iterator structure = structures_.begin(); structure != structures_.end(); ++structure) { - if (structure->name_ != "SKIN" && - structure->name_ != "HEART" && - //structure->name_ != "CORD" && - structure->name_ != "ESOPHAGUS" && - structure->name_ != "LUNG_LT" && - structure->name_ != "LUNG_RT" && - structure->name_ != "GTV_EXH_PRIMARY" && - structure->name_ != "GTV_INH_PRIMARY" && - structure->name_ != "GTV_PRIMARY") - { - continue; - } - - for (Polygons::const_iterator polygon = structure->polygons_.begin(); + for (Polygons::iterator polygon = structure->polygons_.begin(); polygon != structure->polygons_.end(); ++polygon) { - if (IsPolygonOnSlice(*polygon, slice)) + polygon->UpdateReferencedSlice(referencedSlices_); + + if (polygon->IsOnSlice(slice)) { context.SetSourceColor(structure->red_, structure->green_, structure->blue_); - Points::const_iterator p = polygon->points_.begin(); + Points::const_iterator p = polygon->GetPoints().begin(); double x, y; slice.ProjectPoint(x, y, *p); cairo_move_to(cr, x, y); ++p; - while (p != polygon->points_.end()) + while (p != polygon->GetPoints().end()) { slice.ProjectPoint(x, y, *p); cairo_line_to(cr, x, y); ++p; } - slice.ProjectPoint(x, y, *polygon->points_.begin()); + slice.ProjectPoint(x, y, *polygon->GetPoints().begin()); cairo_line_to(cr, x, y); cairo_stroke(cr); @@ -413,4 +375,161 @@ } } } + + + void DicomStructureSet::GetReferencedInstances(std::set& instances) + { + for (Structures::const_iterator structure = structures_.begin(); + structure != structures_.end(); ++structure) + { + for (Polygons::const_iterator polygon = structure->polygons_.begin(); + polygon != structure->polygons_.end(); ++polygon) + { + instances.insert(polygon->GetSopInstanceUid()); + } + } + } + + + void DicomStructureSet::AddReferencedSlice(const std::string& sopInstanceUid, + const std::string& seriesInstanceUid, + const CoordinateSystem3D& geometry, + double thickness) + { + if (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end()) + { + // This geometry is already known + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + if (thickness < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (!referencedSlices_.empty()) + { + const ReferencedSlice& reference = referencedSlices_.begin()->second; + + if (reference.seriesInstanceUid_ != seriesInstanceUid) + { + LOG(ERROR) << "This RT-STRUCT refers to several different series"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!GeometryToolbox::IsParallel(reference.geometry_.GetNormal(), geometry.GetNormal())) + { + LOG(ERROR) << "The slices in this RT-STRUCT are not parallel"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + referencedSlices_[sopInstanceUid] = ReferencedSlice(seriesInstanceUid, geometry, thickness); + } + } + + + void DicomStructureSet::AddReferencedSlice(const Orthanc::DicomMap& dataset) + { + CoordinateSystem3D slice(dataset); + + double thickness = 1; // 1 mm by default + + std::string s; + Vector v; + if (dataset.CopyToString(s, Orthanc::DICOM_TAG_SLICE_THICKNESS, false) && + GeometryToolbox::ParseVector(v, s) && + v.size() > 0) + { + thickness = v[0]; + } + + std::string instance, series; + if (dataset.CopyToString(instance, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) && + dataset.CopyToString(series, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + AddReferencedSlice(instance, series, slice, thickness); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void DicomStructureSet::CheckReferencedSlices() + { + for (Structures::iterator structure = structures_.begin(); + structure != structures_.end(); ++structure) + { + for (Polygons::iterator polygon = structure->polygons_.begin(); + polygon != structure->polygons_.end(); ++polygon) + { + if (!polygon->UpdateReferencedSlice(referencedSlices_)) + { + LOG(ERROR) << "Missing information about referenced instance: " + << polygon->GetSopInstanceUid(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + } + } + + + Vector DicomStructureSet::GetNormal() const + { + if (!referencedSlices_.empty()) + { + Vector v; + GeometryToolbox::AssignVector(v, 0, 0, 1); + return v; + } + else + { + return referencedSlices_.begin()->second.geometry_.GetNormal(); + } + } + + + DicomStructureSet* DicomStructureSet::SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId) + { + const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; + OrthancPlugins::FullOrthancDataset dataset(orthanc, uri); + + std::auto_ptr result(new DicomStructureSet(dataset)); + + std::set instances; + result->GetReferencedInstances(instances); + + for (std::set::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + Json::Value lookup; + MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it); + + 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") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + OrthancPlugins::FullOrthancDataset slice + (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags"); + Orthanc::DicomMap m; + MessagingToolbox::ConvertDataset(m, slice); + result->AddReferencedSlice(m); + } + + result->CheckReferencedSlices(); + + return result.release(); + } + } diff -r e66b2c757790 -r e3433dabfb8d Framework/Toolbox/DicomStructureSet.h --- a/Framework/Toolbox/DicomStructureSet.h Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Toolbox/DicomStructureSet.h Fri Oct 06 17:25:08 2017 +0200 @@ -24,7 +24,7 @@ #include "CoordinateSystem3D.h" #include "../Viewport/CairoContext.h" -#include +#include #include @@ -33,13 +33,64 @@ class DicomStructureSet : public boost::noncopyable { private: + struct ReferencedSlice + { + std::string seriesInstanceUid_; + CoordinateSystem3D geometry_; + double thickness_; + + ReferencedSlice() + { + } + + ReferencedSlice(const std::string& seriesInstanceUid, + const CoordinateSystem3D& geometry, + double thickness) : + seriesInstanceUid_(seriesInstanceUid), + geometry_(geometry), + thickness_(thickness) + { + } + }; + + typedef std::map ReferencedSlices; + typedef std::list Points; - struct Polygon + class Polygon { - double projectionAlongNormal_; - double sliceThickness_; // In millimeters - Points points_; + private: + std::string sopInstanceUid_; + bool hasSlice_; + Vector normal_; + double projectionAlongNormal_; + double sliceThickness_; // In millimeters + Points points_; + + void CheckPoint(const Vector& v); + + public: + Polygon(const std::string& sopInstanceUid) : + sopInstanceUid_(sopInstanceUid), + hasSlice_(false) + { + } + + void AddPoint(const Vector& v); + + bool UpdateReferencedSlice(const ReferencedSlices& slices); + + bool IsOnSlice(const CoordinateSystem3D& geometry) const; + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + const Points& GetPoints() const + { + return points_; + } }; typedef std::list Polygons; @@ -56,25 +107,13 @@ typedef std::vector Structures; - Structures structures_; - std::string parentSeriesId_; - Vector normal_; - - CoordinateSystem3D ExtractSliceGeometry(double& sliceThickness, - OrthancPlugins::IOrthancConnection& orthanc, - const OrthancPlugins::IDicomDataset& tags, - size_t contourIndex, - size_t sliceIndex); + Structures structures_; + ReferencedSlices referencedSlices_; const Structure& GetStructure(size_t index) const; - bool IsPolygonOnSlice(const Polygon& polygon, - const CoordinateSystem3D& geometry) const; - - public: - DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId); + DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance); size_t GetStructureCount() const { @@ -92,12 +131,23 @@ uint8_t& blue, size_t index) const; - const Vector& GetNormal() const - { - return normal_; - } + void GetReferencedInstances(std::set& instances); + + void AddReferencedSlice(const std::string& sopInstanceUid, + const std::string& seriesInstanceUid, + const CoordinateSystem3D& geometry, + double thickness); + + void AddReferencedSlice(const Orthanc::DicomMap& dataset); + + void CheckReferencedSlices(); void Render(CairoContext& context, - const CoordinateSystem3D& slice) const; + const CoordinateSystem3D& slice); + + Vector GetNormal() const; + + static DicomStructureSet* SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId); }; } diff -r e66b2c757790 -r e3433dabfb8d Framework/Toolbox/GeometryToolbox.h --- a/Framework/Toolbox/GeometryToolbox.h Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Toolbox/GeometryToolbox.h Fri Oct 06 17:25:08 2017 +0200 @@ -112,5 +112,11 @@ void GetPixelSpacing(double& spacingX, double& spacingY, const Orthanc::DicomMap& dicom); + + inline double ProjectAlongNormal(const Vector& point, + const Vector& normal) + { + return boost::numeric::ublas::inner_prod(point, normal); + } }; } diff -r e66b2c757790 -r e3433dabfb8d Framework/Toolbox/MessagingToolbox.cpp --- a/Framework/Toolbox/MessagingToolbox.cpp Wed Oct 04 17:53:47 2017 +0200 +++ b/Framework/Toolbox/MessagingToolbox.cpp Fri Oct 06 17:25:08 2017 +0200 @@ -183,11 +183,12 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); } - LOG(WARNING) << "Version of the Orthanc core (must be above 1.1.0): " << version; + LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version; - // Stone is only compatible with Orthanc >= 1.1.0, otherwise deadlocks might occur + // Stone is only compatible with Orthanc >= 1.3.1 if (major < 1 || - (major == 1 && minor < 1)) + (major == 1 && minor < 3) || + (major == 1 && minor == 3 && patch < 1)) { return false; } @@ -437,8 +438,10 @@ AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE); AddTag(target, source, Orthanc::DICOM_TAG_ROWS); AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL); + AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS); AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID); + AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID); AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER); AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH); }