Mercurial > hg > orthanc-stone
changeset 1000:50e5acf5553b
changed RTSTRUCT rendering from polygons to segments
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Fri, 20 Sep 2019 11:59:29 +0200 |
parents | 2d69b8bee484 |
children | e704a53c9d0a |
files | Framework/Loaders/DicomStructureSetLoader.cpp Framework/Loaders/DicomStructureSetLoader.h Framework/Toolbox/DicomStructureSet.cpp Framework/Toolbox/DicomStructureSet.h |
diffstat | 4 files changed, 223 insertions(+), 20 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Loaders/DicomStructureSetLoader.cpp Fri Sep 20 11:58:33 2019 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Fri Sep 20 11:59:29 2019 +0200 @@ -243,7 +243,8 @@ { const Color& color = content_.GetStructureColor(i); - std::vector< std::vector<DicomStructureSet::PolygonPoint2D> > polygons; +#ifdef USE_OLD_SJO_CUT_CODE + std::vector< std::vector<Point2D> > polygons; if (content_.ProjectStructure(polygons, i, cuttingPlane)) { @@ -254,12 +255,29 @@ for (size_t k = 0; k < polygons[j].size(); k++) { - chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second); + chain[k] = ScenePoint2D(polygons[j][k].x, polygons[j][k].y); } layer->AddChain(chain, true /* closed */, color); } } +#else + std::vector< std::pair<Point2D, Point2D> > segments; + + if (content_.ProjectStructure(segments, i, cuttingPlane)) + { + for (size_t j = 0; j < segments.size(); j++) + { + PolylineSceneLayer::Chain chain; + chain.resize(2); + + chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y); + chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y); + + layer->AddChain(chain, false /* NOT closed */, color); + } + } +#endif } return layer.release();
--- a/Framework/Loaders/DicomStructureSetLoader.h Fri Sep 20 11:58:33 2019 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.h Fri Sep 20 11:59:29 2019 +0200 @@ -59,7 +59,7 @@ void LoadInstance(const std::string& instanceId); - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane); + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; void SetStructuresReady();
--- a/Framework/Toolbox/DicomStructureSet.cpp Fri Sep 20 11:58:33 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Fri Sep 20 11:59:29 2019 +0200 @@ -20,6 +20,7 @@ #include "DicomStructureSet.h" +#include "DicomStructureSetUtils.h" #include "../Toolbox/GeometryToolbox.h" @@ -29,6 +30,11 @@ #include <Plugins/Samples/Common/FullOrthancDataset.h> #include <Plugins/Samples/Common/DicomDatasetReader.h> +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4244) +#endif + #include <limits> #include <stdio.h> #include <boost/geometry.hpp> @@ -36,6 +42,10 @@ #include <boost/geometry/geometries/polygon.hpp> #include <boost/geometry/multi/geometries/multi_polygon.hpp> +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + typedef boost::geometry::model::d2::point_xy<double> BoostPoint; typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon; typedef boost::geometry::model::multi_polygon<BoostPolygon> BoostMultiPolygon; @@ -71,6 +81,7 @@ } } +#ifdef USE_OLD_SJO_CUT_CODE static BoostPolygon CreateRectangle(float x1, float y1, float x2, float y2) @@ -83,7 +94,36 @@ return r; } +#else +namespace OrthancStone +{ + static RtStructRectangleInSlab CreateRectangle(float x1, float y1, + float x2, float y2) + { + RtStructRectangleInSlab rect; + rect.xmin = std::min(x1, x2); + rect.xmax = std::max(x1, x2); + rect.ymin = std::min(y1, y2); + rect.ymax = std::max(y1, y2); + return rect; + } + + bool CompareRectanglesForProjection(const std::pair<RtStructRectangleInSlab,double>& r1, const std::pair<RtStructRectangleInSlab, double>& r2) + { + return r1.second < r2.second; + } + + bool CompareSlabsY(const RtStructRectanglesInSlab& r1, const RtStructRectanglesInSlab& r2) + { + if ((r1.size() == 0) || (r2.size() == 0)) + return false; + + return r1[0].ymax < r2[0].ymax; + } +} + +#endif namespace OrthancStone { @@ -267,6 +307,8 @@ if (GeometryToolbox::IsParallelOrOpposite (isOpposite, slice.GetNormal(), geometry_.GetAxisY())) { + // plane is constant Y + if (y < extent_.GetY1() || y > extent_.GetY2()) { @@ -287,6 +329,8 @@ double curX, curY; geometry_.ProjectPoint(curX, curY, points_[i]); + // if prev* and cur* are on opposite sides of y, this means that the + // segment intersects the plane. if ((prevY < y && curY > y) || (prevY > y && curY < y)) { @@ -294,23 +338,33 @@ xmin = std::min(xmin, p); xmax = std::max(xmax, p); isFirst = false; + + // xmin and xmax represent the extent of the rectangle along the + // intersection between the plane and the polygon geometry + } prevX = curX; prevY = curY; } + // if NO segment intersects the plane if (isFirst) { return false; } else { + // y is the plane y coord in the polygon geometry + // xmin and xmax are ALSO expressed in the polygon geometry + + // let's convert them to 3D world geometry... Vector p1 = (geometry_.MapSliceToWorldCoordinates(xmin, y) + sliceThickness_ / 2.0 * geometry_.GetNormal()); Vector p2 = (geometry_.MapSliceToWorldCoordinates(xmax, y) - sliceThickness_ / 2.0 * geometry_.GetNormal()); + // then to the cutting plane geometry... slice.ProjectPoint(x1, y1, p1); slice.ProjectPoint(x2, y2, p2); return true; @@ -319,6 +373,14 @@ else if (GeometryToolbox::IsParallelOrOpposite (isOpposite, slice.GetNormal(), geometry_.GetAxisX())) { + // plane is constant X + + /* + Please read the comments in the section above, by taking into account + the fact that, in this case, the plane has a constant X, not Y (in + polygon geometry_ coordinates) + */ + if (x < extent_.GetX1() || x > extent_.GetX2()) { @@ -741,12 +803,21 @@ } } - - bool DicomStructureSet::ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons, +#ifdef USE_OLD_SJO_CUT_CODE + bool DicomStructureSet::ProjectStructure(std::vector< std::vector<Point2D> >& polygons, const Structure& structure, const CoordinateSystem3D& slice) const +#else + bool DicomStructureSet::ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments, + const Structure& structure, + const CoordinateSystem3D& slice) const +#endif { +#ifdef USE_OLD_SJO_CUT_CODE polygons.clear(); +#else + segments.clear(); +#endif Vector normal = GetNormal(); @@ -760,15 +831,46 @@ { if (polygon->IsOnSlice(slice)) { - polygons.push_back(std::vector<PolygonPoint2D>()); +#ifdef USE_OLD_SJO_CUT_CODE + polygons.push_back(std::vector<Point2D>()); for (Points::const_iterator p = polygon->GetPoints().begin(); p != polygon->GetPoints().end(); ++p) { double x, y; slice.ProjectPoint(x, y, *p); - polygons.back().push_back(std::make_pair(x, y)); + polygons.back().push_back(Point2D(x, y)); } +#else + // we need to add all the segments corresponding to this polygon + const std::vector<Vector>& points3D = polygon->GetPoints(); + if (points3D.size() >= 3) + { + Point2D prev2D; + { + Vector prev = points3D[0]; + double prevX, prevY; + slice.ProjectPoint(prevX, prevY, prev); + prev2D = Point2D(prevX, prevY); + } + + size_t pointCount = points3D.size(); + for (size_t ipt = 1; ipt < pointCount; ++ipt) + { + Vector next = points3D[ipt]; + double nextX, nextY; + slice.ProjectPoint(nextX, nextY, next); + Point2D next2D(nextX, nextY); + segments.push_back(std::pair<Point2D, Point2D>(prev2D, next2D)); + prev2D = next2D; + } + } + else + { + LOG(ERROR) << "Contour with less than 3 points!"; + // !!! + } +#endif } } @@ -779,22 +881,84 @@ { #if 1 // Sagittal or coronal projection + +#ifdef USE_OLD_SJO_CUT_CODE std::vector<BoostPolygon> projected; - +#else + // this will contain the intersection of the polygon slab with + // the cutting plane, projected on the cutting plane coord system + // (that yields a rectangle) + the Z coordinate of the polygon + // (this is required to group polygons with the same Z later) + std::vector<std::pair<RtStructRectangleInSlab, double> > projected; +#endif for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { double x1, y1, x2, y2; + if (polygon->Project(x1, y1, x2, y2, slice)) { - projected.push_back(CreateRectangle( + double curZ = polygon->GetGeometryOrigin()[2]; + + // x1,y1 and x2,y2 are in "slice" coordinates (the cutting plane + // geometry) + projected.push_back(std::make_pair(CreateRectangle( static_cast<float>(x1), static_cast<float>(y1), static_cast<float>(x2), - static_cast<float>(y2))); + static_cast<float>(y2)),curZ)); } } +#ifndef USE_OLD_SJO_CUT_CODE + // projected contains a set of rectangles specified by two opposite + // corners (x1,y1,x2,y2) + // we need to merge them + // each slab yields ONE polygon! + // we need to sorted all the rectangles that originate from the same Z + // into lanes. To make sure they are grouped together in the array, we + // sort it. + std::sort(projected.begin(), projected.end(), CompareRectanglesForProjection); + + std::vector<RtStructRectanglesInSlab> rectanglesForEachSlab; + rectanglesForEachSlab.reserve(projected.size()); + + double curZ = 0; + for (size_t i = 0; i < projected.size(); ++i) + { +#if 0 + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); +#else + if (i == 0) + { + curZ = projected[i].second; + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + } + else + { + // this check is needed to prevent creating a new slab if + // the new polygon is at the same Z coord than last one + if (!LinearAlgebra::IsNear(curZ, projected[i].second)) + { + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + curZ = projected[i].second; + } + } +#endif + + rectanglesForEachSlab.back().push_back(projected[i].first); + + // as long as they have the same y, we should put them into the same lane + // BUT in Sebastien's code, there is only one polygon per lane. + + //std::cout << "rect: xmin = " << rect.xmin << " xmax = " << rect.xmax << " ymin = " << rect.ymin << " ymax = " << rect.ymax << std::endl; + } + + // now we need to sort the slabs in increasing Y order (see ConvertListOfSlabsToSegments) + std::sort(rectanglesForEachSlab.begin(), rectanglesForEachSlab.end(), CompareSlabsY); + + ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, projected.size()); +#else BoostMultiPolygon merged; Union(merged, projected); @@ -806,9 +970,11 @@ polygons[i].resize(outer.size()); for (size_t j = 0; j < outer.size(); j++) { - polygons[i][j] = std::make_pair(outer[j].x(), outer[j].y()); + polygons[i][j] = Point2D(outer[j].x(), outer[j].y()); } } +#endif + #else for (Polygons::iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) @@ -816,7 +982,7 @@ double x1, y1, x2, y2; if (polygon->Project(x1, y1, x2, y2, slice)) { - std::vector<PolygonPoint2D> p(4); + std::vector<Point2D> p(4); p[0] = std::make_pair(x1, y1); p[1] = std::make_pair(x2, y1); p[2] = std::make_pair(x2, y2);
--- a/Framework/Toolbox/DicomStructureSet.h Fri Sep 20 11:58:33 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.h Fri Sep 20 11:59:29 2019 +0200 @@ -21,10 +21,13 @@ #pragma once +#include "DicomStructureSetUtils.h" #include "CoordinateSystem3D.h" #include "Extent2D.h" #include "../Scene2D/Color.h" +//#define USE_OLD_SJO_CUT_CODE 1 + #include <Plugins/Samples/Common/FullOrthancDataset.h> #include <list> @@ -33,9 +36,6 @@ { class DicomStructureSet : public boost::noncopyable { - public: - typedef std::pair<double, double> PolygonPoint2D; - private: struct ReferencedSlice { @@ -93,6 +93,11 @@ bool IsOnSlice(const CoordinateSystem3D& geometry) const; + const Vector& GetGeometryOrigin() const + { + return geometry_.GetOrigin(); + } + const std::string& GetSopInstanceUid() const { return sopInstanceUid_; @@ -136,10 +141,15 @@ Structure& GetStructure(size_t index); - bool ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons, +#ifdef USE_OLD_SJO_CUT_CODE + bool ProjectStructure(std::vector< std::vector<Point2D> >& polygons, const Structure& structure, const CoordinateSystem3D& slice) const; - +#else + bool ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments, + const Structure& structure, + const CoordinateSystem3D& slice) const; +#endif public: DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance); @@ -175,11 +185,20 @@ Vector GetNormal() const; - bool ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons, - size_t index, - const CoordinateSystem3D& slice) const +#ifdef USE_OLD_SJO_CUT_CODE + bool ProjectStructure(std::vector< std::vector<Point2D> >& polygons, + size_t index, + const CoordinateSystem3D& slice) const { return ProjectStructure(polygons, GetStructure(index), slice); } +#else + bool ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments, + size_t index, + const CoordinateSystem3D& slice) const + { + return ProjectStructure(segments, GetStructure(index), slice); + } +#endif }; }