# HG changeset patch # User Sebastien Jodogne # Date 1643701112 -3600 # Node ID affde38b84de03a2367a0a7c676ec2a80ce556e6 # Parent 0208f99b8bdecc5a26146ae7d14deec52c2cea67 moved tentative bgo reimplementation of rt-struct into graveyard diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake --- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Tue Feb 01 07:19:15 2022 +0100 +++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Tue Feb 01 08:38:32 2022 +0100 @@ -403,17 +403,8 @@ ${ORTHANC_STONE_ROOT}/Toolbox/CoordinateSystem3D.h ${ORTHANC_STONE_ROOT}/Toolbox/DicomInstanceParameters.cpp ${ORTHANC_STONE_ROOT}/Toolbox/DicomInstanceParameters.h - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructure2.cpp - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructure2.h - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructurePolygon2.cpp - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructurePolygon2.h ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet.cpp ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet.h - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet2.cpp - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet2.h - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSetUtils.cpp - ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSetUtils.h - ${ORTHANC_STONE_ROOT}/Toolbox/DisjointDataSet.h ${ORTHANC_STONE_ROOT}/Toolbox/DynamicBitmap.cpp ${ORTHANC_STONE_ROOT}/Toolbox/DynamicBitmap.h ${ORTHANC_STONE_ROOT}/Toolbox/Extent2D.cpp @@ -469,8 +460,6 @@ ${ORTHANC_STONE_ROOT}/Volumes/VolumeReslicer.h ${ORTHANC_STONE_ROOT}/Volumes/VolumeSceneLayerSource.cpp ${ORTHANC_STONE_ROOT}/Volumes/VolumeSceneLayerSource.h - ${ORTHANC_STONE_ROOT}/Volumes/DicomStructureSetSlicer2.cpp - ${ORTHANC_STONE_ROOT}/Volumes/DicomStructureSetSlicer2.h ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.h ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.cpp ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.h diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,297 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructure2.h" + +#include "GeometryToolbox.h" +#include "DisjointDataSet.h" + +#include + +namespace OrthancStone +{ + // see header + //void DicomStructure2::ComputeNormal() + //{ + // try + // { + // if (polygons_.size() > 0) + // { + + // // TODO: check all polygons are OK + // const DicomStructurePolygon2 polygon = polygons_[0]; + // $$$$$$$$$$$$$$$$$ + // state_ = NormalComputed; + // } + // else + // { + // // bogus! no polygons. Let's assign a "nothing here" value + // LinearAlgebra::AssignVector(normal_, 0, 0, 0); + // state_ = Invalid; + // } + // } + // catch (const Orthanc::OrthancException& e) + // { + // state_ = Invalid; + // if (e.HasDetails()) + // { + // LOG(ERROR) << "OrthancException in ComputeNormal: " << e.What() << " Details: " << e.GetDetails(); + // } + // else + // { + // LOG(ERROR) << "OrthancException in ComputeNormal: " << e.What(); + // } + // throw; + // } + // catch (const std::exception& e) + // { + // state_ = Invalid; + // LOG(ERROR) << "std::exception in ComputeNormal: " << e.what(); + // throw; + // } + // catch (...) + // { + // state_ = Invalid; + // LOG(ERROR) << "Unknown exception in ComputeNormal"; + // throw; + // } + //} + + void DicomStructure2::ComputeSliceThickness() + { + if (state_ != NormalComputed) + { + LOG(ERROR) << "DicomStructure2::ComputeSliceThickness - state must be NormalComputed"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (polygons_.size() < 2) + { + // cannot compute thickness if there are not at least 2 slabs (structures) + sliceThickness_ = 1.0; + state_ = Invalid; + } + else + { + // normal can be (1,0,0), (0,1,0) or (0,0,1), nothing else. + // these can be compared with == (exact double representation) + if (normal_[0] == 1) + { + // in a single polygon, all the points have the same X + sliceThickness_ = fabs(polygons_[0].GetPoint(0)[0] - polygons_[1].GetPoint(0)[0]); + } + else if (normal_[1] == 1) + { + // in a single polygon, all the points have the same X + sliceThickness_ = fabs(polygons_[0].GetPoint(0)[1] - polygons_[1].GetPoint(0)[1]); + } + else if (normal_[2] == 1) + { + // in a single polygon, all the points have the same X + sliceThickness_ = fabs(polygons_[0].GetPoint(0)[2] - polygons_[1].GetPoint(0)[2]); + } + else + { + ORTHANC_ASSERT(false); + state_ = Invalid; + } + } + state_ = Valid; + } + + void DicomStructure2::AddPolygon(const DicomStructurePolygon2& polygon) + { + if (state_ != Building) + { + LOG(ERROR) << "DicomStructure2::AddPolygon - can only add polygon while building"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + polygons_.push_back(polygon); + } + + void DicomStructure2::ComputeDependentProperties() + { + if (state_ != Building) + { + LOG(ERROR) << "DicomStructure2::ComputeDependentProperties - can only be called once"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + for (size_t i = 0; i < polygons_.size(); ++i) + { + // "compute" the polygon normal + polygons_[i].ComputeDependentProperties(); + } + if (polygons_.size() > 0) + { + normal_ = polygons_[0].GetNormal(); + state_ = NormalComputed; + } + else + { + LinearAlgebra::AssignVector(normal_, 0, 0, 0); + state_ = Invalid; // THIS MAY HAPPEN !!! (for instance for instance 72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661 :) ) + } + if (polygons_.size() >= 2) + ComputeSliceThickness(); // this will change state_ from NormalComputed to Valid + } + + Vector DicomStructure2::GetNormal() const + { + if (state_ != Valid && state_ != Invalid) + { + LOG(ERROR) << "DicomStructure2::GetNormal() -- please call ComputeDependentProperties first."; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (state_ == Invalid) + { + LOG(ERROR) << "DicomStructure2::GetNormal() -- The Dicom structure is invalid. The normal is set to 0,0,0"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + return normal_; + } + + const DicomStructurePolygon2* DicomStructure2::GetPolygonClosestToSlice( + const CoordinateSystem3D& plane) const + { + ORTHANC_ASSERT(state_ == Valid); + + // we assume 0,0,1 for now + ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0)); + ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0)); + + for (size_t i = 0; i < polygons_.size(); ++i) + { + const DicomStructurePolygon2& polygon = polygons_[i]; + + // "height" of cutting plane + double cutZ = plane.GetOrigin()[2]; + + if (LinearAlgebra::IsNear( + cutZ, polygon.GetZ(), + sliceThickness_ / 2.0 /* in mm */)) + return &polygon; + } + return NULL; + } + + + bool DicomStructure2::Project(std::vector< std::pair > & segments, const CoordinateSystem3D & plane) const + { + segments.clear(); + + Vector normal = GetNormal(); + + size_t totalRectCount = 0; + + // dummy var + bool isOpposite = false; + + // This is an axial projection + if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, plane.GetNormal())) + { + const DicomStructurePolygon2* polygon = GetPolygonClosestToSlice(plane); + if (polygon) + { + polygon->ProjectOnParallelPlane(segments, plane); + } + } + else + { + // let's compute the dot product of the plane normal and the polygons + // normal. + double dot = LinearAlgebra::DotProduct(plane.GetNormal(), normal); + + if (LinearAlgebra::IsNear(dot, 0)) + { + // Coronal or sagittal projection + + // vector of vector of rectangles that will be merged in a single big contour: + + // each polygon slab cut by a perpendicular plane yields 0..* rectangles + std::vector< RtStructRectanglesInSlab > rectanglesForEachSlab; + + for (size_t i = 0; i < polygons_.size(); ++i) + { + // book an entry for this slab + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + + // let's compute the intersection between the polygon and the plane + // intersections are in plane coords + std::vector intersections; + + polygons_[i].ProjectOnConstantPlane(intersections, plane); + + // for each pair of intersections, we add a rectangle. + if ((intersections.size() % 2) != 0) + { + LOG(WARNING) << "Odd number of intersections between structure " + << name_ << ", polygon # " << i + << " and plane where X axis is parallel to polygon normal vector"; + } + + size_t numRects = intersections.size() / 2; + + // we keep count of the total number of rects for vector pre-allocations + totalRectCount += numRects; + + for (size_t iRect = 0; iRect < numRects; ++iRect) + { + RtStructRectangleInSlab rectangle; + ORTHANC_ASSERT(LinearAlgebra::IsNear(intersections[2 * iRect].GetY(), intersections[2 * iRect + 1].GetY())); + ORTHANC_ASSERT((2 * iRect + 1) < intersections.size()); + double x1 = intersections[2 * iRect].GetX(); + double x2 = intersections[2 * iRect + 1].GetX(); + double y1 = intersections[2 * iRect].GetY() - sliceThickness_ * 0.5; + double y2 = intersections[2 * iRect].GetY() + sliceThickness_ * 0.5; + + rectangle.xmin = std::min(x1, x2); + rectangle.xmax = std::max(x1, x2); + rectangle.ymin = std::min(y1, y2); + rectangle.ymax = std::max(y1, y2); + + // TODO: keep them sorted!!!! + + rectanglesForEachSlab.back().push_back(rectangle); + } + } + // now we need to merge all the slabs into a set of polygons (1 or more) + ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, totalRectCount); + } + else + { + // plane is not perpendicular to the polygons + // 180.0 / [Math]::Pi = 57.2957795130823 + double acDot = 57.2957795130823 * acos(dot); + LOG(ERROR) << "DicomStructure2::Project -- cutting plane must be " + << "perpendicular to the structures, but dot product is: " + << dot << " and (180/pi)*acos(dot) = " << acDot; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + return segments.size() != 0; + } +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructure2.h Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,163 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#pragma once + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructurePolygon2.h" +#include "DicomStructureSetUtils.h" + +namespace OrthancStone +{ + + /* + A structure has a color, a name, a set of slices.. + + Each slice is a polygon. + */ + struct DicomStructure2 + { + DicomStructure2() : + red_(0), green_(0), blue_(0), sliceThickness_(0), state_(Building) {} + + void AddPolygon(const DicomStructurePolygon2& polygon); + + /** + Once all polygons have been added, this method will determine: + - the slice orientation (through the normal vector) + - the spacing between slices (slice thickness) + + it will also set up the info required to efficiently compute plane + intersections later on. + */ + void ComputeDependentProperties(); + + /** + Being given a plane that is PARALLEL to the set of polygon structures, this + returns a pointer to the polygon located at that position (if it is closer + than thickness/2) or NULL if there is none. + + TODO: use sorted vector to improve + + DO NOT STORE THE RETURNED POINTER! + */ + const DicomStructurePolygon2* GetPolygonClosestToSlice(const CoordinateSystem3D& plane) const; + + Vector GetNormal() const; + + Color GetColor() const + { + return Color(red_, green_, blue_); + } + + bool IsValid() const + { + return state_ == Valid; + } + + /** + This method is used to project the 3D structure on a 2D plane. + + A structure is a stack of polygons, representing a volume. + + We need to compute the intersection between this volume and the supplied + cutting plane (the "slice"). This is more than a cutting plane: it is also + a 2D-coordinate system (the plane has axes vectors) + + The cutting plane is always parallel to the plane defined by two of the + world coordinate system axes. + + The result is a set of closed polygons. + + If the cut is parallel to the polygons, we pick the polygon closest to + the slice, project it on the slice and return it in slice coordinates. + + If the cut is perpendicular to the polygons, for each polygon, we compute + the intersection between the cutting plane and the polygon slab (imaginary + volume created by extruding the polygon above and below its plane by + thickness/2) : + - each slab, intersected by the plane, gives a set of 0..* rectangles \ + (only one if the polygon is convex) + - when doing this for the whole stack of slabs, we get a set of rectangles: + To compute these rectangles, for each polygon, we compute the intersection + between : + - the line defined by the intersection of the polygon plane and the cutting + plane + - the polygon itself + This yields 0 or 2*K points along the line C. These are turned into K + rectangles by taking two consecutive points along the line and extruding + this segment by sliceThickness/2 in the orientation of the polygon normal, + in both directions. + + Then, once this list of rectangles is computed, we need to group the + connected rectangles together. Connected, here, means sharing at least part + of an edge --> union/find data structures and algorithm. + */ + bool Project(std::vector< std::pair >& polygons, const CoordinateSystem3D& plane) const; + + std::string interpretation_; + std::string name_; + uint8_t red_; + uint8_t green_; + uint8_t blue_; + + /** Internal */ + const std::vector& GetPolygons() const + { + return polygons_; + } + + /** Internal */ + double GetSliceThickness() const + { + return sliceThickness_; + } + + private: + enum State + { + Building, + NormalComputed, + Valid, // When normal components AND slice thickness are computed + Invalid + }; + + void ComputeNormal(); + void ComputeSliceThickness(); + + std::vector polygons_; + Vector normal_; + double sliceThickness_; + + /* + After creation (and while polygons are added), state is Building. + After ComputeDependentProperties() is called, state can either be + Valid or Invalid. In any case, the object becomes immutable. + */ + State state_; + }; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,307 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructurePolygon2.h" + +#include "../Toolbox/LinearAlgebra.h" + +#include + +namespace OrthancStone +{ + void DicomStructurePolygon2::ComputeDependentProperties() + { + ORTHANC_ASSERT(state_ == Building); + + for (size_t j = 0; j < points_.size(); ++j) + { + // TODO: move to AddPoint! + const Vector& p = points_[j]; + if (p[0] < minX_) + minX_ = p[0]; + if (p[0] > maxX_) + maxX_ = p[0]; + + if (p[1] < minY_) + minY_ = p[1]; + if (p[1] > maxY_) + maxY_ = p[1]; + + if (p[2] < minZ_) + minZ_ = p[2]; + if (p[2] > maxZ_) + maxZ_ = p[2]; + } + + if (LinearAlgebra::IsNear(minX_, maxX_)) + { + LinearAlgebra::AssignVector(normal_, 1, 0, 0); + //ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX, maxX)); + ORTHANC_ASSERT(!LinearAlgebra::IsNear(minY_, maxY_)); + ORTHANC_ASSERT(!LinearAlgebra::IsNear(minZ_, maxZ_)); + } + else if (LinearAlgebra::IsNear(minY_, maxY_)) + { + LinearAlgebra::AssignVector(normal_, 0, 1, 0); + ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX_, maxX_)); + ORTHANC_ASSERT(!LinearAlgebra::IsNear(minZ_, maxZ_)); + } + else if (LinearAlgebra::IsNear(minZ_, maxZ_)) + { + LinearAlgebra::AssignVector(normal_, 0, 0, 1); + ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX_, maxX_)); + ORTHANC_ASSERT(!LinearAlgebra::IsNear(minY_, maxY_)); + } + else + { + LOG(ERROR) << "The contour is not coplanar and not parallel to any axis."; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + state_ = Valid; + } + + + void DicomStructurePolygon2::ProjectOnConstantPlane( + std::vector& intersections, const CoordinateSystem3D& plane) const + { + // the plane can either have constant X, or constant Y. + // - for constant Z planes, use the ProjectOnParallelPlane method + // - other type of planes are not supported + + // V is the coordinate that is constant in the plane + double planeV = 0.0; + + // if true, then "u" in the code is "x" and "v" is "y". + // (v is constant in the plane) + bool uvxy = false; + + size_t uindex = static_cast(-1); + size_t vindex = static_cast(-1); + + ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[2], 0.0)); + + if (LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0)) + { + // normal is 1,0,0 (or -1,0,0). + // plane is constant X + uindex = 1; + vindex = 0; + + uvxy = false; + planeV = plane.GetOrigin()[0]; + if (planeV < minX_) + return; + if (planeV > maxX_) + return; + } + else if (LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0)) + { + // normal is 0,1,0 (or 0,-1,0). + // plane is constant Y + uindex = 0; + vindex = 1; + + uvxy = true; + planeV = plane.GetOrigin()[1]; + if (planeV < minY_) + return; + if (planeV > maxY_) + return; + } + else + { + // if the following assertion(s) fail(s), it means the plane is NOT a constant-X or constant-Y plane + LOG(ERROR) << "Plane normal must be (a,0,0) or (0,a,0), with a == -1 or a == 1"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + size_t pointCount = GetPointCount(); + if (pointCount >= 3) + { + // this vector will contain the coordinates of the intersection points + // between the plane and the polygon. + // these are expressed in the U coordinate, that is either X or Y, + // depending upon the plane orientation + std::vector uIntersections; + + // we loop on the segments of the polygon (TODO: optimize) + // and we compute the intersection between each segment and the cut + // cutting plane (slice) has a constant X + + for (size_t iPoint = 0; iPoint < pointCount; ++iPoint) + { + double u1 = points_[iPoint][uindex]; + double v1 = points_[iPoint][vindex]; + + double u2 = 0; + double v2 = 0; + + if (iPoint < pointCount - 1) + { + u2 = points_[iPoint + 1][uindex]; + v2 = points_[iPoint + 1][vindex]; + } + else + { + u2 = points_[0][uindex]; + v2 = points_[0][vindex]; + } + + // Check if the segment intersects the plane + if ((std::min(v1, v2) <= planeV) && (std::max(v1, v2) >= planeV)) + { + // special case: the segment is parallel to the plane but close to it + if (LinearAlgebra::IsNear(v1, v2)) + { + // in that case, we choose to label both points as an intersection + double x, y; + plane.ProjectPoint(x, y, points_[iPoint]); + intersections.push_back(ScenePoint2D(x, y)); + + plane.ProjectPoint(x, y, points_[iPoint + 1]); + intersections.push_back(ScenePoint2D(x, y)); + } + else + { + // we are looking for u so that (u,planeV) belongs to the segment + // let's define alpha = (u-u2)/(u1-u2) --> u = alpha*(u1-u2) + u2 + // alpha = (v2-planeV)/(v2-v1) + // because the following two triangles are similar + // [ (planeY,x) , (y2,x2), (planeY,x2) ] or + // [ (planeX,y) , (x2,y2), (planeX,y2) ] + // and + // [ (y1 ,x1) , (y2,x2), (y1 ,x2) ] or + // [ (x1 ,y1) , (x2,y2), (x1 ,y2) ] + + /* + void CoordinateSystem3D::ProjectPoint(double& offsetX, + double& offsetY, + const Vector& point) const + */ + double alpha = (v2 - planeV) / (v2 - v1); + + // get rid of numerical oddities + if (alpha < 0.0) + alpha = 0.0; + if (alpha > 1.0) + alpha = 1.0; + double u = alpha * (u1 - u2) + u2; + + // here is the intersection in world coordinates + Vector intersection; + if(uvxy) + LinearAlgebra::AssignVector(intersection, u, planeV, minZ_); + else + LinearAlgebra::AssignVector(intersection, planeV, u, minZ_); + + // and we convert it to plane coordinates + { + double xi, yi; + plane.ProjectPoint(xi, yi, intersection); + + // we consider that the x axis is always parallel to the polygons + // TODO: is this hypothesis safe?????? + uIntersections.insert(std::lower_bound(uIntersections.begin(), uIntersections.end(), xi), xi); + } + } + } + } // end of for (size_t iPoint = 0; iPoint < pointCount; ++iPoint) + + // now we convert the intersections to plane points + // we consider that the x axis is always parallel to the polygons + // TODO: same hypothesis as above: plane is perpendicular to polygons, + // plane is parallel to the XZ (constant Y) or YZ (constant X) 3D planes + for (size_t i = 0; i < uIntersections.size(); ++i) + { + double x = uIntersections[i]; + intersections.push_back(ScenePoint2D(x, minZ_)); + } + } // end of if (pointCount >= 3) + else + { + LOG(ERROR) << "This polygon has " << pointCount << " vertices, which is less than 3 --> skipping"; + } + } + + void DicomStructurePolygon2::ProjectOnParallelPlane( + std::vector< std::pair >& segments, + const CoordinateSystem3D& plane) const + { + if (points_.size() < 3) + return; + + // the plane is horizontal + ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0)); + ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0)); + + segments.clear(); + segments.reserve(points_.size()); + // since the returned values need to be expressed in the supplied coordinate + // system, we need to subtract origin_ from the returned points + + double planeOriginX = plane.GetOrigin()[0]; + double planeOriginY = plane.GetOrigin()[1]; + + // precondition: points_.size() >= 3 + for (size_t j = 0; j < points_.size()-1; ++j) + { + // segment between point j and j+1 + + const Vector& point0 = GetPoint(j); + // subtract plane origin x and y + ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY); + + const Vector& point1 = GetPoint(j+1); + // subtract plane origin x and y + ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY); + + segments.push_back(std::pair(p0,p1)); + } + + + // final segment + + const Vector& point0 = GetPoint(points_.size() - 1); + // subtract plane origin x and y + ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY); + + const Vector& point1 = GetPoint(0); + // subtract plane origin x and y + ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY); + + segments.push_back(std::pair(p0, p1)); + } + + double DicomStructurePolygon2::GetZ() const + { + ORTHANC_ASSERT(LinearAlgebra::IsNear(normal_[0], 0.0)); + ORTHANC_ASSERT(LinearAlgebra::IsNear(normal_[1], 0.0)); + ORTHANC_ASSERT(LinearAlgebra::IsNear(minZ_, maxZ_)); + return minZ_; + } +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructurePolygon2.h Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,163 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#pragma once + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "CoordinateSystem3D.h" +#include "DicomStructureSetUtils.h" +#include "Extent2D.h" + +#include "../Scene2D/Color.h" +#include "../StoneException.h" + +#include "OrthancDatasets/FullOrthancDataset.h" + +#include +#include + +namespace OrthancStone +{ + + /** + Only polygons that are planar and parallel to either the X,Y or Z plane + ("X plane" == plane where X is equal to a constant for each point) are + supported. + */ + class DicomStructurePolygon2 + { + public: + enum Type + { + ClosedPlanar, + Unsupported + }; + + DicomStructurePolygon2(std::string referencedSopInstanceUid, const std::string& type) + : referencedSopInstanceUid_(referencedSopInstanceUid) + , state_(Building) + , minX_(std::numeric_limits::max()) + , maxX_(-std::numeric_limits::max()) + , minY_(std::numeric_limits::max()) + , maxY_(-std::numeric_limits::max()) + , minZ_(std::numeric_limits::max()) + , maxZ_(-std::numeric_limits::max()) + , type_(TypeFromString(type)) + { + ORTHANC_ASSERT(type_ == ClosedPlanar); + } + + void ComputeDependentProperties(); + + size_t GetPointCount() const + { + ORTHANC_ASSERT(state_ == Valid); + return points_.size(); + } + + const Vector& GetPoint(size_t i) const + { + ORTHANC_ASSERT(state_ == Valid); + return points_.at(i); + } + + void AddPoint(const Vector& v) + { + ORTHANC_ASSERT(state_ == Building); + points_.push_back(v); + } + + void Reserve(size_t n) + { + ORTHANC_ASSERT(state_ == Building); + points_.reserve(n); + } + + /** + This method takes a plane+coord system that is parallel to the polygon + and adds to polygons a new vector with the ordered set of points projected + on the plane, in the plane coordinate system. + */ + void ProjectOnParallelPlane( + std::vector< std::pair >& segments, + const CoordinateSystem3D& plane) const; + + /** + Returns the coordinates of the intersection of the polygon and a plane + that is perpendicular to the polygons (plane has either constant X or + constant Y) + */ + void ProjectOnConstantPlane( + std::vector& intersections, + const CoordinateSystem3D& plane) const; + + /** + This method assumes polygon has a normal equal to 0,0,-1 and 0,0,1 (thus, + the polygon is parallel to the XY plane) and returns the Z coordinate of + all the polygon points + */ + double GetZ() const; + + /** + The normal sign is left undefined for now + */ + Vector GetNormal() const + { + return normal_; + } + + /** + This method will compute the intersection between a polygon and + a plane where either X, Y or Z is constant. + The plane is given with an origin and a normal. If the normal is + not parallel to an axis, an error is raised. + */ + void ComputeIntersectionWithPlane(const CoordinateSystem3D& plane); + + private: + static Type TypeFromString(const std::string& s) + { + if (s == "CLOSED_PLANAR") + return ClosedPlanar; + else + return Unsupported; + } + enum State + { + Building, + Valid + }; + std::string referencedSopInstanceUid_; + CoordinateSystem3D geometry_; + std::vector points_; + Vector normal_; // sign is irrelevant for now + State state_; + double minX_, maxX_, minY_, maxY_, minZ_, maxZ_; + Type type_; + }; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet-BGO.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet-BGO.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,124 @@ +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& r1, + const std::pair& 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; + } +} + + + bool DicomStructureSet::ProjectStructure(std::vector< std::vector >& chains, + const Structure& structure, + const CoordinateSystem3D& sourceSlice) const + { + + // FOR SAGITTAL AND CORONAL + + + // 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 > projected; + + 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, GetEstimatedNormal(), GetEstimatedSliceThickness())) + { + 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(x1), + static_cast(y1), + static_cast(x2), + static_cast(y2)),curZ)); + } + } + + // 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 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); + + std::vector< std::pair > segments; + ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, projected.size()); + + chains.resize(segments.size()); + for (size_t i = 0; i < segments.size(); i++) + { + chains[i].resize(2); + chains[i][0] = segments[i].first; + chains[i][1] = segments[i].second; + } +#endif + + return true; + } diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,313 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructureSet2.h" + +#include "../Toolbox/LinearAlgebra.h" +#include "../StoneException.h" + +#include +#include +#include +#include + +#include "DicomStructure2.h" +#include "GenericToolbox.h" +#include "OrthancDatasets/DicomDatasetReader.h" + +namespace OrthancStone +{ + static const Orthanc::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); + static const Orthanc::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); + static const Orthanc::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); + static const Orthanc::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); + static const Orthanc::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); + static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); + static const Orthanc::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); + static const Orthanc::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a); + static const Orthanc::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026); + static const Orthanc::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4); + static const Orthanc::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080); + static const Orthanc::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020); + + static inline uint8_t ConvertAndClipToByte(double v) + { + if (v < 0) + { + return 0; + } + else if (v >= 255) + { + return 255; + } + else + { + return static_cast(v); + } + } + + static bool ReadDicomToVector(Vector& target, + const IDicomDataset& dataset, + const Orthanc::DicomPath& tag) + { + std::string value; + return (dataset.GetStringValue(value, tag) && + GenericToolbox::FastParseVector(target, value)); + } + + + void DicomPathToString(std::string& s, const Orthanc::DicomPath& dicomPath) + { + std::stringstream tmp; + for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i) + { + Orthanc::DicomTag tag = dicomPath.GetPrefixTag(i); + + // We use this other object to be able to use Format + Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement()); + size_t index = dicomPath.GetPrefixIndex(i); + tmp << " (" << tag2.Format() << ") [" << index << "] / "; + } + const Orthanc::DicomTag& tag = dicomPath.GetFinalTag(); + Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement()); + tmp << " (" << tag2.Format() << ")"; + s = tmp.str(); + } + + std::ostream& operator<<(std::ostream& s, const Orthanc::DicomPath& dicomPath) + { + std::string tmp; + DicomPathToString(tmp, dicomPath); + s << tmp; + return s; + } + + + DicomStructureSet2::DicomStructureSet2() + { + + } + + + DicomStructureSet2::~DicomStructureSet2() + { + + } + + void DicomStructureSet2::SetContents(const FullOrthancDataset& tags) + { + FillStructuresFromDataset(tags); + ComputeDependentProperties(); + } + + void DicomStructureSet2::ComputeDependentProperties() + { + for (size_t i = 0; i < structures_.size(); ++i) + { + structures_[i].ComputeDependentProperties(); + } + } + + void DicomStructureSet2::FillStructuresFromDataset(const FullOrthancDataset& tags) + { + DicomDatasetReader reader(tags); + + // a few sanity checks + size_t count = 0, tmp = 0; + + // DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080); + // DICOM_TAG_ROI_CONTOUR_SEQUENCE (0x3006, 0x0039); + // DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE (0x3006, 0x0020); + if (!tags.GetSequenceSize(count, Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE)) || + !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE)) || + tmp != count || + !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE)) || + tmp != count) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // let's now parse the structures stored in the dicom file + // DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080) + // DICOM_TAG_RT_ROI_INTERPRETED_TYPE (0x3006, 0x00a4) + // DICOM_TAG_ROI_DISPLAY_COLOR (0x3006, 0x002a) + // DICOM_TAG_ROI_NAME (0x3006, 0x0026) + structures_.resize(count); + for (size_t i = 0; i < count; i++) + { + // (0x3006, 0x0080)[i]/(0x3006, 0x00a4) + structures_[i].interpretation_ = reader.GetStringValue + (Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, + DICOM_TAG_RT_ROI_INTERPRETED_TYPE), + "No interpretation"); + + // (0x3006, 0x0020)[i]/(0x3006, 0x0026) + structures_[i].name_ = reader.GetStringValue + (Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, + DICOM_TAG_ROI_NAME), + "No name"); + + Vector color; + // (0x3006, 0x0039)[i]/(0x3006, 0x002a) + if (ReadDicomToVector(color, tags, Orthanc::DicomPath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) + && color.size() == 3) + { + structures_[i].red_ = ConvertAndClipToByte(color[0]); + structures_[i].green_ = ConvertAndClipToByte(color[1]); + structures_[i].blue_ = ConvertAndClipToByte(color[2]); + } + else + { + structures_[i].red_ = 255; + structures_[i].green_ = 0; + structures_[i].blue_ = 0; + } + + size_t countSlices; + // DICOM_TAG_ROI_CONTOUR_SEQUENCE (0x3006, 0x0039); + // DICOM_TAG_CONTOUR_SEQUENCE (0x3006, 0x0040); + if (!tags.GetSequenceSize(countSlices, Orthanc::DicomPath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE))) + { + LOG(WARNING) << "DicomStructureSet2::SetContents | structure \"" << structures_[i].name_ << "\" has no slices!"; + countSlices = 0; + } + + LOG(INFO) << "New RT structure: \"" << structures_[i].name_ + << "\" with interpretation \"" << structures_[i].interpretation_ + << "\" containing " << countSlices << " slices (color: " + << static_cast(structures_[i].red_) << "," + << static_cast(structures_[i].green_) << "," + << static_cast(structures_[i].blue_) << ")"; + + // These temporary variables avoid allocating many vectors in the loop below + + // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046) + Orthanc::DicomPath countPointsPath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_NUMBER_OF_CONTOUR_POINTS); + + Orthanc::DicomPath geometricTypePath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_GEOMETRIC_TYPE); + + Orthanc::DicomPath imageSequencePath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_IMAGE_SEQUENCE); + + // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155) + Orthanc::DicomPath referencedInstancePath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, + DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); + + Orthanc::DicomPath contourDataPath( + DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_DATA); + + for (size_t j = 0; j < countSlices; j++) + { + unsigned int countPoints = 0; + + countPointsPath.SetPrefixIndex(1, j); + if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath)) + { + std::string s; + DicomPathToString(s, countPointsPath); + LOG(ERROR) << "Dicom path " << s << " is not valid (should contain an unsigned integer)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; + + geometricTypePath.SetPrefixIndex(1, j); + std::string type = reader.GetMandatoryStringValue(geometricTypePath); + if (type != "CLOSED_PLANAR") + { + // TODO: support points!! + LOG(WARNING) << "Ignoring contour with geometry type: " << type; + continue; + } + + size_t size = 0; + + imageSequencePath.SetPrefixIndex(1, j); + if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1) + { + LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry."; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + referencedInstancePath.SetPrefixIndex(1, j); + std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath); + + contourDataPath.SetPrefixIndex(1, j); + std::string slicesData = reader.GetMandatoryStringValue(contourDataPath); + + Vector points; + if (!GenericToolbox::FastParseVector(points, slicesData) || + points.size() != 3 * countPoints) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // seen in real world + if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "") + { + LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)"; + } + + DicomStructurePolygon2 polygon(sopInstanceUid,type); + polygon.Reserve(countPoints); + + for (size_t k = 0; k < countPoints; k++) + { + Vector v(3); + v[0] = points[3 * k]; + v[1] = points[3 * k + 1]; + v[2] = points[3 * k + 2]; + polygon.AddPoint(v); + } + structures_[i].AddPolygon(polygon); + } + } + } + + + void DicomStructureSet2::Clear() + { + structures_.clear(); + } + +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSet2.h Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,72 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#pragma once + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "../Scene2D/Color.h" +#include "CoordinateSystem3D.h" +#include "DicomStructure2.h" +#include "Extent2D.h" +#include "OrthancDatasets/FullOrthancDataset.h" + +#include + +namespace OrthancStone +{ + class DicomStructureSet2 : public boost::noncopyable + { + public: + DicomStructureSet2(); + ~DicomStructureSet2(); + + void SetContents(const FullOrthancDataset& tags); + + size_t GetStructuresCount() const + { + return structures_.size(); + } + + void Clear(); + + const DicomStructure2& GetStructure(size_t i) const + { + // at() is like []() but with range check + return structures_.at(i); + } + + /** Internal use only */ + void FillStructuresFromDataset(const FullOrthancDataset& tags); + + /** Internal use only */ + void ComputeDependentProperties(); + + /** Internal use only */ + std::vector structures_; + }; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,118 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructureSetSlicer2.h" + +#include "../Toolbox/GeometryToolbox.h" +#include "../Volumes/IVolumeSlicer.h" +#include "../Scene2D/PolylineSceneLayer.h" + +namespace OrthancStone +{ + DicomStructureSetSlicer2::DicomStructureSetSlicer2(boost::shared_ptr structureSet) + : structureSet_(structureSet) + {} + + IVolumeSlicer::IExtractedSlice* DicomStructureSetSlicer2::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + { + // revision is always the same, hence 0 + return new DicomStructureSetSlice2(structureSet_, 0, cuttingPlane); + } + + DicomStructureSetSlice2::DicomStructureSetSlice2( + boost::weak_ptr structureSet, + uint64_t revision, + const CoordinateSystem3D& cuttingPlane) + : structureSet_(structureSet.lock()) + , isValid_(false) + { + bool opposite = false; + + if (structureSet_->GetStructuresCount() == 0) + { + isValid_ = false; + } + else + { + // some structures seen in real life have no polygons. We must be + // careful + bool found = false; + size_t curStructure = 0; + while (!found && curStructure < structureSet_->GetStructuresCount()) + { + if (structureSet_->GetStructure(curStructure).IsValid()) + { + found = true; + const Vector normal = structureSet_->GetStructure(0).GetNormal(); + isValid_ = ( + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); + } + } + } + } + + ISceneLayer* DicomStructureSetSlice2::CreateSceneLayer( + const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + assert(isValid_); + + std::unique_ptr layer(new PolylineSceneLayer); + layer->SetThickness(2); // thickness of the on-screen line + + for (size_t i = 0; i < structureSet_->GetStructuresCount(); i++) + { + const DicomStructure2& structure = structureSet_->GetStructure(i); + if (structure.IsValid()) + { + const Color& color = structure.GetColor(); + + std::vector< std::pair > segments; + + if (structure.Project(segments, cuttingPlane)) + { + for (size_t j = 0; j < segments.size(); j++) + { + PolylineSceneLayer::Chain chain; + chain.resize(2); + + chain[0] = ScenePoint2D(segments[j].first.GetX(), segments[j].first.GetY()); + chain[1] = ScenePoint2D(segments[j].second.GetX(), segments[j].second.GetY()); + + layer->AddChain(chain, false /* NOT closed */, color); + } + } + } + } + return layer.release(); + } +} + + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetSlicer2.h Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,78 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "../Toolbox/DicomStructureSet2.h" +#include "../Volumes/IVolumeSlicer.h" + +#include +#include + +namespace OrthancStone +{ + class DicomStructureSetSlice2 : public IVolumeSlicer::IExtractedSlice + { + public: + DicomStructureSetSlice2( + boost::weak_ptr structureSet, + uint64_t revision, + const CoordinateSystem3D& cuttingPlane); + + virtual bool IsValid() ORTHANC_OVERRIDE + { + return isValid_; + } + + virtual uint64_t GetRevision() ORTHANC_OVERRIDE + { + return revision_; + } + + virtual ISceneLayer* CreateSceneLayer( + const ILayerStyleConfigurator* configurator, // possibly absent + const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + + private: + boost::shared_ptr structureSet_; + bool isValid_; + uint64_t revision_; + }; + + class DicomStructureSetSlicer2 : public IVolumeSlicer + { + public: + DicomStructureSetSlicer2(boost::shared_ptr structureSet); + + /** IVolumeSlicer impl */ + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + private: + boost::weak_ptr structureSet_; + }; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,274 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#include "DicomStructureSetUtils.h" + +namespace OrthancStone +{ + +#if 0 + void DicomStructure2::PartitionRectangleList(std::vector< std::vector > & sets, const std::vector slabCuts) + { + // map position ( )--> disjoint set index + std::map, size_t> posToIndex; + + // disjoint set index --> position + std::map > indexToPos; + + size_t nextIndex = 0; + for (size_t i = 0; i < slabCuts.size(); ++i) + { + for (size_t j = 0; j < slabCuts[i].size(); ++j) + { + std::pair pos(i, j); + posToIndex = nextIndex; + indexToPos = pos; + } + } + // nextIndex is now the total rectangle count + DisjointDataSet ds(nextIndex); + + // we loop on all slabs (except the last one) and we connect all rectangles + if (slabCuts.size() < 2) + { +#error write special case + } + else + { + for (size_t i = 0; i < slabCuts.size() - 1; ++i) + { + for (size_t j = 0; j < slabCuts[i].size(); ++j) + { + const RtStructRectangleInSlab& r1 = slabCuts[i][j]; + const size_t r1i = posToIndex(std::pair(i, j)); + for (size_t k = 0; k < slabCuts[i + 1].size(); ++k) + { + const RtStructRectangleInSlab& r2 = slabCuts[i + 1][k]; + const size_t r2i = posToIndex(std::pair(i, j)); + // rect.xmin <= rectBottom.xmax && rectBottom.xmin <= rect.xmax + if ((r1.xmin <= r2.xmax) && (r2.xmin <= r1.xmax)) + { +#error now go! + } + + } + } + } + } +#endif + + /* + + compute list of segments : + + numberOfRectsFromHereOn = 0 + possibleNext = {in_k,in_kplus1} + + for all boundaries: + - we create a vertical segment and we push it + - if boundary is a start, numberOfRectsFromHereOn += 1. + - if we switch from 0 to 1, we start a segment + - if we switch from 1 to 2, we end the current segment and we record it + - if boundary is an end, numberOfRectsFromHereOn -= 1. + - if we switch from 1 to 0, we end the current segment and we record it + - if we switch from 2 to 1, we start a segment + */ + + // static + void AddSlabBoundaries( + std::vector > & boundaries, + const std::vector & slabCuts, size_t iSlab) + { + if (iSlab < slabCuts.size()) + { + const RtStructRectanglesInSlab& slab = slabCuts[iSlab]; + for (size_t iRect = 0; iRect < slab.size(); ++iRect) + { + const RtStructRectangleInSlab& rect = slab[iRect]; + { + std::pair boundary(rect.xmin, RectangleBoundaryKind_Start); + boundaries.insert(std::lower_bound(boundaries.begin(), boundaries.end(), boundary), boundary); + } + { + std::pair boundary(rect.xmax, RectangleBoundaryKind_End); + boundaries.insert(std::lower_bound(boundaries.begin(), boundaries.end(), boundary), boundary); + } + } + } + } + + // static + void ProcessBoundaryList( + std::vector< std::pair > & segments, + const std::vector > & boundaries, + double y) + { + ScenePoint2D start; + ScenePoint2D end; + int curNumberOfSegments = 0; // we count the number of segments. we only draw if it is 1 (not 0 or 2) + for (size_t i = 0; i < boundaries.size(); ++i) + { + switch (boundaries[i].second) + { + case RectangleBoundaryKind_Start: + curNumberOfSegments += 1; + switch (curNumberOfSegments) + { + case 0: + assert(false); + break; + case 1: + // a new segment has begun! + start = ScenePoint2D(boundaries[i].first, y); + break; + case 2: + // an extra segment has begun : stop the current one (we don't draw overlaps) + end = ScenePoint2D(boundaries[i].first, y); + segments.push_back(std::pair(start, end)); + break; + default: + //assert(false); // seen IRL ! + break; + } + break; + case RectangleBoundaryKind_End: + curNumberOfSegments -= 1; + switch (curNumberOfSegments) + { + case 0: + // a lone (thus active) segment has ended. + end = ScenePoint2D(boundaries[i].first, y); + segments.push_back(std::pair(start, end)); + break; + case 1: + // an extra segment has ended : start a new one one + start = ScenePoint2D(boundaries[i].first, y); + break; + default: + // this should not happen! + //assert(false); + break; + } + break; + default: + assert(false); + break; + } + } + } + +#if 0 + void ConvertListOfSlabsToSegments( + std::vector< std::pair >& segments, + const std::vector& slabCuts, + const size_t totalRectCount) + { +#error to delete + } +#else + // See https://www.dropbox.com/s/bllco6q8aazxk44/2019-09-18-rtstruct-cut-algorithm-rect-merge.png + void ConvertListOfSlabsToSegments( + std::vector< std::pair > & segments, + const std::vector & slabCuts, + const size_t totalRectCount) + { + if (slabCuts.size() == 0) + return; + + if (totalRectCount > 0) + segments.reserve(4 * totalRectCount); // worst case, but common. + + /* + VERTICAL + */ + for (size_t iSlab = 0; iSlab < slabCuts.size(); ++iSlab) + { + for (size_t iRect = 0; iRect < slabCuts[iSlab].size(); ++iRect) + { + const RtStructRectangleInSlab& rect = slabCuts[iSlab][iRect]; + { + ScenePoint2D p1(rect.xmin, rect.ymin); + ScenePoint2D p2(rect.xmin, rect.ymax); + segments.push_back(std::pair(p1, p2)); + } + { + ScenePoint2D p1(rect.xmax, rect.ymin); + ScenePoint2D p2(rect.xmax, rect.ymax); + segments.push_back(std::pair(p1, p2)); + } + } + } + + /* + HORIZONTAL + */ + + // if we have N slabs, we have N+1 potential vertical positions for horizontal segments + // - one for top of slab 0 + // - N-1 for all positions between two slabs + // - one for bottom of slab N-1 + + // this adds all the horizontal segments for the tops of 3the rectangles + // in row 0 + if (slabCuts[0].size() > 0) + { + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, 0); + + ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin); + } + + // this adds all the horizontal segments belonging to two slabs + for (size_t iSlab = 0; iSlab < slabCuts.size() - 1; ++iSlab) + { + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, iSlab); + AddSlabBoundaries(boundaries, slabCuts, iSlab + 1); + double curY = 0; + if (slabCuts[iSlab].size() > 0) + { + curY = slabCuts[iSlab][0].ymax; + ProcessBoundaryList(segments, boundaries, curY); + } + else if (slabCuts[iSlab + 1].size() > 0) + { + curY = slabCuts[iSlab + 1][0].ymin; + ProcessBoundaryList(segments, boundaries, curY); + } + else + { + // nothing to do!! : both slab lists are empty! + } + } + + // this adds all the horizontal segments for the BOTTOM of the rectangles + // on last row + if (slabCuts[slabCuts.size() - 1].size() > 0) + { + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, slabCuts.size() - 1); + + ProcessBoundaryList(segments, boundaries, slabCuts[slabCuts.size() - 1][0].ymax); + } + } +#endif + } diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DicomStructureSetUtils.h Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,66 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#pragma once + +#include +#include + +#include "../Scene2D/ScenePoint2D.h" +#include "../Toolbox/LinearAlgebra.h" + +namespace OrthancStone +{ + /** Internal */ + struct RtStructRectangleInSlab + { + double xmin, xmax, ymin, ymax; + }; + typedef std::vector RtStructRectanglesInSlab; + + enum RectangleBoundaryKind + { + RectangleBoundaryKind_Start, + RectangleBoundaryKind_End + }; + +#if 0 + /** Internal */ + void PartitionRectangleList(std::vector< std::vector > & sets, const std::vector); +#endif + + /** Internal */ + void ConvertListOfSlabsToSegments(std::vector< std::pair >& segments, + const std::vector& slabCuts, + const size_t totalRectCount); + + /** Internal */ + void AddSlabBoundaries(std::vector >& boundaries, + const std::vector& slabCuts, + size_t iSlab); + + /** Internal */ + void ProcessBoundaryList(std::vector< std::pair >& segments, + const std::vector >& boundaries, + double y); + +} diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DisjointDataSet.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/DisjointDataSet.h Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,146 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#pragma once + +#include + +#include "../StoneException.h" + +namespace OrthancStone +{ + class DisjointDataSet + { + public: + DisjointDataSet(size_t itemCount) : + parents_(itemCount), + ranks_(itemCount) + { + for (size_t index = 0; index < parents_.size(); index++) + { + SetParent(index,index); + ranks_[index] = 1; + } + } + + size_t Find(size_t item) + { + /* + If parents_[i] == i, it means i is representative of a set. + Otherwise, we go up the tree... + */ + if (GetParent(item) != item) + { + // if item is not a top item (representative of its set), + // we use path compression to improve future lookups + // see: https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Path_compression + SetParent(item, Find(parents_[item])); + } + + // now that paths have been compressed, we are positively certain + // that item's parent is a set ("X is a set" means that X is the + // representative of a set) + return GetParent(item); + } + + /* + This merge the two sets that contains itemA and itemB + */ + void Union(size_t itemA, size_t itemB) + { + // Find current sets of x and y + size_t setA = Find(itemA); + size_t setB = Find(itemB); + + // if setA == setB, it means they are already in the same set and + // do not need to be merged! + if (setA != setB) + { + // we need to merge the sets, which means that the trees representing + // the sets needs to be merged (there must be a single top parent to + // all the items originally belonging to setA and setB must be the same) + + // since the algorithm speed is inversely proportional to the tree + // height (the rank), we need to combine trees in a way that + // minimizes this rank. See "Union by rank" at + // https://en.wikipedia.org/wiki/Disjoint-set_data_structure#by_rank + if (GetRank(setA) < GetRank(setB)) + { + SetParent(setA, setB); + } + else if (GetRank(setA) > GetRank(setB)) + { + SetParent(setB, setA); + } + else + { + SetParent(setB, setA); + BumpRank(setA); + // the trees had the same height but we attached the whole of setB + // under setA (under its parent), so the resulting tree is now + // 1 higher. setB is NOT representative of a set anymore. + } + } + } + + private: + size_t GetRank(size_t i) const + { + ORTHANC_ASSERT(i < ranks_.size()); + ORTHANC_ASSERT(ranks_.size() == parents_.size()); + return ranks_[i]; + } + + size_t GetParent(size_t i) const + { + ORTHANC_ASSERT(i < parents_.size()); + ORTHANC_ASSERT(ranks_.size() == parents_.size()); + return parents_[i]; + } + + void SetParent(size_t i, size_t parent) + { + ORTHANC_ASSERT(i < parents_.size()); + ORTHANC_ASSERT(ranks_.size() == parents_.size()); + parents_[i] = parent; + } + + void BumpRank(size_t i) + { + ORTHANC_ASSERT(i < ranks_.size()); + ORTHANC_ASSERT(ranks_.size() == parents_.size()); + ranks_[i] = ranks_[i] + 1u; + } + + /* + This vector contains the direct parent of each item + */ + std::vector parents_; + + /* + This vector contains the tree height of each set. The values in the + vector for non-representative items is UNDEFINED! + */ + std::vector ranks_; + }; + +} diff -r 0208f99b8bde -r affde38b84de OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/TestStructureSet.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Graveyard/RTStructTentativeReimplementation-BGO/TestStructureSet.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -0,0 +1,1155 @@ + +#define BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +// working around a bug where the Visual C++ compiler would get +// stuck trying to compile this cpp file in release mode +// (versions: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B) +#ifdef _MSC_VER +# pragma optimize("", off) +// warning C4748: /GS can not protect parameters and local variables from +// local buffer overrun because optimizations are disabled in function +# pragma warning(disable: 4748) +#endif + + + +#include +#include +#include + + + +using namespace OrthancStone; + +static const double DELTA_MAX = 10.0 * std::numeric_limits::epsilon(); + + + +#define STONE_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static double pointsCoord1[] = { 2, 2, 3, 3, 6, 8, 8, 7, 8, 8, 6 }; +static double pointsCoord2[] = { 2, 6, 8, 10, 12, 10, 8, 6, 4, 2, 4 }; +static const size_t pointsCoord1Count = STONE_ARRAY_SIZE(pointsCoord1); +static const size_t pointsCoord2Count = STONE_ARRAY_SIZE(pointsCoord2); +const size_t POLYGON_POINT_COUNT = pointsCoord1Count; + + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +static void CheckGroundTruth( + const std::vector& structures, + const size_t structureIndex, + const size_t sliceIndex, + std::vector groundTruth) +{ + const std::vector& polygonsForThisStruct = structures.at(structureIndex).GetPolygons(); + const DicomStructurePolygon2& polygon = polygonsForThisStruct.at(sliceIndex); + + //double groundTruth[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 }; + size_t groundTruthItems = groundTruth.size(); + + size_t pointCount = 3 * polygon.GetPointCount(); + + EXPECT_EQ(groundTruthItems, pointCount); + + for (size_t i = 0; i < polygon.GetPointCount(); ++i) + { + const Vector& point = polygon.GetPoint(i); + + // loop over X, Y then Z. + for (size_t j = 0; j < 3; ++j) + { + size_t index = 3 * i + j; + ASSERT_LT(index, groundTruthItems); + bool isNear = LinearAlgebra::IsNear(groundTruth[index], point[j]); + EXPECT_TRUE(isNear); + } + } +} + + +#include + +TEST(StructureSet2, ReadFromJsonThatsAll) +{ + /* + The "RT_STRUCT_00" string is the reply to the following Orthanc request: + + http://localhost:8042/instances/1aa5f84b-c32a03b4-3c1857da-da2e69f3-3ef6e2b3/tags?ignore-length=3006-0050 + + The tag hierarchy can be found here: https://dicom.innolitics.com/ciods/rt-dose + */ + + DicomStructureSet2 structureSet; + + FullOrthancDataset dicom(Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00), + Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00)); + structureSet.Clear(); + + structureSet.FillStructuresFromDataset(dicom); + structureSet.ComputeDependentProperties(); + + const std::vector& structures = structureSet.structures_; + + /* + + ██████╗ █████╗ ███████╗██╗ ██████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗███████╗ + ██╔══██╗██╔══██╗██╔════╝██║██╔════╝ ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝ + ██████╔╝███████║███████╗██║██║ ██║ ███████║█████╗ ██║ █████╔╝ ███████╗ + ██╔══██╗██╔══██║╚════██║██║██║ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ╚════██║ + ██████╔╝██║ ██║███████║██║╚██████╗ ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗███████║ + ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ + http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS + */ + + // (0x3006, 0x0080) seq. size + EXPECT_EQ(7u, structures.size()); + + // (0x3006, 0x0080)[i]/(0x3006, 0x00a4) + for (size_t i = 0; i < 5; ++i) + { + EXPECT_EQ(std::string("ORGAN"), structures[i].interpretation_); + } + EXPECT_EQ(std::string("EXTERNAL"), structures[5].interpretation_); + EXPECT_EQ(std::string("PTV"), structures[6].interpretation_); + + // (0x3006, 0x0020)[i]/(0x3006, 0x0026) + EXPECT_EQ(std::string("LN300"), structures[0].name_); + EXPECT_EQ(std::string("Cortical Bone"), structures[1].name_); + EXPECT_EQ(std::string("Adipose"), structures[2].name_); + EXPECT_EQ(std::string("CB2-50%"), structures[3].name_); + EXPECT_EQ(std::string("Water"), structures[4].name_); + EXPECT_EQ(std::string("External"), structures[5].name_); + EXPECT_EQ(std::string("PTV"), structures[6].name_); + + // (0x3006, 0x0039)[i]/(0x3006, 0x002a) + EXPECT_EQ(0xff, structures[0].red_); + EXPECT_EQ(0x00, structures[0].green_); + EXPECT_EQ(0x00, structures[0].blue_); + + EXPECT_EQ(0x00, structures[1].red_); + EXPECT_EQ(0xff, structures[1].green_); + EXPECT_EQ(0xff, structures[1].blue_); + + // ... + + EXPECT_EQ(0x00, structures[5].red_); + EXPECT_EQ(0x80, structures[5].green_); + EXPECT_EQ(0x00, structures[5].blue_); + + EXPECT_EQ(0xff, structures[6].red_); + EXPECT_EQ(0x00, structures[6].green_); + EXPECT_EQ(0xff, structures[6].blue_); + + /* + + ██████╗ ███████╗ ██████╗ ███╗ ███╗███████╗████████╗██████╗ ██╗ ██╗ + ██╔════╝ ██╔════╝██╔═══██╗████╗ ████║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝ + ██║ ███╗█████╗ ██║ ██║██╔████╔██║█████╗ ██║ ██████╔╝ ╚████╔╝ + ██║ ██║██╔══╝ ██║ ██║██║╚██╔╝██║██╔══╝ ██║ ██╔══██╗ ╚██╔╝ + ╚██████╔╝███████╗╚██████╔╝██║ ╚═╝ ██║███████╗ ██║ ██║ ██║ ██║ + ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ + http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS + */ + + + { + double groundTruthRaw[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 }; + size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); + std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); + CheckGroundTruth(structures, 0, 0, groundTruth); + } + { + double groundTruthRaw[] = { 7.657838, 108.2725, 310.01, 6.826687, 107.4413, 310.01, 6.152492, 106.4785, 310.01, 5.655735, 105.4132, 310.01, 5.351513, 104.2778, 310.01, 5.249068, 103.1069, 310.01, 5.351513, 101.9359, 310.01, 5.655735, 100.8005, 310.01, 6.152492, 99.73524, 310.01, 6.826687, 98.77239, 310.01, 7.657838, 97.94124, 310.01, 8.620689, 97.26704, 310.01, 9.685987, 96.77029, 310.01, 10.82136, 96.46606, 310.01, 11.99231, 96.36362, 310.01, 13.16326, 96.46606, 310.01, 14.29864, 96.77029, 310.01, 15.36393, 97.26704, 310.01, 16.32678, 97.94124, 310.01, 17.15794, 98.77239, 310.01, 17.83213, 99.73524, 310.01, 18.32889, 100.8005, 310.01, 18.63311, 101.9359, 310.01, 18.73555, 103.1069, 310.01, 18.63311, 104.2778, 310.01, 18.32889, 105.4132, 310.01, 17.83213, 106.4785, 310.01, 17.15794, 107.4413, 310.01, 16.32678, 108.2725, 310.01, 15.36393, 108.9467, 310.01, 14.29864, 109.4434, 310.01, 13.16326, 109.7477, 310.01, 11.99231, 109.8501, 310.01, 10.82136, 109.7477, 310.01, 9.685987, 109.4434, 310.01, 8.620689, 108.9467, 310.01 }; + size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); + std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); + CheckGroundTruth(structures, 0, 2, groundTruth); + } + { + double groundTruthRaw[] = { -37.967, 161.9664, 304.01, -39.10237, 161.6622, 304.01, -40.16767, 161.1655, 304.01, -41.13052, 160.4913, 304.01, -41.96167, 159.6601, 304.01, -42.63587, 158.6973, 304.01, -43.13263, 157.632, 304.01, -43.43685, 156.4966, 304.01, -43.53929, 155.3257, 304.01, -43.43685, 154.1547, 304.01, -43.13263, 153.0193, 304.01, -42.63587, 151.954, 304.01, -41.96167, 150.9912, 304.01, -41.13052, 150.16, 304.01, -40.16767, 149.4858, 304.01, -39.10237, 148.9891, 304.01, -37.967, 148.6849, 304.01, -36.79605, 148.5824, 304.01, -35.6251, 148.6849, 304.01, -34.48972, 148.9891, 304.01, -33.42443, 149.4858, 304.01, -32.46157, 150.16, 304.01, -31.63042, 150.9912, 304.01, -30.95623, 151.954, 304.01, -30.45947, 153.0193, 304.01, -30.15525, 154.1547, 304.01, -30.0528, 155.3257, 304.01, -30.15525, 156.4966, 304.01, -30.45947, 157.632, 304.01, -30.95623, 158.6973, 304.01, -31.63042, 159.6601, 304.01, -32.46157, 160.4913, 304.01, -33.42443, 161.1655, 304.01, -34.48972, 161.6622, 304.01, -35.6251, 161.9664, 304.01, -36.79605, 162.0689, 304.01 }; + size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); + std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); + CheckGroundTruth(structures, 1, 0, groundTruth); + } + { + double groundTruthRaw[] = { 69.4042, 150.7324, 307.01, 69.70842, 151.8678, 307.01, 69.81087, 153.0387, 307.01, 69.70842, 154.2097, 307.01, 69.4042, 155.345, 307.01, 68.90745, 156.4103, 307.01, 68.23325, 157.3732, 307.01, 67.4021, 158.2043, 307.01, 66.43925, 158.8785, 307.01, 65.37395, 159.3753, 307.01, 64.23858, 159.6795, 307.01, 63.06762, 159.7819, 307.01, 61.89667, 159.6795, 307.01, 60.7613, 159.3753, 307.01, 59.696, 158.8785, 307.01, 58.73315, 158.2043, 307.01, 57.902, 157.3732, 307.01, 57.22781, 156.4103, 307.01, 56.73105, 155.345, 307.01, 56.42683, 154.2097, 307.01, 56.32438, 153.0387, 307.01, 56.42683, 151.8678, 307.01, 56.73105, 150.7324, 307.01, 57.22781, 149.6671, 307.01, 57.902, 148.7042, 307.01, 58.73315, 147.8731, 307.01, 59.696, 147.1989, 307.01, 60.7613, 146.7021, 307.01, 61.89667, 146.3979, 307.01, 63.06762, 146.2955, 307.01, 64.23858, 146.3979, 307.01, 65.37395, 146.7021, 307.01, 66.43925, 147.1989, 307.01, 67.4021, 147.8731, 307.01, 68.23325, 148.7042, 307.01, 68.90745, 149.6671, 307.01 }; + size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); + std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); + CheckGroundTruth(structures, 2, 1, groundTruth); + } + + { + double groundTruthRaw[] = { 108.3984, 232.7406, 274.01, 106.0547, 231.7948, 274.01, 103.7109, 232.8407, 274.01, 96.67969, 232.8757, 274.01, 77.92969, 232.887, 274.01, 47.46094, 232.8902, 274.01, 38.08594, 232.7537, 274.01, 37.6668, 232.3734, 274.01, 38.08594, 231.9774, 274.01, 40.42969, 231.8475, 274.01, 41.76413, 230.0297, 274.01, 42.77344, 229.1388, 274.01, 45.11719, 228.5069, 274.01, 47.46094, 227.1533, 274.01, 49.80469, 226.3505, 274.01, 52.14844, 224.6564, 274.01, 54.49219, 223.923, 274.01, 56.83594, 222.0692, 274.01, 59.17969, 220.3438, 274.01, 61.52344, 219.3888, 274.01, 63.86719, 217.1287, 274.01, 65.83488, 215.9672, 274.01, 68.55469, 213.2383, 274.01, 70.89844, 211.2328, 274.01, 72.8125, 208.9359, 274.01, 75.58594, 206.3615, 274.01, 76.91445, 204.2484, 274.01, 78.89509, 201.9047, 274.01, 80.51276, 199.5609, 274.01, 81.51955, 197.2172, 274.01, 83.67448, 194.8734, 274.01, 84.60938, 192.5297, 274.01, 85.86986, 190.1859, 274.01, 86.57623, 187.8422, 274.01, 88.30051, 185.4984, 274.01, 88.94002, 183.1547, 274.01, 89.23261, 180.8109, 274.01, 89.64844, 180.3263, 274.01, 90.71885, 178.4672, 274.01, 90.97656, 176.1234, 274.01, 91.99219, 174.4794, 274.01, 92.56773, 173.7797, 274.01, 92.80016, 171.4359, 274.01, 93.23473, 169.0922, 274.01, 93.37606, 166.7484, 274.01, 93.60748, 157.3734, 274.01, 93.6341, 152.6859, 274.01, 93.35742, 140.9672, 274.01, 92.89317, 138.6234, 274.01, 92.7069, 136.2797, 274.01, 92.03726, 133.9359, 274.01, 90.84009, 131.5922, 274.01, 90.3769, 129.2484, 274.01, 89.09074, 126.9047, 274.01, 88.13225, 122.2172, 274.01, 86.17828, 119.8734, 274.01, 84.96094, 117.4163, 274.01, 83.99619, 115.1859, 274.01, 83.13079, 112.8422, 274.01, 82.61719, 112.2984, 274.01, 80.27344, 108.8454, 274.01, 79.64514, 108.1547, 274.01, 77.21497, 105.8109, 274.01, 76.47787, 103.4672, 274.01, 75.58594, 102.6177, 274.01, 73.24219, 100.0077, 274.01, 69.54492, 96.43594, 274.01, 67.34096, 94.09219, 274.01, 64.66306, 91.74844, 274.01, 63.86719, 90.92619, 274.01, 61.52344, 90.20454, 274.01, 59.17969, 87.78574, 274.01, 56.83594, 86.48566, 274.01, 54.49219, 84.31388, 274.01, 52.14844, 83.44438, 274.01, 49.80469, 82.75121, 274.01, 49.37617, 82.37344, 274.01, 47.46094, 81.26244, 274.01, 45.71391, 80.02969, 274.01, 45.11719, 79.45415, 274.01, 42.77344, 79.08185, 274.01, 40.42969, 78.51941, 274.01, 38.08594, 78.27534, 274.01, 37.36932, 77.68594, 274.01, 35.74219, 76.67624, 274.01, 33.39844, 76.49941, 274.01, 31.05469, 76.03495, 274.01, 28.71094, 74.83174, 274.01, 26.36719, 74.62859, 274.01, 24.02344, 74.55463, 274.01, 21.67969, 74.22861, 274.01, 19.33594, 74.05312, 274.01, 12.30469, 73.99397, 274.01, 5.273438, 74.0736, 274.01, 2.929688, 74.55463, 274.01, 0.5859375, 74.68513, 274.01, -1.757813, 74.914, 274.01, -2.319131, 75.34219, 274.01, -4.101563, 76.31516, 274.01, -8.789063, 76.74514, 274.01, -11.13281, 78.39038, 274.01, -13.47656, 78.6124, 274.01, -15.82031, 79.19784, 274.01, -18.16406, 81.11024, 274.01, -20.50781, 82.03296, 274.01, -22.85156, 83.13991, 274.01, -25.19531, 83.70732, 274.01, -27.53906, 85.85863, 274.01, -29.88281, 87.03368, 274.01, -32.22656, 88.3274, 274.01, -34.57031, 90.53674, 274.01, -36.91406, 92.5602, 274.01, -39.25781, 93.55952, 274.01, -41.60156, 95.74537, 274.01, -43.94531, 98.26609, 274.01, -46.28906, 100.3701, 274.01, -47.02621, 101.1234, 274.01, -47.86611, 103.4672, 274.01, -49.83594, 105.8109, 274.01, -51.98182, 108.1547, 274.01, -53.06448, 110.4984, 274.01, -53.32031, 110.7675, 274.01, -54.53804, 112.8422, 274.01, -55.66406, 114.273, 274.01, -56.55722, 115.1859, 274.01, -57.13953, 117.5297, 274.01, -58.29264, 119.8734, 274.01, -59.26869, 122.2172, 274.01, -60.35156, 124.0119, 274.01, -60.84229, 124.5609, 274.01, -61.54484, 126.9047, 274.01, -61.71691, 129.2484, 274.01, -63.62281, 131.5922, 274.01, -63.81256, 133.9359, 274.01, -64.12511, 136.2797, 274.01, -64.84515, 138.6234, 274.01, -65.13599, 140.9672, 274.01, -65.33604, 143.3109, 274.01, -65.87358, 145.6547, 274.01, -66.10577, 147.9984, 274.01, -66.17618, 155.0297, 274.01, -66.09933, 162.0609, 274.01, -65.40382, 164.4047, 274.01, -65.24833, 166.7484, 274.01, -64.71442, 171.4359, 274.01, -63.88171, 173.7797, 274.01, -63.69299, 176.1234, 274.01, -61.79081, 178.4672, 274.01, -61.59269, 180.8109, 274.01, -61.19405, 183.1547, 274.01, -60.35156, 185.2055, 274.01, -59.08288, 187.8422, 274.01, -58.00781, 189.3499, 274.01, -57.25858, 190.1859, 274.01, -56.64558, 192.5297, 274.01, -55.29191, 194.8734, 274.01, -54.28698, 197.2172, 274.01, -52.28595, 199.5609, 274.01, -51.47569, 201.9047, 274.01, -48.63281, 204.6417, 274.01, -47.10181, 206.5922, 274.01, -44.64154, 208.9359, 274.01, -42.38504, 211.2797, 274.01, -39.25781, 214.4025, 274.01, -37.42723, 215.9672, 274.01, -34.57031, 218.9107, 274.01, -32.22656, 219.7277, 274.01, -29.88281, 221.6934, 274.01, -27.53906, 222.852, 274.01, -25.19531, 224.5168, 274.01, -22.85156, 225.9419, 274.01, -20.50781, 226.7359, 274.01, -18.16406, 228.3332, 274.01, -15.82031, 229.065, 274.01, -13.47656, 229.267, 274.01, -12.63854, 230.0297, 274.01, -11.13281, 231.9201, 274.01, -10.65505, 232.3734, 274.01, -11.13281, 232.7794, 274.01, -15.82031, 232.792, 274.01, -18.16406, 232.8902, 274.01, -36.91406, 232.9015, 274.01, -39.25781, 232.8902, 274.01, -50.97656, 232.9236, 274.01, -60.35156, 232.9126, 274.01, -67.38281, 232.8407, 274.01, -72.07031, 232.8642, 274.01, -79.10156, 232.8555, 274.01, -83.78906, 232.8788, 274.01, -95.50781, 232.8902, 274.01, -97.85156, 233.4886, 274.01, -100.1953, 233.647, 274.01, -102.5391, 232.9858, 274.01, -104.8828, 233.6969, 274.01, -109.5703, 233.722, 274.01, -125.9766, 233.7086, 274.01, -128.3203, 233.2849, 274.01, -130.6641, 233.702, 274.01, -135.3516, 233.727, 274.01, -149.4141, 233.7135, 274.01, -156.4453, 233.727, 274.01, -163.4766, 233.7119, 274.01, -168.1641, 233.7643, 274.01, -191.6016, 233.7809, 274.01, -210.3516, 233.7716, 274.01, -224.4141, 233.7998, 274.01, -233.7891, 233.7647, 274.01, -243.1641, 233.7785, 274.01, -247.8516, 233.7378, 274.01, -254.8828, 233.8578, 274.01, -257.2266, 235.2519, 274.01, -259.5703, 236.0817, 274.01, -260.7617, 237.0609, 274.01, -261.9141, 238.2262, 274.01, -262.8989, 239.4047, 274.01, -262.9743, 241.7484, 274.01, -262.5977, 244.0922, 274.01, -260.6675, 246.4359, 274.01, -259.6161, 248.7797, 274.01, -257.2266, 251.0035, 274.01, -255.0361, 253.4672, 274.01, -252.5391, 256.0995, 274.01, -251.2277, 258.1547, 274.01, -246.7444, 262.8422, 274.01, -243.1641, 266.3515, 274.01, -239.7411, 269.8734, 274.01, -238.4766, 270.9495, 274.01, -237.2269, 272.2172, 274.01, -236.1328, 273.5215, 274.01, -235.0934, 274.5609, 274.01, -233.7891, 275.6655, 274.01, -232.5319, 276.9047, 274.01, -231.4453, 278.1693, 274.01, -227.917, 281.5922, 274.01, -224.4141, 285.1802, 274.01, -222.0703, 287.4025, 274.01, -218.6841, 290.9672, 274.01, -217.3828, 291.9709, 274.01, -215.0391, 293.1788, 274.01, -212.6953, 294.5138, 274.01, -210.3516, 295.2614, 274.01, -209.8994, 295.6547, 274.01, -208.0078, 296.7083, 274.01, -203.3203, 296.9372, 274.01, -196.2891, 296.9317, 274.01, -193.9453, 296.8988, 274.01, -172.8516, 296.8482, 274.01, -161.1328, 296.843, 274.01, -137.6953, 296.8542, 274.01, -130.6641, 296.8378, 274.01, -107.2266, 296.8379, 274.01, -93.16406, 296.8208, 274.01, -74.41406, 296.838, 274.01, -65.03906, 296.8609, 274.01, -50.97656, 296.8556, 274.01, -46.28906, 296.9051, 274.01, -41.60156, 298.5331, 274.01, -39.25781, 298.5624, 274.01, -36.91406, 297.1455, 274.01, -34.57031, 297.0498, 274.01, -32.22656, 298.5589, 274.01, -25.19531, 298.5624, 274.01, -22.85156, 297.2842, 274.01, -20.50781, 298.5624, 274.01, -1.757813, 298.5624, 274.01, 0.5859375, 297.2104, 274.01, 2.929688, 298.5624, 274.01, 5.273438, 297.6946, 274.01, 7.617188, 298.5168, 274.01, 9.960938, 298.5512, 274.01, 12.30469, 296.937, 274.01, 14.64844, 298.5478, 274.01, 16.99219, 298.5478, 274.01, 19.33594, 297.0782, 274.01, 21.67969, 296.844, 274.01, 23.54531, 297.9984, 274.01, 24.02344, 298.4023, 274.01, 24.50156, 297.9984, 274.01, 26.36719, 296.844, 274.01, 38.08594, 296.8381, 274.01, 52.14844, 296.8033, 274.01, 59.17969, 296.8033, 274.01, 73.24219, 296.7682, 274.01, 99.02344, 296.7566, 274.01, 117.7734, 296.7216, 274.01, 129.4922, 296.7152, 274.01, 131.8359, 295.9083, 274.01, 134.1797, 295.5245, 274.01, 138.8672, 295.4763, 274.01, 155.2734, 295.4763, 274.01, 176.3672, 295.3861, 274.01, 190.4297, 295.3718, 274.01, 197.4609, 295.4763, 274.01, 202.1484, 295.4454, 274.01, 204.4922, 295.3438, 274.01, 206.8359, 295.0757, 274.01, 209.1797, 294.4124, 274.01, 211.5234, 292.3133, 274.01, 213.8672, 291.0809, 274.01, 216.2109, 289.6743, 274.01, 217.3081, 288.6234, 274.01, 219.3558, 286.2797, 274.01, 221.8608, 283.9359, 274.01, 225.5859, 280.045, 274.01, 227.9297, 277.8885, 274.01, 230.2734, 275.2857, 274.01, 232.6172, 273.2225, 274.01, 233.6225, 272.2172, 274.01, 234.9609, 270.5822, 274.01, 238.2254, 267.5297, 274.01, 240.3691, 265.1859, 274.01, 244.3359, 261.3326, 274.01, 246.6797, 258.8034, 274.01, 249.0234, 256.7196, 274.01, 251.3672, 254.0746, 274.01, 254.5313, 251.1234, 274.01, 255.333, 248.7797, 274.01, 257.3723, 246.4359, 274.01, 259.7201, 244.0922, 274.01, 260.106, 241.7484, 274.01, 261.6423, 239.4047, 274.01, 261.0804, 237.0609, 274.01, 259.3552, 234.7172, 274.01, 258.3984, 233.7696, 274.01, 256.0547, 232.8757, 274.01, 253.7109, 232.792, 274.01, 251.3672, 232.8161, 274.01, 246.6797, 232.6981, 274.01, 244.3359, 232.725, 274.01, 239.6484, 232.9137, 274.01, 234.9609, 232.8525, 274.01, 225.5859, 232.8757, 274.01, 209.1797, 232.8757, 274.01, 204.4922, 232.7537, 274.01, 195.1172, 232.7794, 274.01, 171.6797, 232.792, 274.01, 164.6484, 232.7666, 274.01, 152.9297, 232.7666, 274.01, 148.2422, 232.792, 274.01, 138.8672, 232.7406, 274.01 }; + size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); + std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); + EXPECT_EQ(340u * 3, groundTruth.size()); + CheckGroundTruth(structures, 5, 0, groundTruth); + } + + { + double groundTruthRaw[] = { -18.16406, 233.0632, 298.01, -27.53906, 233.1042, 298.01, -29.88281, 233.0819, 298.01, -34.57031, 233.131, 298.01, -43.94531, 233.1221, 298.01, -50.97656, 233.1736, 298.01, -62.69531, 233.1397, 298.01, -65.03906, 232.8376, 298.01, -69.72656, 232.9839, 298.01, -79.10156, 233.0245, 298.01, -90.82031, 233.0382, 298.01, -93.16406, 233.0859, 298.01, -109.5703, 233.1132, 298.01, -111.9141, 233.1791, 298.01, -114.2578, 233.7139, 298.01, -118.9453, 233.9793, 298.01, -128.3203, 234.0284, 298.01, -130.6641, 233.9793, 298.01, -135.3516, 234.0591, 298.01, -137.6953, 234.0284, 298.01, -142.3828, 234.0855, 298.01, -144.7266, 234.0284, 298.01, -151.7578, 234.002, 298.01, -158.7891, 234.0263, 298.01, -163.4766, 233.9784, 298.01, -165.8203, 234.0072, 298.01, -168.1641, 234.1756, 298.01, -170.5078, 234.2214, 298.01, -179.8828, 234.1934, 298.01, -186.9141, 234.2721, 298.01, -189.2578, 234.2289, 298.01, -193.9453, 234.2431, 298.01, -198.6328, 234.1692, 298.01, -200.9766, 234.2326, 298.01, -205.6641, 234.1271, 298.01, -212.6953, 234.2224, 298.01, -215.0391, 234.1992, 298.01, -222.0703, 234.3115, 298.01, -224.4141, 234.2224, 298.01, -226.7578, 234.2502, 298.01, -233.7891, 234.0906, 298.01, -238.4766, 234.0329, 298.01, -243.1641, 234.0283, 298.01, -247.8516, 233.7949, 298.01, -250.1953, 233.8681, 298.01, -252.5391, 234.7626, 298.01, -254.3469, 237.0609, 298.01, -255.6034, 239.4047, 298.01, -254.5181, 241.7484, 298.01, -254.2274, 244.0922, 298.01, -254.181, 248.7797, 298.01, -253.9355, 251.1234, 298.01, -253.5926, 253.4672, 298.01, -252.7483, 255.8109, 298.01, -250.8092, 258.1547, 298.01, -248.713, 260.4984, 298.01, -246.263, 262.8422, 298.01, -244.1406, 265.1859, 298.01, -241.6671, 267.5297, 298.01, -239.4754, 269.8734, 298.01, -237.0156, 272.2172, 298.01, -233.7891, 275.382, 298.01, -231.4453, 277.8249, 298.01, -229.1016, 279.9981, 298.01, -226.7578, 282.5281, 298.01, -224.4141, 284.6784, 298.01, -222.0703, 287.2355, 298.01, -220.5414, 288.6234, 298.01, -218.2745, 290.9672, 298.01, -217.3828, 291.6508, 298.01, -212.6953, 294.5949, 298.01, -210.3516, 295.3142, 298.01, -208.0078, 296.4674, 298.01, -205.6641, 296.8852, 298.01, -203.3203, 297.1563, 298.01, -196.2891, 297.1488, 298.01, -193.9453, 297.0597, 298.01, -182.2266, 296.9529, 298.01, -168.1641, 296.8576, 298.01, -154.1016, 296.9249, 298.01, -149.4141, 296.8921, 298.01, -128.3203, 296.9228, 298.01, -121.2891, 296.8623, 298.01, -111.9141, 296.8549, 298.01, -107.2266, 296.8266, 298.01, -102.5391, 296.8731, 298.01, -95.50781, 296.8453, 298.01, -88.47656, 296.9218, 298.01, -83.78906, 296.9016, 298.01, -69.72656, 296.979, 298.01, -67.38281, 296.9514, 298.01, -65.03906, 297.2199, 298.01, -62.69531, 296.9622, 298.01, -55.66406, 296.9926, 298.01, -50.97656, 296.9467, 298.01, -48.63281, 297.3652, 298.01, -46.28906, 297.0439, 298.01, -43.94531, 297.2875, 298.01, -39.25781, 297.0121, 298.01, -34.57031, 297.1564, 298.01, -32.22656, 297.3612, 298.01, -29.88281, 297.4229, 298.01, -27.53906, 297.1687, 298.01, -25.19531, 297.4334, 298.01, -18.16406, 297.3612, 298.01, -15.82031, 297.4441, 298.01, -13.47656, 297.4125, 298.01, -11.13281, 297.2468, 298.01, -8.789063, 297.4125, 298.01, -6.445313, 297.373, 298.01, -4.101563, 297.4195, 298.01, -1.757813, 297.077, 298.01, 0.5859375, 297.4229, 298.01, 2.929688, 297.4125, 298.01, 5.273438, 296.9489, 298.01, 7.617188, 297.3168, 298.01, 9.960938, 296.9377, 298.01, 12.30469, 296.8998, 298.01, 14.64844, 297.1975, 298.01, 16.99219, 296.8579, 298.01, 28.71094, 296.878, 298.01, 40.42969, 296.8163, 298.01, 42.77344, 296.8369, 298.01, 49.80469, 296.734, 298.01, 59.17969, 296.6906, 298.01, 61.52344, 296.6365, 298.01, 68.55469, 296.6278, 298.01, 73.24219, 296.5777, 298.01, 75.58594, 296.6191, 298.01, 84.96094, 296.5284, 298.01, 96.67969, 296.5538, 298.01, 103.7109, 296.479, 298.01, 115.4297, 296.4259, 298.01, 122.4609, 296.3434, 298.01, 129.4922, 296.3495, 298.01, 131.8359, 295.9141, 298.01, 136.5234, 296.2256, 298.01, 138.8672, 295.833, 298.01, 143.5547, 295.9857, 298.01, 145.8984, 295.8791, 298.01, 152.9297, 295.833, 298.01, 164.6484, 295.6819, 298.01, 171.6797, 295.6819, 298.01, 181.0547, 295.5401, 298.01, 185.7422, 295.5742, 298.01, 192.7734, 295.557, 298.01, 197.4609, 295.8012, 298.01, 202.1484, 295.6819, 298.01, 204.4922, 295.3698, 298.01, 206.8359, 294.803, 298.01, 209.1797, 294.3656, 298.01, 211.5234, 292.4764, 298.01, 213.8672, 291.1765, 298.01, 216.2109, 289.5873, 298.01, 217.229, 288.6234, 298.01, 218.5547, 287.0752, 298.01, 221.7097, 283.9359, 298.01, 225.5859, 279.8775, 298.01, 227.9297, 277.5633, 298.01, 230.2734, 275.0808, 298.01, 233.1989, 272.2172, 298.01, 234.9609, 270.2887, 298.01, 237.7384, 267.5297, 298.01, 241.9922, 263.0843, 298.01, 244.3359, 260.7643, 298.01, 246.788, 258.1547, 298.01, 249.0234, 255.451, 298.01, 250.3651, 253.4672, 298.01, 251.5297, 251.1234, 298.01, 252.1947, 248.7797, 298.01, 252.4915, 246.4359, 298.01, 252.5755, 241.7484, 298.01, 252.8592, 239.4047, 298.01, 252.9236, 237.0609, 298.01, 252.2924, 234.7172, 298.01, 251.3672, 233.4697, 298.01, 249.0234, 232.882, 298.01, 244.3359, 232.9048, 298.01, 241.9922, 233.0145, 298.01, 232.6172, 232.9048, 298.01, 227.9297, 233.0007, 298.01, 216.2109, 233.0632, 298.01, 211.5234, 233.0537, 298.01, 206.8359, 232.9699, 298.01, 204.4922, 232.7322, 298.01, 199.8047, 232.7186, 298.01, 190.4297, 232.7719, 298.01, 183.3984, 232.7719, 298.01, 181.0547, 232.7322, 298.01, 174.0234, 232.7048, 298.01, 171.6797, 232.7322, 298.01, 166.9922, 232.6908, 298.01, 157.6172, 232.7975, 298.01, 155.2734, 232.7588, 298.01, 148.2422, 232.7875, 298.01, 143.5547, 232.7614, 298.01, 138.8672, 232.6477, 298.01, 124.8047, 232.6179, 298.01, 122.4609, 232.6477, 298.01, 113.0859, 232.6027, 298.01, 110.7422, 232.4552, 298.01, 108.3984, 232.2192, 298.01, 106.0547, 231.6764, 298.01, 103.7109, 231.8559, 298.01, 102.8237, 232.3734, 298.01, 101.3672, 232.9839, 298.01, 99.02344, 233.0951, 298.01, 87.30469, 233.0819, 298.01, 84.96094, 233.1091, 298.01, 80.27344, 233.0726, 298.01, 77.92969, 233.1132, 298.01, 70.89844, 233.1397, 298.01, 68.55469, 233.1132, 298.01, 52.14844, 233.131, 298.01, 45.11719, 233.0859, 298.01, 44.16726, 232.3734, 298.01, 42.77344, 231.0206, 298.01, 42.04498, 230.0297, 298.01, 42.77344, 229.2462, 298.01, 45.11719, 228.5664, 298.01, 47.46094, 227.0695, 298.01, 49.80469, 226.0552, 298.01, 52.14844, 224.5723, 298.01, 54.49219, 223.6857, 298.01, 56.83594, 221.8519, 298.01, 59.17969, 220.2086, 298.01, 61.52344, 218.8854, 298.01, 64.94469, 215.9672, 298.01, 66.21094, 215.0191, 298.01, 67.72036, 213.6234, 298.01, 68.55469, 212.6986, 298.01, 70.89844, 210.5055, 298.01, 74.53191, 206.5922, 298.01, 76.54903, 204.2484, 298.01, 78.26105, 201.9047, 298.01, 80.27344, 198.9262, 298.01, 82.61719, 195.2822, 298.01, 82.98087, 194.8734, 298.01, 84.96094, 190.9255, 298.01, 85.43701, 190.1859, 298.01, 86.33423, 187.8422, 298.01, 87.78722, 185.4984, 298.01, 88.60233, 183.1547, 298.01, 89.10253, 180.8109, 298.01, 90.17504, 178.4672, 298.01, 90.88959, 176.1234, 298.01, 91.43783, 173.7797, 298.01, 92.39601, 171.4359, 298.01, 92.95762, 169.0922, 298.01, 93.55695, 159.7172, 298.01, 93.65527, 157.3734, 298.01, 93.67542, 152.6859, 298.01, 93.61213, 150.3422, 298.01, 93.22542, 143.3109, 298.01, 93.06345, 140.9672, 298.01, 92.77563, 138.6234, 298.01, 91.21714, 133.9359, 298.01, 90.67235, 131.5922, 298.01, 89.88776, 129.2484, 298.01, 88.8737, 126.9047, 298.01, 88.44087, 124.5609, 298.01, 86.09712, 119.8734, 298.01, 85.05786, 117.5297, 298.01, 83.87151, 115.1859, 298.01, 82.22388, 112.8422, 298.01, 81.09117, 110.4984, 298.01, 77.92969, 106.4052, 298.01, 77.3894, 105.8109, 298.01, 75.94332, 103.4672, 298.01, 71.71799, 98.77969, 298.01, 68.55469, 95.65721, 298.01, 63.86719, 91.54878, 298.01, 61.52344, 90.1121, 298.01, 59.17969, 88.15762, 298.01, 56.83594, 86.51503, 298.01, 54.49219, 85.42721, 298.01, 52.14844, 83.64907, 298.01, 49.80469, 82.89023, 298.01, 47.46094, 81.50237, 298.01, 45.11719, 80.62591, 298.01, 42.77344, 79.18153, 298.01, 40.42969, 78.7203, 298.01, 38.08594, 78.1349, 298.01, 35.74219, 77.11755, 298.01, 33.39844, 76.51949, 298.01, 31.05469, 76.07934, 298.01, 26.36719, 74.67744, 298.01, 24.02344, 74.42056, 298.01, 14.64844, 74.07317, 298.01, 9.960938, 74.11538, 298.01, 2.929688, 74.40105, 298.01, 0.5859375, 74.67952, 298.01, -1.757813, 75.31406, 298.01, -4.101563, 76.07065, 298.01, -6.445313, 76.49051, 298.01, -8.789063, 77.17276, 298.01, -11.13281, 78.20097, 298.01, -15.82031, 79.31967, 298.01, -18.16406, 80.76948, 298.01, -20.50781, 81.64266, 298.01, -22.85156, 83.0305, 298.01, -25.19531, 83.7937, 298.01, -27.53906, 85.63515, 298.01, -29.88281, 86.7363, 298.01, -32.22656, 88.36089, 298.01, -34.57031, 90.3302, 298.01, -36.56719, 91.74844, 298.01, -41.60156, 95.93605, 298.01, -46.58845, 101.1234, 298.01, -50.17995, 105.8109, 298.01, -52.10386, 108.1547, 298.01, -53.63992, 110.4984, 298.01, -54.95532, 112.8422, 298.01, -56.64794, 115.1859, 298.01, -57.4403, 117.5297, 298.01, -58.91927, 119.8734, 298.01, -59.78655, 122.2172, 298.01, -61.11754, 124.5609, 298.01, -61.58921, 126.9047, 298.01, -62.38012, 129.2484, 298.01, -63.49118, 131.5922, 298.01, -64.02599, 133.9359, 298.01, -64.3932, 136.2797, 298.01, -65.11897, 138.6234, 298.01, -65.64544, 140.9672, 298.01, -66.23938, 147.9984, 298.01, -66.46289, 152.6859, 298.01, -66.48911, 155.0297, 298.01, -66.34437, 159.7172, 298.01, -65.99894, 164.4047, 298.01, -65.49149, 169.0922, 298.01, -64.6875, 171.4359, 298.01, -63.7739, 176.1234, 298.01, -62.9398, 178.4672, 298.01, -61.86011, 180.8109, 298.01, -61.33423, 183.1547, 298.01, -60.43332, 185.4984, 298.01, -58.00781, 190.0632, 298.01, -56.85406, 192.5297, 298.01, -55.66406, 194.7283, 298.01, -54.11692, 197.2172, 298.01, -50.97656, 201.8369, 298.01, -47.36435, 206.5922, 298.01, -45.04395, 208.9359, 298.01, -42.83026, 211.2797, 298.01, -39.25781, 214.7435, 298.01, -34.57031, 218.4974, 298.01, -32.22656, 219.9595, 298.01, -28.02053, 222.9984, 298.01, -27.53906, 223.4238, 298.01, -25.19531, 224.4187, 298.01, -22.85156, 225.8252, 298.01, -20.50781, 226.9067, 298.01, -18.16406, 228.4286, 298.01, -15.82031, 229.1235, 298.01, -14.9447, 230.0297, 298.01, -15.82031, 231.3969, 298.01, -16.94484, 232.3734, 298.01 }; + size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); + std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); + EXPECT_EQ(358u * 3, groundTruth.size()); + CheckGroundTruth(structures, 5, 8, groundTruth); + } +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#if 0 + +TEST(StructureSet2, ReadFromJsonAndCompute1) +{ + DicomStructureSet2 structureSet; + + OrthancPlugins::FullOrthancDataset dicom(GetTestJson()); + structureSet.Clear(); + + structureSet.FillStructuresFromDataset(dicom); + + structureSet.ComputeDependentProperties(); +} + +TEST(StructureSet2, ReadFromJsonAndCompute2) +{ + DicomStructureSet2 structureSet; + + OrthancPlugins::FullOrthancDataset dicom(GetTestJson()); + structureSet.Clear(); + + structureSet.SetContents(dicom); +} +#endif + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +static bool CutStructureWithPlane( + std::vector< std::pair >& segments, + const DicomStructure2& structure, + const double originX, const double originY, const double originZ, + const double axisX_X, const double axisX_Y, const double axisX_Z, + const double axisY_X, const double axisY_Y, const double axisY_Z +) +{ + // create an AXIAL cutting plane, too far away from the volume + // (> sliceThickness/2) + Vector origin, axisX, axisY; + LinearAlgebra::AssignVector(origin, originX, originY, originZ); + LinearAlgebra::AssignVector(axisX, axisX_X, axisX_Y, axisX_Z); + LinearAlgebra::AssignVector(axisY, axisY_X, axisY_Y, axisY_Z); + CoordinateSystem3D cuttingPlane(origin, axisX, axisY); + + // compute intersection + bool ok = structure.Project(segments, cuttingPlane); + return ok; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +static void CreateBasicStructure(DicomStructure2& structure) +{ + // see https://www.dropbox.com/s/1o1vg53hsbvx4cc/test-rtstruct-polygons.jpg?dl=0 + EXPECT_EQ(pointsCoord1Count, pointsCoord2Count); + EXPECT_EQ(11u, pointsCoord2Count); + + for (size_t slice = 0; slice < 3; ++slice) + { + DicomStructurePolygon2 polygon("Oblomptu", "CLOSED_PLANAR"); + for (size_t ip = 0; ip < pointsCoord1Count; ++ip) + { + Vector pt; + double pt0 = pointsCoord1[ip]; + double pt1 = pointsCoord2[ip]; + double pt2 = 4 * (static_cast(slice) - 1); // -4, 0, 4 + LinearAlgebra::AssignVector(pt, pt0, pt1, pt2); + polygon.AddPoint(pt); + } + structure.AddPolygon(polygon); + } + structure.ComputeDependentProperties(); +} + + +TEST(StructureSet2, CutAxialOutsideTop) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an AXIAL cutting plane, too far away from the volume + // (> sliceThickness/2) + bool ok = CutStructureWithPlane(segments, structure, + 0, 0, 7, + 1, 0, 0, + 0, 1, 0); + EXPECT_FALSE(ok); +} + + +TEST(StructureSet2, CutAxialOutsideBottom) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an AXIAL cutting plane, too far away from the volume + // (> sliceThickness/2) + bool ok = CutStructureWithPlane(segments, structure, + 0, 0, -6.66, + 1, 0, 0, + 0, 1, 0); + EXPECT_FALSE(ok); +} + +TEST(StructureSet2, CutAxialInsideClose) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an AXIAL cutting plane in the volume + bool ok = CutStructureWithPlane(segments, structure, + 0, 0, 1.1, + 1, 0, 0, + 0, 1, 0); + EXPECT_TRUE(ok); + EXPECT_EQ(POLYGON_POINT_COUNT, segments.size()); + + for (size_t i = 0; i < segments.size(); ++i) + { + EXPECT_LT(i, POLYGON_POINT_COUNT); + EXPECT_LT(i, POLYGON_POINT_COUNT); + + const ScenePoint2D& pt = segments[i].first; + + // ...should be at the same location as the 3D coords since the plane + // is rooted at 0,0,0 with normal 0,0,1 + EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX); + EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX); + } +} + + +TEST(StructureSet2, CutAxialInsideFar) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an AXIAL cutting plane + Vector origin, axisX, axisY; + LinearAlgebra::AssignVector(origin, 0, 0, 0); + LinearAlgebra::AssignVector(axisX, 1, 0, 0); + LinearAlgebra::AssignVector(axisY, 0, 1, 0); + CoordinateSystem3D cuttingPlane(origin, axisX, axisY); + + // compute intersection + bool ok = structure.Project(segments, cuttingPlane); + EXPECT_TRUE(ok); + + EXPECT_EQ(11u, segments.size()); + for (size_t i = 0; i < segments.size(); ++i) + { + EXPECT_LT(i, pointsCoord1Count); + EXPECT_LT(i, pointsCoord2Count); + + // the 2D points of the projected polygon + const ScenePoint2D& pt = segments[i].first; + + // ...should be at the same location as the 3D coords since the plane + // is rooted at 0,0,0 with normal 0,0,1 + EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX); + EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX); + } +} + +TEST(StructureSet2, CutCoronalOutsideClose) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an X,Z cutting plane, outside of the volume + Vector origin, axisX, axisY; + LinearAlgebra::AssignVector(origin, 0, 0, 0); + LinearAlgebra::AssignVector(axisX, 1, 0, 0); + LinearAlgebra::AssignVector(axisY, 0, 0, 1); + CoordinateSystem3D cuttingPlane(origin, axisX, axisY); + + // compute intersection + bool ok = structure.Project(segments, cuttingPlane); + EXPECT_FALSE(ok); +} + +TEST(StructureSet2, CutCoronalInsideClose1DTest) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + + // create an X,Z cutting plane, outside of the volume + Vector origin, axisX, axisY; + LinearAlgebra::AssignVector(origin, 0, 3, 0); + LinearAlgebra::AssignVector(axisX, 1, 0, 0); + LinearAlgebra::AssignVector(axisY, 0, 0, 1); + CoordinateSystem3D cuttingPlane(origin, axisX, axisY); + + ASSERT_EQ(3u, structure.GetPolygons().size()); + + for (int i = 0; i < 3; ++i) + { + double polygonZ = static_cast(i - 1) * 4.0; + + const DicomStructurePolygon2& topSlab = structure.GetPolygons()[i]; + + // let's compute the intersection between the polygon and the plane + // intersections are in plane coords + std::vector intersects; + topSlab.ProjectOnConstantPlane(intersects, cuttingPlane); + + ASSERT_EQ(4u, intersects.size()); + + EXPECT_NEAR(2, intersects[0].GetX(), DELTA_MAX); + EXPECT_NEAR(4, intersects[1].GetX(), DELTA_MAX); + EXPECT_NEAR(7, intersects[2].GetX(), DELTA_MAX); + EXPECT_NEAR(8, intersects[3].GetX(), DELTA_MAX); + + for (size_t i = 0; i < 4u; ++i) + { + EXPECT_NEAR(polygonZ, intersects[i].GetY(), DELTA_MAX); + } + } +} + +TEST(StructureSet2, CutCoronalInsideClose) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an X,Z cutting plane, outside of the volume + Vector origin, axisX, axisY; + LinearAlgebra::AssignVector(origin, 0, 3, 0); + LinearAlgebra::AssignVector(axisX, 1, 0, 0); + LinearAlgebra::AssignVector(axisY, 0, 0, 1); + CoordinateSystem3D cuttingPlane(origin, axisX, axisY); + + // compute intersection + ASSERT_TRUE(structure.Project(segments, cuttingPlane)); + EXPECT_EQ(24u, segments.size()); + + size_t numberOfVeryShortSegments = 0; + for (size_t iSegment = 0; iSegment < segments.size(); ++iSegment) + { + // count the NON vertical very short segments + if (LinearAlgebra::IsNear(segments[iSegment].first.GetX(), segments[iSegment].second.GetX())) + { + if (LinearAlgebra::IsNear(segments[iSegment].first.GetY(), segments[iSegment].second.GetY())) + { + numberOfVeryShortSegments++; + } + } + } + EXPECT_EQ(8u, numberOfVeryShortSegments); +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +TEST(DisjointDataSet, BasicTest) +{ + const size_t ITEM_COUNT = 10; + DisjointDataSet ds(ITEM_COUNT); + + for (size_t i = 0; i < ITEM_COUNT; ++i) + { + EXPECT_EQ(i, ds.Find(i)); + } + + ds.Union(0, 4); + EXPECT_EQ(0u, ds.Find(0)); + EXPECT_EQ(0u, ds.Find(4)); + + ds.Union(4, 6); + ds.Union(8, 9); + ds.Union(0, 8); + + for (size_t i = 0; i < ITEM_COUNT; ++i) + { + size_t parent = ds.Find(i); + EXPECT_TRUE(0 == parent || 1 == parent || 2 == parent || 3 == parent || 5 == parent || 7 == parent); + } + + ds.Union(1, 2); + ds.Union(1, 7); + for (size_t i = 0; i < ITEM_COUNT; ++i) + { + size_t parent = ds.Find(i); + EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent || 5 == parent); + } + + ds.Union(3, 5); + for (size_t i = 0; i < ITEM_COUNT; ++i) + { + size_t parent = ds.Find(i); + EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent); + } + + EXPECT_EQ(ds.Find(0), ds.Find(0)); + EXPECT_EQ(ds.Find(0), ds.Find(4)); + EXPECT_EQ(ds.Find(0), ds.Find(6)); + EXPECT_EQ(ds.Find(0), ds.Find(8)); + EXPECT_EQ(ds.Find(0), ds.Find(8)); + + EXPECT_EQ(ds.Find(1), ds.Find(7)); + EXPECT_EQ(ds.Find(2), ds.Find(1)); + EXPECT_EQ(ds.Find(7), ds.Find(2)); + + EXPECT_EQ(ds.Find(3), ds.Find(5)); + EXPECT_EQ(ds.Find(5), ds.Find(3)); + + ds.Union(0, 1); + ds.Union(3, 1); + for (size_t i = 0; i < ITEM_COUNT; ++i) + { + EXPECT_EQ(ds.Find(0), ds.Find(i)); + } +} + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +TEST(StructureSet2, CutSagittalInsideClose) +{ + DicomStructure2 structure; + CreateBasicStructure(structure); + std::vector< std::pair > segments; + + // create an X,Z cutting plane, inside of the volume + Vector origin, axisX, axisY; + LinearAlgebra::AssignVector(origin, 0, 3, 0); + LinearAlgebra::AssignVector(axisX, 1, 0, 0); + LinearAlgebra::AssignVector(axisY, 0, 0, 1); + CoordinateSystem3D cuttingPlane(origin, axisX, axisY); + + // compute intersection + bool ok = structure.Project(segments, cuttingPlane); + EXPECT_TRUE(ok); +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + +static size_t ConvertListOfSlabsToSegments_Add(RtStructRectanglesInSlab& rectangles, int row, double xmin, double xmax) +{ + double ymin = static_cast(row) * 5.0; + double ymax = static_cast(row + 1) * 5.0; + + RtStructRectangleInSlab rectangle; + rectangle.xmin = xmin; + rectangle.xmax = xmax; + rectangle.ymin = ymin; + rectangle.ymax = ymax; + + rectangles.push_back(rectangle); + + return 1u; +} + +static size_t FillTestRectangleList(std::vector< RtStructRectanglesInSlab >& rectanglesForEachSlab) +{ + // ConvertListOfSlabsToSegments + size_t rectCount = 0; + + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 5, 31); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 36, 50); + + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 20, 45); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 52, 70); + + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 0, 32); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 35, 44); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 60, 75); + + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 10, 41); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 46, 80); + + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 34, 42); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 90, 96); + + rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 1, 33); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 40, 43); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 51, 61); + rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 76, 95); + + return rectCount; +} + +/* +void AddSlabBoundaries( + std::vector >& boundaries, + const std::vector& slabCuts, size_t iSlab) +*/ + + +/* +void ProcessBoundaryList( + std::vector< std::pair >& segments, + const std::vector >& boundaries, + double y) +*/ + + +TEST(StructureSet2, ProcessBoundaryList_Empty) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + + boundaries.clear(); + EXPECT_NO_THROW(AddSlabBoundaries(boundaries, slabCuts, 0)); + ASSERT_EQ(0u, boundaries.size()); +} + +TEST(StructureSet2, ProcessBoundaryListTopRow) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + FillTestRectangleList(slabCuts); + + boundaries.clear(); + AddSlabBoundaries(boundaries, slabCuts, 0); + + { + size_t i = 0; + ASSERT_EQ(4u, boundaries.size()); + + ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); + ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); + ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); + ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); + ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX); + i++; + } +} + +TEST(StructureSet2, ProcessBoundaryListRows_0_and_1) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + FillTestRectangleList(slabCuts); + + boundaries.clear(); + AddSlabBoundaries(boundaries, slabCuts, 0); + AddSlabBoundaries(boundaries, slabCuts, 1); + + ASSERT_EQ(8u, boundaries.size()); + + { + size_t i = 0; + + ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); + ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); + ASSERT_NEAR(20, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); + ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); + ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); + ASSERT_NEAR(45, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); + ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); + ASSERT_NEAR(52, boundaries[i].first, DELTA_MAX); + i++; + + ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); + ASSERT_NEAR(70, boundaries[i].first, DELTA_MAX); + i++; + } +} + +TEST(StructureSet2, ConvertListOfSlabsToSegments_EmptyBoundaries) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + FillTestRectangleList(slabCuts); + boundaries.clear(); + std::vector< std::pair > segments; + ASSERT_NO_THROW(ProcessBoundaryList(segments, boundaries, 42.0)); + ASSERT_EQ(0u, segments.size()); +} + +TEST(StructureSet2, ConvertListOfSlabsToSegments_TopRow_Horizontal) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + FillTestRectangleList(slabCuts); + + // top row + { + std::vector< std::pair > segments; + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, 0); + ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin); + + ASSERT_EQ(2u, segments.size()); + + ASSERT_NEAR( 5.0, segments[0].first.GetX(), DELTA_MAX); + ASSERT_NEAR(31.0, segments[0].second.GetX(), DELTA_MAX); + ASSERT_NEAR( 0.0, segments[0].first.GetY(), DELTA_MAX); + ASSERT_NEAR( 0.0, segments[0].second.GetY(), DELTA_MAX); + + ASSERT_NEAR(36.0, segments[1].first.GetX(), DELTA_MAX); + ASSERT_NEAR(50.0, segments[1].second.GetX(), DELTA_MAX); + ASSERT_NEAR( 0.0, segments[1].first.GetY(), DELTA_MAX); + ASSERT_NEAR( 0.0, segments[1].second.GetY(), DELTA_MAX); + } +} + +TEST(StructureSet2, ConvertListOfSlabsToSegments_All_Horizontal) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + FillTestRectangleList(slabCuts); + + // top row + { + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, 0); + std::vector< std::pair > segments; + ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin); + } + + // mids + { + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, 0); + AddSlabBoundaries(boundaries, slabCuts, 1); + std::vector< std::pair > segments; + ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymax); + + ASSERT_EQ(4u, segments.size()); + + ASSERT_NEAR(05.0, segments[0].first.GetX(), DELTA_MAX); + ASSERT_NEAR(20.0, segments[0].second.GetX(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[0].first.GetY(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[0].second.GetY(), DELTA_MAX); + + ASSERT_NEAR(31.0, segments[1].first.GetX(), DELTA_MAX); + ASSERT_NEAR(36.0, segments[1].second.GetX(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[1].first.GetY(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[1].second.GetY(), DELTA_MAX); + + ASSERT_NEAR(45.0, segments[2].first.GetX(), DELTA_MAX); + ASSERT_NEAR(50.0, segments[2].second.GetX(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[2].first.GetY(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[2].second.GetY(), DELTA_MAX); + + ASSERT_NEAR(52.0, segments[3].first.GetX(), DELTA_MAX); + ASSERT_NEAR(70.0, segments[3].second.GetX(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[3].first.GetY(), DELTA_MAX); + ASSERT_NEAR(05.0, segments[3].second.GetY(), DELTA_MAX); + } + + // bottom row + { + std::vector > boundaries; + AddSlabBoundaries(boundaries, slabCuts, 1); + std::vector< std::pair > segments; + ProcessBoundaryList(segments, boundaries, slabCuts[1][0].ymax); + + ASSERT_EQ(2u, segments.size()); + + ASSERT_NEAR(20.0, segments[0].first.GetX(), DELTA_MAX); + ASSERT_NEAR(45.0, segments[0].second.GetX(), DELTA_MAX); + ASSERT_NEAR(10.0, segments[0].first.GetY(), DELTA_MAX); + ASSERT_NEAR(10.0, segments[0].second.GetY(), DELTA_MAX); + + ASSERT_NEAR(52.0, segments[1].first.GetX(), DELTA_MAX); + ASSERT_NEAR(70.0, segments[1].second.GetX(), DELTA_MAX); + ASSERT_NEAR(10.0, segments[1].first.GetY(), DELTA_MAX); + ASSERT_NEAR(10.0, segments[1].second.GetY(), DELTA_MAX); + } + +} + +TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Empty) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + + std::vector< std::pair > segments; + + ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, 0)); + ASSERT_EQ(0u, segments.size()); +} + +TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Regular) +{ + std::vector< RtStructRectanglesInSlab > slabCuts; + std::vector > boundaries; + size_t totalRectCount = FillTestRectangleList(slabCuts); + + std::vector< std::pair > segments; + + ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, totalRectCount)); + ASSERT_EQ(60u, segments.size()); + + size_t i = 0; + + ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 50.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 52.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 32.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 35.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 60.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 41.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 46.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 80.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 96.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 1.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 33.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 43.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 51.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 61.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 76.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); + i++; + ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX); + ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); +} + +#if defined(BGO_ENABLE_DICOMSTRUCTURESETLOADER2) && (ORTHANC_SANDBOXED != 1) + +#include + +TEST(StructureSet2, ReadFromJsonPart2) +{ + DicomStructureSet2 structureSet; + std::string jsonText; + + Orthanc::SystemToolbox::ReadFile(jsonText, "72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json"); + + FullOrthancDataset dicom(jsonText); + structureSet.Clear(); + + structureSet.FillStructuresFromDataset(dicom); + structureSet.ComputeDependentProperties(); + + //const std::vector& structures = structureSet.structures_; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructure2.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructure2.cpp Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructure2.h" - -#include "GeometryToolbox.h" -#include "DisjointDataSet.h" - -#include - -namespace OrthancStone -{ - // see header - //void DicomStructure2::ComputeNormal() - //{ - // try - // { - // if (polygons_.size() > 0) - // { - - // // TODO: check all polygons are OK - // const DicomStructurePolygon2 polygon = polygons_[0]; - // $$$$$$$$$$$$$$$$$ - // state_ = NormalComputed; - // } - // else - // { - // // bogus! no polygons. Let's assign a "nothing here" value - // LinearAlgebra::AssignVector(normal_, 0, 0, 0); - // state_ = Invalid; - // } - // } - // catch (const Orthanc::OrthancException& e) - // { - // state_ = Invalid; - // if (e.HasDetails()) - // { - // LOG(ERROR) << "OrthancException in ComputeNormal: " << e.What() << " Details: " << e.GetDetails(); - // } - // else - // { - // LOG(ERROR) << "OrthancException in ComputeNormal: " << e.What(); - // } - // throw; - // } - // catch (const std::exception& e) - // { - // state_ = Invalid; - // LOG(ERROR) << "std::exception in ComputeNormal: " << e.what(); - // throw; - // } - // catch (...) - // { - // state_ = Invalid; - // LOG(ERROR) << "Unknown exception in ComputeNormal"; - // throw; - // } - //} - - void DicomStructure2::ComputeSliceThickness() - { - if (state_ != NormalComputed) - { - LOG(ERROR) << "DicomStructure2::ComputeSliceThickness - state must be NormalComputed"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - if (polygons_.size() < 2) - { - // cannot compute thickness if there are not at least 2 slabs (structures) - sliceThickness_ = 1.0; - state_ = Invalid; - } - else - { - // normal can be (1,0,0), (0,1,0) or (0,0,1), nothing else. - // these can be compared with == (exact double representation) - if (normal_[0] == 1) - { - // in a single polygon, all the points have the same X - sliceThickness_ = fabs(polygons_[0].GetPoint(0)[0] - polygons_[1].GetPoint(0)[0]); - } - else if (normal_[1] == 1) - { - // in a single polygon, all the points have the same X - sliceThickness_ = fabs(polygons_[0].GetPoint(0)[1] - polygons_[1].GetPoint(0)[1]); - } - else if (normal_[2] == 1) - { - // in a single polygon, all the points have the same X - sliceThickness_ = fabs(polygons_[0].GetPoint(0)[2] - polygons_[1].GetPoint(0)[2]); - } - else - { - ORTHANC_ASSERT(false); - state_ = Invalid; - } - } - state_ = Valid; - } - - void DicomStructure2::AddPolygon(const DicomStructurePolygon2& polygon) - { - if (state_ != Building) - { - LOG(ERROR) << "DicomStructure2::AddPolygon - can only add polygon while building"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - polygons_.push_back(polygon); - } - - void DicomStructure2::ComputeDependentProperties() - { - if (state_ != Building) - { - LOG(ERROR) << "DicomStructure2::ComputeDependentProperties - can only be called once"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - for (size_t i = 0; i < polygons_.size(); ++i) - { - // "compute" the polygon normal - polygons_[i].ComputeDependentProperties(); - } - if (polygons_.size() > 0) - { - normal_ = polygons_[0].GetNormal(); - state_ = NormalComputed; - } - else - { - LinearAlgebra::AssignVector(normal_, 0, 0, 0); - state_ = Invalid; // THIS MAY HAPPEN !!! (for instance for instance 72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661 :) ) - } - if (polygons_.size() >= 2) - ComputeSliceThickness(); // this will change state_ from NormalComputed to Valid - } - - Vector DicomStructure2::GetNormal() const - { - if (state_ != Valid && state_ != Invalid) - { - LOG(ERROR) << "DicomStructure2::GetNormal() -- please call ComputeDependentProperties first."; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - if (state_ == Invalid) - { - LOG(ERROR) << "DicomStructure2::GetNormal() -- The Dicom structure is invalid. The normal is set to 0,0,0"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - return normal_; - } - - const DicomStructurePolygon2* DicomStructure2::GetPolygonClosestToSlice( - const CoordinateSystem3D& plane) const - { - ORTHANC_ASSERT(state_ == Valid); - - // we assume 0,0,1 for now - ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0)); - ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0)); - - for (size_t i = 0; i < polygons_.size(); ++i) - { - const DicomStructurePolygon2& polygon = polygons_[i]; - - // "height" of cutting plane - double cutZ = plane.GetOrigin()[2]; - - if (LinearAlgebra::IsNear( - cutZ, polygon.GetZ(), - sliceThickness_ / 2.0 /* in mm */)) - return &polygon; - } - return NULL; - } - - - bool DicomStructure2::Project(std::vector< std::pair > & segments, const CoordinateSystem3D & plane) const - { - segments.clear(); - - Vector normal = GetNormal(); - - size_t totalRectCount = 0; - - // dummy var - bool isOpposite = false; - - // This is an axial projection - if (GeometryToolbox::IsParallelOrOpposite(isOpposite, normal, plane.GetNormal())) - { - const DicomStructurePolygon2* polygon = GetPolygonClosestToSlice(plane); - if (polygon) - { - polygon->ProjectOnParallelPlane(segments, plane); - } - } - else - { - // let's compute the dot product of the plane normal and the polygons - // normal. - double dot = LinearAlgebra::DotProduct(plane.GetNormal(), normal); - - if (LinearAlgebra::IsNear(dot, 0)) - { - // Coronal or sagittal projection - - // vector of vector of rectangles that will be merged in a single big contour: - - // each polygon slab cut by a perpendicular plane yields 0..* rectangles - std::vector< RtStructRectanglesInSlab > rectanglesForEachSlab; - - for (size_t i = 0; i < polygons_.size(); ++i) - { - // book an entry for this slab - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - - // let's compute the intersection between the polygon and the plane - // intersections are in plane coords - std::vector intersections; - - polygons_[i].ProjectOnConstantPlane(intersections, plane); - - // for each pair of intersections, we add a rectangle. - if ((intersections.size() % 2) != 0) - { - LOG(WARNING) << "Odd number of intersections between structure " - << name_ << ", polygon # " << i - << " and plane where X axis is parallel to polygon normal vector"; - } - - size_t numRects = intersections.size() / 2; - - // we keep count of the total number of rects for vector pre-allocations - totalRectCount += numRects; - - for (size_t iRect = 0; iRect < numRects; ++iRect) - { - RtStructRectangleInSlab rectangle; - ORTHANC_ASSERT(LinearAlgebra::IsNear(intersections[2 * iRect].GetY(), intersections[2 * iRect + 1].GetY())); - ORTHANC_ASSERT((2 * iRect + 1) < intersections.size()); - double x1 = intersections[2 * iRect].GetX(); - double x2 = intersections[2 * iRect + 1].GetX(); - double y1 = intersections[2 * iRect].GetY() - sliceThickness_ * 0.5; - double y2 = intersections[2 * iRect].GetY() + sliceThickness_ * 0.5; - - rectangle.xmin = std::min(x1, x2); - rectangle.xmax = std::max(x1, x2); - rectangle.ymin = std::min(y1, y2); - rectangle.ymax = std::max(y1, y2); - - // TODO: keep them sorted!!!! - - rectanglesForEachSlab.back().push_back(rectangle); - } - } - // now we need to merge all the slabs into a set of polygons (1 or more) - ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, totalRectCount); - } - else - { - // plane is not perpendicular to the polygons - // 180.0 / [Math]::Pi = 57.2957795130823 - double acDot = 57.2957795130823 * acos(dot); - LOG(ERROR) << "DicomStructure2::Project -- cutting plane must be " - << "perpendicular to the structures, but dot product is: " - << dot << " and (180/pi)*acos(dot) = " << acDot; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - return segments.size() != 0; - } -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructure2.h --- a/OrthancStone/Sources/Toolbox/DicomStructure2.h Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#pragma once - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructurePolygon2.h" -#include "DicomStructureSetUtils.h" - -namespace OrthancStone -{ - - /* - A structure has a color, a name, a set of slices.. - - Each slice is a polygon. - */ - struct DicomStructure2 - { - DicomStructure2() : - red_(0), green_(0), blue_(0), sliceThickness_(0), state_(Building) {} - - void AddPolygon(const DicomStructurePolygon2& polygon); - - /** - Once all polygons have been added, this method will determine: - - the slice orientation (through the normal vector) - - the spacing between slices (slice thickness) - - it will also set up the info required to efficiently compute plane - intersections later on. - */ - void ComputeDependentProperties(); - - /** - Being given a plane that is PARALLEL to the set of polygon structures, this - returns a pointer to the polygon located at that position (if it is closer - than thickness/2) or NULL if there is none. - - TODO: use sorted vector to improve - - DO NOT STORE THE RETURNED POINTER! - */ - const DicomStructurePolygon2* GetPolygonClosestToSlice(const CoordinateSystem3D& plane) const; - - Vector GetNormal() const; - - Color GetColor() const - { - return Color(red_, green_, blue_); - } - - bool IsValid() const - { - return state_ == Valid; - } - - /** - This method is used to project the 3D structure on a 2D plane. - - A structure is a stack of polygons, representing a volume. - - We need to compute the intersection between this volume and the supplied - cutting plane (the "slice"). This is more than a cutting plane: it is also - a 2D-coordinate system (the plane has axes vectors) - - The cutting plane is always parallel to the plane defined by two of the - world coordinate system axes. - - The result is a set of closed polygons. - - If the cut is parallel to the polygons, we pick the polygon closest to - the slice, project it on the slice and return it in slice coordinates. - - If the cut is perpendicular to the polygons, for each polygon, we compute - the intersection between the cutting plane and the polygon slab (imaginary - volume created by extruding the polygon above and below its plane by - thickness/2) : - - each slab, intersected by the plane, gives a set of 0..* rectangles \ - (only one if the polygon is convex) - - when doing this for the whole stack of slabs, we get a set of rectangles: - To compute these rectangles, for each polygon, we compute the intersection - between : - - the line defined by the intersection of the polygon plane and the cutting - plane - - the polygon itself - This yields 0 or 2*K points along the line C. These are turned into K - rectangles by taking two consecutive points along the line and extruding - this segment by sliceThickness/2 in the orientation of the polygon normal, - in both directions. - - Then, once this list of rectangles is computed, we need to group the - connected rectangles together. Connected, here, means sharing at least part - of an edge --> union/find data structures and algorithm. - */ - bool Project(std::vector< std::pair >& polygons, const CoordinateSystem3D& plane) const; - - std::string interpretation_; - std::string name_; - uint8_t red_; - uint8_t green_; - uint8_t blue_; - - /** Internal */ - const std::vector& GetPolygons() const - { - return polygons_; - } - - /** Internal */ - double GetSliceThickness() const - { - return sliceThickness_; - } - - private: - enum State - { - Building, - NormalComputed, - Valid, // When normal components AND slice thickness are computed - Invalid - }; - - void ComputeNormal(); - void ComputeSliceThickness(); - - std::vector polygons_; - Vector normal_; - double sliceThickness_; - - /* - After creation (and while polygons are added), state is Building. - After ComputeDependentProperties() is called, state can either be - Valid or Invalid. In any case, the object becomes immutable. - */ - State state_; - }; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructurePolygon2.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructurePolygon2.cpp Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,307 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructurePolygon2.h" - -#include "../Toolbox/LinearAlgebra.h" - -#include - -namespace OrthancStone -{ - void DicomStructurePolygon2::ComputeDependentProperties() - { - ORTHANC_ASSERT(state_ == Building); - - for (size_t j = 0; j < points_.size(); ++j) - { - // TODO: move to AddPoint! - const Vector& p = points_[j]; - if (p[0] < minX_) - minX_ = p[0]; - if (p[0] > maxX_) - maxX_ = p[0]; - - if (p[1] < minY_) - minY_ = p[1]; - if (p[1] > maxY_) - maxY_ = p[1]; - - if (p[2] < minZ_) - minZ_ = p[2]; - if (p[2] > maxZ_) - maxZ_ = p[2]; - } - - if (LinearAlgebra::IsNear(minX_, maxX_)) - { - LinearAlgebra::AssignVector(normal_, 1, 0, 0); - //ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX, maxX)); - ORTHANC_ASSERT(!LinearAlgebra::IsNear(minY_, maxY_)); - ORTHANC_ASSERT(!LinearAlgebra::IsNear(minZ_, maxZ_)); - } - else if (LinearAlgebra::IsNear(minY_, maxY_)) - { - LinearAlgebra::AssignVector(normal_, 0, 1, 0); - ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX_, maxX_)); - ORTHANC_ASSERT(!LinearAlgebra::IsNear(minZ_, maxZ_)); - } - else if (LinearAlgebra::IsNear(minZ_, maxZ_)) - { - LinearAlgebra::AssignVector(normal_, 0, 0, 1); - ORTHANC_ASSERT(!LinearAlgebra::IsNear(minX_, maxX_)); - ORTHANC_ASSERT(!LinearAlgebra::IsNear(minY_, maxY_)); - } - else - { - LOG(ERROR) << "The contour is not coplanar and not parallel to any axis."; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - state_ = Valid; - } - - - void DicomStructurePolygon2::ProjectOnConstantPlane( - std::vector& intersections, const CoordinateSystem3D& plane) const - { - // the plane can either have constant X, or constant Y. - // - for constant Z planes, use the ProjectOnParallelPlane method - // - other type of planes are not supported - - // V is the coordinate that is constant in the plane - double planeV = 0.0; - - // if true, then "u" in the code is "x" and "v" is "y". - // (v is constant in the plane) - bool uvxy = false; - - size_t uindex = static_cast(-1); - size_t vindex = static_cast(-1); - - ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[2], 0.0)); - - if (LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0)) - { - // normal is 1,0,0 (or -1,0,0). - // plane is constant X - uindex = 1; - vindex = 0; - - uvxy = false; - planeV = plane.GetOrigin()[0]; - if (planeV < minX_) - return; - if (planeV > maxX_) - return; - } - else if (LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0)) - { - // normal is 0,1,0 (or 0,-1,0). - // plane is constant Y - uindex = 0; - vindex = 1; - - uvxy = true; - planeV = plane.GetOrigin()[1]; - if (planeV < minY_) - return; - if (planeV > maxY_) - return; - } - else - { - // if the following assertion(s) fail(s), it means the plane is NOT a constant-X or constant-Y plane - LOG(ERROR) << "Plane normal must be (a,0,0) or (0,a,0), with a == -1 or a == 1"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - size_t pointCount = GetPointCount(); - if (pointCount >= 3) - { - // this vector will contain the coordinates of the intersection points - // between the plane and the polygon. - // these are expressed in the U coordinate, that is either X or Y, - // depending upon the plane orientation - std::vector uIntersections; - - // we loop on the segments of the polygon (TODO: optimize) - // and we compute the intersection between each segment and the cut - // cutting plane (slice) has a constant X - - for (size_t iPoint = 0; iPoint < pointCount; ++iPoint) - { - double u1 = points_[iPoint][uindex]; - double v1 = points_[iPoint][vindex]; - - double u2 = 0; - double v2 = 0; - - if (iPoint < pointCount - 1) - { - u2 = points_[iPoint + 1][uindex]; - v2 = points_[iPoint + 1][vindex]; - } - else - { - u2 = points_[0][uindex]; - v2 = points_[0][vindex]; - } - - // Check if the segment intersects the plane - if ((std::min(v1, v2) <= planeV) && (std::max(v1, v2) >= planeV)) - { - // special case: the segment is parallel to the plane but close to it - if (LinearAlgebra::IsNear(v1, v2)) - { - // in that case, we choose to label both points as an intersection - double x, y; - plane.ProjectPoint(x, y, points_[iPoint]); - intersections.push_back(ScenePoint2D(x, y)); - - plane.ProjectPoint(x, y, points_[iPoint + 1]); - intersections.push_back(ScenePoint2D(x, y)); - } - else - { - // we are looking for u so that (u,planeV) belongs to the segment - // let's define alpha = (u-u2)/(u1-u2) --> u = alpha*(u1-u2) + u2 - // alpha = (v2-planeV)/(v2-v1) - // because the following two triangles are similar - // [ (planeY,x) , (y2,x2), (planeY,x2) ] or - // [ (planeX,y) , (x2,y2), (planeX,y2) ] - // and - // [ (y1 ,x1) , (y2,x2), (y1 ,x2) ] or - // [ (x1 ,y1) , (x2,y2), (x1 ,y2) ] - - /* - void CoordinateSystem3D::ProjectPoint(double& offsetX, - double& offsetY, - const Vector& point) const - */ - double alpha = (v2 - planeV) / (v2 - v1); - - // get rid of numerical oddities - if (alpha < 0.0) - alpha = 0.0; - if (alpha > 1.0) - alpha = 1.0; - double u = alpha * (u1 - u2) + u2; - - // here is the intersection in world coordinates - Vector intersection; - if(uvxy) - LinearAlgebra::AssignVector(intersection, u, planeV, minZ_); - else - LinearAlgebra::AssignVector(intersection, planeV, u, minZ_); - - // and we convert it to plane coordinates - { - double xi, yi; - plane.ProjectPoint(xi, yi, intersection); - - // we consider that the x axis is always parallel to the polygons - // TODO: is this hypothesis safe?????? - uIntersections.insert(std::lower_bound(uIntersections.begin(), uIntersections.end(), xi), xi); - } - } - } - } // end of for (size_t iPoint = 0; iPoint < pointCount; ++iPoint) - - // now we convert the intersections to plane points - // we consider that the x axis is always parallel to the polygons - // TODO: same hypothesis as above: plane is perpendicular to polygons, - // plane is parallel to the XZ (constant Y) or YZ (constant X) 3D planes - for (size_t i = 0; i < uIntersections.size(); ++i) - { - double x = uIntersections[i]; - intersections.push_back(ScenePoint2D(x, minZ_)); - } - } // end of if (pointCount >= 3) - else - { - LOG(ERROR) << "This polygon has " << pointCount << " vertices, which is less than 3 --> skipping"; - } - } - - void DicomStructurePolygon2::ProjectOnParallelPlane( - std::vector< std::pair >& segments, - const CoordinateSystem3D& plane) const - { - if (points_.size() < 3) - return; - - // the plane is horizontal - ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[0], 0.0)); - ORTHANC_ASSERT(LinearAlgebra::IsNear(plane.GetNormal()[1], 0.0)); - - segments.clear(); - segments.reserve(points_.size()); - // since the returned values need to be expressed in the supplied coordinate - // system, we need to subtract origin_ from the returned points - - double planeOriginX = plane.GetOrigin()[0]; - double planeOriginY = plane.GetOrigin()[1]; - - // precondition: points_.size() >= 3 - for (size_t j = 0; j < points_.size()-1; ++j) - { - // segment between point j and j+1 - - const Vector& point0 = GetPoint(j); - // subtract plane origin x and y - ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY); - - const Vector& point1 = GetPoint(j+1); - // subtract plane origin x and y - ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY); - - segments.push_back(std::pair(p0,p1)); - } - - - // final segment - - const Vector& point0 = GetPoint(points_.size() - 1); - // subtract plane origin x and y - ScenePoint2D p0(point0[0] - planeOriginX, point0[1] - planeOriginY); - - const Vector& point1 = GetPoint(0); - // subtract plane origin x and y - ScenePoint2D p1(point1[0] - planeOriginX, point1[1] - planeOriginY); - - segments.push_back(std::pair(p0, p1)); - } - - double DicomStructurePolygon2::GetZ() const - { - ORTHANC_ASSERT(LinearAlgebra::IsNear(normal_[0], 0.0)); - ORTHANC_ASSERT(LinearAlgebra::IsNear(normal_[1], 0.0)); - ORTHANC_ASSERT(LinearAlgebra::IsNear(minZ_, maxZ_)); - return minZ_; - } -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructurePolygon2.h --- a/OrthancStone/Sources/Toolbox/DicomStructurePolygon2.h Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#pragma once - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "CoordinateSystem3D.h" -#include "DicomStructureSetUtils.h" -#include "Extent2D.h" - -#include "../Scene2D/Color.h" -#include "../StoneException.h" - -#include "OrthancDatasets/FullOrthancDataset.h" - -#include -#include - -namespace OrthancStone -{ - - /** - Only polygons that are planar and parallel to either the X,Y or Z plane - ("X plane" == plane where X is equal to a constant for each point) are - supported. - */ - class DicomStructurePolygon2 - { - public: - enum Type - { - ClosedPlanar, - Unsupported - }; - - DicomStructurePolygon2(std::string referencedSopInstanceUid, const std::string& type) - : referencedSopInstanceUid_(referencedSopInstanceUid) - , state_(Building) - , minX_(std::numeric_limits::max()) - , maxX_(-std::numeric_limits::max()) - , minY_(std::numeric_limits::max()) - , maxY_(-std::numeric_limits::max()) - , minZ_(std::numeric_limits::max()) - , maxZ_(-std::numeric_limits::max()) - , type_(TypeFromString(type)) - { - ORTHANC_ASSERT(type_ == ClosedPlanar); - } - - void ComputeDependentProperties(); - - size_t GetPointCount() const - { - ORTHANC_ASSERT(state_ == Valid); - return points_.size(); - } - - const Vector& GetPoint(size_t i) const - { - ORTHANC_ASSERT(state_ == Valid); - return points_.at(i); - } - - void AddPoint(const Vector& v) - { - ORTHANC_ASSERT(state_ == Building); - points_.push_back(v); - } - - void Reserve(size_t n) - { - ORTHANC_ASSERT(state_ == Building); - points_.reserve(n); - } - - /** - This method takes a plane+coord system that is parallel to the polygon - and adds to polygons a new vector with the ordered set of points projected - on the plane, in the plane coordinate system. - */ - void ProjectOnParallelPlane( - std::vector< std::pair >& segments, - const CoordinateSystem3D& plane) const; - - /** - Returns the coordinates of the intersection of the polygon and a plane - that is perpendicular to the polygons (plane has either constant X or - constant Y) - */ - void ProjectOnConstantPlane( - std::vector& intersections, - const CoordinateSystem3D& plane) const; - - /** - This method assumes polygon has a normal equal to 0,0,-1 and 0,0,1 (thus, - the polygon is parallel to the XY plane) and returns the Z coordinate of - all the polygon points - */ - double GetZ() const; - - /** - The normal sign is left undefined for now - */ - Vector GetNormal() const - { - return normal_; - } - - /** - This method will compute the intersection between a polygon and - a plane where either X, Y or Z is constant. - The plane is given with an origin and a normal. If the normal is - not parallel to an axis, an error is raised. - */ - void ComputeIntersectionWithPlane(const CoordinateSystem3D& plane); - - private: - static Type TypeFromString(const std::string& s) - { - if (s == "CLOSED_PLANAR") - return ClosedPlanar; - else - return Unsupported; - } - enum State - { - Building, - Valid - }; - std::string referencedSopInstanceUid_; - CoordinateSystem3D geometry_; - std::vector points_; - Vector normal_; // sign is irrelevant for now - State state_; - double minX_, maxX_, minY_, maxY_, minZ_, maxZ_; - Type type_; - }; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructureSet.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp Tue Feb 01 07:19:15 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -57,7 +57,6 @@ # include # include #else -# include "DicomStructureSetUtils.h" // TODO REMOVE # include "UnionOfRectangles.h" #endif @@ -117,38 +116,8 @@ 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; - } +#endif - bool CompareRectanglesForProjection(const std::pair& r1, - const std::pair& 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 { @@ -921,7 +890,7 @@ } } -#elif 1 +#else std::list rectangles; @@ -948,91 +917,8 @@ chains.push_back(*it); } -#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 > projected; - - 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, GetEstimatedNormal(), GetEstimatedSliceThickness())) - { - 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(x1), - static_cast(y1), - static_cast(x2), - static_cast(y2)),curZ)); - } - } - - // 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 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); - - std::vector< std::pair > segments; - ConvertListOfSlabsToSegments(segments, rectanglesForEachSlab, projected.size()); - - chains.resize(segments.size()); - for (size_t i = 0; i < segments.size(); i++) - { - chains[i].resize(2); - chains[i][0] = segments[i].first; - chains[i][1] = segments[i].second; - } -#endif - return true; } else diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructureSet2.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructureSet2.cpp Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,313 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructureSet2.h" - -#include "../Toolbox/LinearAlgebra.h" -#include "../StoneException.h" - -#include -#include -#include -#include - -#include "DicomStructure2.h" -#include "GenericToolbox.h" -#include "OrthancDatasets/DicomDatasetReader.h" - -namespace OrthancStone -{ - static const Orthanc::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); - static const Orthanc::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); - static const Orthanc::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); - static const Orthanc::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); - static const Orthanc::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); - static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); - static const Orthanc::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); - static const Orthanc::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a); - static const Orthanc::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026); - static const Orthanc::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4); - static const Orthanc::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080); - static const Orthanc::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020); - - static inline uint8_t ConvertAndClipToByte(double v) - { - if (v < 0) - { - return 0; - } - else if (v >= 255) - { - return 255; - } - else - { - return static_cast(v); - } - } - - static bool ReadDicomToVector(Vector& target, - const IDicomDataset& dataset, - const Orthanc::DicomPath& tag) - { - std::string value; - return (dataset.GetStringValue(value, tag) && - GenericToolbox::FastParseVector(target, value)); - } - - - void DicomPathToString(std::string& s, const Orthanc::DicomPath& dicomPath) - { - std::stringstream tmp; - for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i) - { - Orthanc::DicomTag tag = dicomPath.GetPrefixTag(i); - - // We use this other object to be able to use Format - Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement()); - size_t index = dicomPath.GetPrefixIndex(i); - tmp << " (" << tag2.Format() << ") [" << index << "] / "; - } - const Orthanc::DicomTag& tag = dicomPath.GetFinalTag(); - Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement()); - tmp << " (" << tag2.Format() << ")"; - s = tmp.str(); - } - - std::ostream& operator<<(std::ostream& s, const Orthanc::DicomPath& dicomPath) - { - std::string tmp; - DicomPathToString(tmp, dicomPath); - s << tmp; - return s; - } - - - DicomStructureSet2::DicomStructureSet2() - { - - } - - - DicomStructureSet2::~DicomStructureSet2() - { - - } - - void DicomStructureSet2::SetContents(const FullOrthancDataset& tags) - { - FillStructuresFromDataset(tags); - ComputeDependentProperties(); - } - - void DicomStructureSet2::ComputeDependentProperties() - { - for (size_t i = 0; i < structures_.size(); ++i) - { - structures_[i].ComputeDependentProperties(); - } - } - - void DicomStructureSet2::FillStructuresFromDataset(const FullOrthancDataset& tags) - { - DicomDatasetReader reader(tags); - - // a few sanity checks - size_t count = 0, tmp = 0; - - // DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080); - // DICOM_TAG_ROI_CONTOUR_SEQUENCE (0x3006, 0x0039); - // DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE (0x3006, 0x0020); - if (!tags.GetSequenceSize(count, Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE)) || - !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE)) || - tmp != count || - !tags.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE)) || - tmp != count) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - // let's now parse the structures stored in the dicom file - // DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080) - // DICOM_TAG_RT_ROI_INTERPRETED_TYPE (0x3006, 0x00a4) - // DICOM_TAG_ROI_DISPLAY_COLOR (0x3006, 0x002a) - // DICOM_TAG_ROI_NAME (0x3006, 0x0026) - structures_.resize(count); - for (size_t i = 0; i < count; i++) - { - // (0x3006, 0x0080)[i]/(0x3006, 0x00a4) - structures_[i].interpretation_ = reader.GetStringValue - (Orthanc::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, - DICOM_TAG_RT_ROI_INTERPRETED_TYPE), - "No interpretation"); - - // (0x3006, 0x0020)[i]/(0x3006, 0x0026) - structures_[i].name_ = reader.GetStringValue - (Orthanc::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, - DICOM_TAG_ROI_NAME), - "No name"); - - Vector color; - // (0x3006, 0x0039)[i]/(0x3006, 0x002a) - if (ReadDicomToVector(color, tags, Orthanc::DicomPath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) - && color.size() == 3) - { - structures_[i].red_ = ConvertAndClipToByte(color[0]); - structures_[i].green_ = ConvertAndClipToByte(color[1]); - structures_[i].blue_ = ConvertAndClipToByte(color[2]); - } - else - { - structures_[i].red_ = 255; - structures_[i].green_ = 0; - structures_[i].blue_ = 0; - } - - size_t countSlices; - // DICOM_TAG_ROI_CONTOUR_SEQUENCE (0x3006, 0x0039); - // DICOM_TAG_CONTOUR_SEQUENCE (0x3006, 0x0040); - if (!tags.GetSequenceSize(countSlices, Orthanc::DicomPath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE))) - { - LOG(WARNING) << "DicomStructureSet2::SetContents | structure \"" << structures_[i].name_ << "\" has no slices!"; - countSlices = 0; - } - - LOG(INFO) << "New RT structure: \"" << structures_[i].name_ - << "\" with interpretation \"" << structures_[i].interpretation_ - << "\" containing " << countSlices << " slices (color: " - << static_cast(structures_[i].red_) << "," - << static_cast(structures_[i].green_) << "," - << static_cast(structures_[i].blue_) << ")"; - - // These temporary variables avoid allocating many vectors in the loop below - - // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046) - Orthanc::DicomPath countPointsPath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, 0, - DICOM_TAG_NUMBER_OF_CONTOUR_POINTS); - - Orthanc::DicomPath geometricTypePath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, 0, - DICOM_TAG_CONTOUR_GEOMETRIC_TYPE); - - Orthanc::DicomPath imageSequencePath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, 0, - DICOM_TAG_CONTOUR_IMAGE_SEQUENCE); - - // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155) - Orthanc::DicomPath referencedInstancePath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, 0, - DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, - DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); - - Orthanc::DicomPath contourDataPath( - DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, 0, - DICOM_TAG_CONTOUR_DATA); - - for (size_t j = 0; j < countSlices; j++) - { - unsigned int countPoints = 0; - - countPointsPath.SetPrefixIndex(1, j); - if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath)) - { - std::string s; - DicomPathToString(s, countPointsPath); - LOG(ERROR) << "Dicom path " << s << " is not valid (should contain an unsigned integer)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; - - geometricTypePath.SetPrefixIndex(1, j); - std::string type = reader.GetMandatoryStringValue(geometricTypePath); - if (type != "CLOSED_PLANAR") - { - // TODO: support points!! - LOG(WARNING) << "Ignoring contour with geometry type: " << type; - continue; - } - - size_t size = 0; - - imageSequencePath.SetPrefixIndex(1, j); - if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1) - { - LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry."; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - referencedInstancePath.SetPrefixIndex(1, j); - std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath); - - contourDataPath.SetPrefixIndex(1, j); - std::string slicesData = reader.GetMandatoryStringValue(contourDataPath); - - Vector points; - if (!GenericToolbox::FastParseVector(points, slicesData) || - points.size() != 3 * countPoints) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - // seen in real world - if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "") - { - LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)"; - } - - DicomStructurePolygon2 polygon(sopInstanceUid,type); - polygon.Reserve(countPoints); - - for (size_t k = 0; k < countPoints; k++) - { - Vector v(3); - v[0] = points[3 * k]; - v[1] = points[3 * k + 1]; - v[2] = points[3 * k + 2]; - polygon.AddPoint(v); - } - structures_[i].AddPolygon(polygon); - } - } - } - - - void DicomStructureSet2::Clear() - { - structures_.clear(); - } - -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructureSet2.h --- a/OrthancStone/Sources/Toolbox/DicomStructureSet2.h Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#pragma once - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "../Scene2D/Color.h" -#include "CoordinateSystem3D.h" -#include "DicomStructure2.h" -#include "Extent2D.h" -#include "OrthancDatasets/FullOrthancDataset.h" - -#include - -namespace OrthancStone -{ - class DicomStructureSet2 : public boost::noncopyable - { - public: - DicomStructureSet2(); - ~DicomStructureSet2(); - - void SetContents(const FullOrthancDataset& tags); - - size_t GetStructuresCount() const - { - return structures_.size(); - } - - void Clear(); - - const DicomStructure2& GetStructure(size_t i) const - { - // at() is like []() but with range check - return structures_.at(i); - } - - /** Internal use only */ - void FillStructuresFromDataset(const FullOrthancDataset& tags); - - /** Internal use only */ - void ComputeDependentProperties(); - - /** Internal use only */ - std::vector structures_; - }; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructureSetUtils.cpp --- a/OrthancStone/Sources/Toolbox/DicomStructureSetUtils.cpp Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#include "DicomStructureSetUtils.h" - -namespace OrthancStone -{ - -#if 0 - void DicomStructure2::PartitionRectangleList(std::vector< std::vector > & sets, const std::vector slabCuts) - { - // map position ( )--> disjoint set index - std::map, size_t> posToIndex; - - // disjoint set index --> position - std::map > indexToPos; - - size_t nextIndex = 0; - for (size_t i = 0; i < slabCuts.size(); ++i) - { - for (size_t j = 0; j < slabCuts[i].size(); ++j) - { - std::pair pos(i, j); - posToIndex = nextIndex; - indexToPos = pos; - } - } - // nextIndex is now the total rectangle count - DisjointDataSet ds(nextIndex); - - // we loop on all slabs (except the last one) and we connect all rectangles - if (slabCuts.size() < 2) - { -#error write special case - } - else - { - for (size_t i = 0; i < slabCuts.size() - 1; ++i) - { - for (size_t j = 0; j < slabCuts[i].size(); ++j) - { - const RtStructRectangleInSlab& r1 = slabCuts[i][j]; - const size_t r1i = posToIndex(std::pair(i, j)); - for (size_t k = 0; k < slabCuts[i + 1].size(); ++k) - { - const RtStructRectangleInSlab& r2 = slabCuts[i + 1][k]; - const size_t r2i = posToIndex(std::pair(i, j)); - // rect.xmin <= rectBottom.xmax && rectBottom.xmin <= rect.xmax - if ((r1.xmin <= r2.xmax) && (r2.xmin <= r1.xmax)) - { -#error now go! - } - - } - } - } - } -#endif - - /* - - compute list of segments : - - numberOfRectsFromHereOn = 0 - possibleNext = {in_k,in_kplus1} - - for all boundaries: - - we create a vertical segment and we push it - - if boundary is a start, numberOfRectsFromHereOn += 1. - - if we switch from 0 to 1, we start a segment - - if we switch from 1 to 2, we end the current segment and we record it - - if boundary is an end, numberOfRectsFromHereOn -= 1. - - if we switch from 1 to 0, we end the current segment and we record it - - if we switch from 2 to 1, we start a segment - */ - - // static - void AddSlabBoundaries( - std::vector > & boundaries, - const std::vector & slabCuts, size_t iSlab) - { - if (iSlab < slabCuts.size()) - { - const RtStructRectanglesInSlab& slab = slabCuts[iSlab]; - for (size_t iRect = 0; iRect < slab.size(); ++iRect) - { - const RtStructRectangleInSlab& rect = slab[iRect]; - { - std::pair boundary(rect.xmin, RectangleBoundaryKind_Start); - boundaries.insert(std::lower_bound(boundaries.begin(), boundaries.end(), boundary), boundary); - } - { - std::pair boundary(rect.xmax, RectangleBoundaryKind_End); - boundaries.insert(std::lower_bound(boundaries.begin(), boundaries.end(), boundary), boundary); - } - } - } - } - - // static - void ProcessBoundaryList( - std::vector< std::pair > & segments, - const std::vector > & boundaries, - double y) - { - ScenePoint2D start; - ScenePoint2D end; - int curNumberOfSegments = 0; // we count the number of segments. we only draw if it is 1 (not 0 or 2) - for (size_t i = 0; i < boundaries.size(); ++i) - { - switch (boundaries[i].second) - { - case RectangleBoundaryKind_Start: - curNumberOfSegments += 1; - switch (curNumberOfSegments) - { - case 0: - assert(false); - break; - case 1: - // a new segment has begun! - start = ScenePoint2D(boundaries[i].first, y); - break; - case 2: - // an extra segment has begun : stop the current one (we don't draw overlaps) - end = ScenePoint2D(boundaries[i].first, y); - segments.push_back(std::pair(start, end)); - break; - default: - //assert(false); // seen IRL ! - break; - } - break; - case RectangleBoundaryKind_End: - curNumberOfSegments -= 1; - switch (curNumberOfSegments) - { - case 0: - // a lone (thus active) segment has ended. - end = ScenePoint2D(boundaries[i].first, y); - segments.push_back(std::pair(start, end)); - break; - case 1: - // an extra segment has ended : start a new one one - start = ScenePoint2D(boundaries[i].first, y); - break; - default: - // this should not happen! - //assert(false); - break; - } - break; - default: - assert(false); - break; - } - } - } - -#if 0 - void ConvertListOfSlabsToSegments( - std::vector< std::pair >& segments, - const std::vector& slabCuts, - const size_t totalRectCount) - { -#error to delete - } -#else - // See https://www.dropbox.com/s/bllco6q8aazxk44/2019-09-18-rtstruct-cut-algorithm-rect-merge.png - void ConvertListOfSlabsToSegments( - std::vector< std::pair > & segments, - const std::vector & slabCuts, - const size_t totalRectCount) - { - if (slabCuts.size() == 0) - return; - - if (totalRectCount > 0) - segments.reserve(4 * totalRectCount); // worst case, but common. - - /* - VERTICAL - */ - for (size_t iSlab = 0; iSlab < slabCuts.size(); ++iSlab) - { - for (size_t iRect = 0; iRect < slabCuts[iSlab].size(); ++iRect) - { - const RtStructRectangleInSlab& rect = slabCuts[iSlab][iRect]; - { - ScenePoint2D p1(rect.xmin, rect.ymin); - ScenePoint2D p2(rect.xmin, rect.ymax); - segments.push_back(std::pair(p1, p2)); - } - { - ScenePoint2D p1(rect.xmax, rect.ymin); - ScenePoint2D p2(rect.xmax, rect.ymax); - segments.push_back(std::pair(p1, p2)); - } - } - } - - /* - HORIZONTAL - */ - - // if we have N slabs, we have N+1 potential vertical positions for horizontal segments - // - one for top of slab 0 - // - N-1 for all positions between two slabs - // - one for bottom of slab N-1 - - // this adds all the horizontal segments for the tops of 3the rectangles - // in row 0 - if (slabCuts[0].size() > 0) - { - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, 0); - - ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin); - } - - // this adds all the horizontal segments belonging to two slabs - for (size_t iSlab = 0; iSlab < slabCuts.size() - 1; ++iSlab) - { - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, iSlab); - AddSlabBoundaries(boundaries, slabCuts, iSlab + 1); - double curY = 0; - if (slabCuts[iSlab].size() > 0) - { - curY = slabCuts[iSlab][0].ymax; - ProcessBoundaryList(segments, boundaries, curY); - } - else if (slabCuts[iSlab + 1].size() > 0) - { - curY = slabCuts[iSlab + 1][0].ymin; - ProcessBoundaryList(segments, boundaries, curY); - } - else - { - // nothing to do!! : both slab lists are empty! - } - } - - // this adds all the horizontal segments for the BOTTOM of the rectangles - // on last row - if (slabCuts[slabCuts.size() - 1].size() > 0) - { - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, slabCuts.size() - 1); - - ProcessBoundaryList(segments, boundaries, slabCuts[slabCuts.size() - 1][0].ymax); - } - } -#endif - } diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DicomStructureSetUtils.h --- a/OrthancStone/Sources/Toolbox/DicomStructureSetUtils.h Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#pragma once - -#include -#include - -#include "../Scene2D/ScenePoint2D.h" -#include "../Toolbox/LinearAlgebra.h" - -namespace OrthancStone -{ - /** Internal */ - struct RtStructRectangleInSlab - { - double xmin, xmax, ymin, ymax; - }; - typedef std::vector RtStructRectanglesInSlab; - - enum RectangleBoundaryKind - { - RectangleBoundaryKind_Start, - RectangleBoundaryKind_End - }; - -#if 0 - /** Internal */ - void PartitionRectangleList(std::vector< std::vector > & sets, const std::vector); -#endif - - /** Internal */ - void ConvertListOfSlabsToSegments(std::vector< std::pair >& segments, - const std::vector& slabCuts, - const size_t totalRectCount); - - /** Internal */ - void AddSlabBoundaries(std::vector >& boundaries, - const std::vector& slabCuts, - size_t iSlab); - - /** Internal */ - void ProcessBoundaryList(std::vector< std::pair >& segments, - const std::vector >& boundaries, - double y); - -} diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Toolbox/DisjointDataSet.h --- a/OrthancStone/Sources/Toolbox/DisjointDataSet.h Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,146 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#pragma once - -#include - -#include "../StoneException.h" - -namespace OrthancStone -{ - class DisjointDataSet - { - public: - DisjointDataSet(size_t itemCount) : - parents_(itemCount), - ranks_(itemCount) - { - for (size_t index = 0; index < parents_.size(); index++) - { - SetParent(index,index); - ranks_[index] = 1; - } - } - - size_t Find(size_t item) - { - /* - If parents_[i] == i, it means i is representative of a set. - Otherwise, we go up the tree... - */ - if (GetParent(item) != item) - { - // if item is not a top item (representative of its set), - // we use path compression to improve future lookups - // see: https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Path_compression - SetParent(item, Find(parents_[item])); - } - - // now that paths have been compressed, we are positively certain - // that item's parent is a set ("X is a set" means that X is the - // representative of a set) - return GetParent(item); - } - - /* - This merge the two sets that contains itemA and itemB - */ - void Union(size_t itemA, size_t itemB) - { - // Find current sets of x and y - size_t setA = Find(itemA); - size_t setB = Find(itemB); - - // if setA == setB, it means they are already in the same set and - // do not need to be merged! - if (setA != setB) - { - // we need to merge the sets, which means that the trees representing - // the sets needs to be merged (there must be a single top parent to - // all the items originally belonging to setA and setB must be the same) - - // since the algorithm speed is inversely proportional to the tree - // height (the rank), we need to combine trees in a way that - // minimizes this rank. See "Union by rank" at - // https://en.wikipedia.org/wiki/Disjoint-set_data_structure#by_rank - if (GetRank(setA) < GetRank(setB)) - { - SetParent(setA, setB); - } - else if (GetRank(setA) > GetRank(setB)) - { - SetParent(setB, setA); - } - else - { - SetParent(setB, setA); - BumpRank(setA); - // the trees had the same height but we attached the whole of setB - // under setA (under its parent), so the resulting tree is now - // 1 higher. setB is NOT representative of a set anymore. - } - } - } - - private: - size_t GetRank(size_t i) const - { - ORTHANC_ASSERT(i < ranks_.size()); - ORTHANC_ASSERT(ranks_.size() == parents_.size()); - return ranks_[i]; - } - - size_t GetParent(size_t i) const - { - ORTHANC_ASSERT(i < parents_.size()); - ORTHANC_ASSERT(ranks_.size() == parents_.size()); - return parents_[i]; - } - - void SetParent(size_t i, size_t parent) - { - ORTHANC_ASSERT(i < parents_.size()); - ORTHANC_ASSERT(ranks_.size() == parents_.size()); - parents_[i] = parent; - } - - void BumpRank(size_t i) - { - ORTHANC_ASSERT(i < ranks_.size()); - ORTHANC_ASSERT(ranks_.size() == parents_.size()); - ranks_[i] = ranks_[i] + 1u; - } - - /* - This vector contains the direct parent of each item - */ - std::vector parents_; - - /* - This vector contains the tree height of each set. The values in the - vector for non-representative items is UNDEFINED! - */ - std::vector ranks_; - }; - -} diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.cpp --- a/OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.cpp Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructureSetSlicer2.h" - -#include "../Toolbox/GeometryToolbox.h" -#include "../Volumes/IVolumeSlicer.h" -#include "../Scene2D/PolylineSceneLayer.h" - -namespace OrthancStone -{ - DicomStructureSetSlicer2::DicomStructureSetSlicer2(boost::shared_ptr structureSet) - : structureSet_(structureSet) - {} - - IVolumeSlicer::IExtractedSlice* DicomStructureSetSlicer2::ExtractSlice(const CoordinateSystem3D& cuttingPlane) - { - // revision is always the same, hence 0 - return new DicomStructureSetSlice2(structureSet_, 0, cuttingPlane); - } - - DicomStructureSetSlice2::DicomStructureSetSlice2( - boost::weak_ptr structureSet, - uint64_t revision, - const CoordinateSystem3D& cuttingPlane) - : structureSet_(structureSet.lock()) - , isValid_(false) - { - bool opposite = false; - - if (structureSet_->GetStructuresCount() == 0) - { - isValid_ = false; - } - else - { - // some structures seen in real life have no polygons. We must be - // careful - bool found = false; - size_t curStructure = 0; - while (!found && curStructure < structureSet_->GetStructuresCount()) - { - if (structureSet_->GetStructure(curStructure).IsValid()) - { - found = true; - const Vector normal = structureSet_->GetStructure(0).GetNormal(); - isValid_ = ( - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); - } - } - } - } - - ISceneLayer* DicomStructureSetSlice2::CreateSceneLayer( - const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) - { - assert(isValid_); - - std::unique_ptr layer(new PolylineSceneLayer); - layer->SetThickness(2); // thickness of the on-screen line - - for (size_t i = 0; i < structureSet_->GetStructuresCount(); i++) - { - const DicomStructure2& structure = structureSet_->GetStructure(i); - if (structure.IsValid()) - { - const Color& color = structure.GetColor(); - - std::vector< std::pair > segments; - - if (structure.Project(segments, cuttingPlane)) - { - for (size_t j = 0; j < segments.size(); j++) - { - PolylineSceneLayer::Chain chain; - chain.resize(2); - - chain[0] = ScenePoint2D(segments[j].first.GetX(), segments[j].first.GetY()); - chain[1] = ScenePoint2D(segments[j].second.GetX(), segments[j].second.GetY()); - - layer->AddChain(chain, false /* NOT closed */, color); - } - } - } - } - return layer.release(); - } -} - - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - diff -r 0208f99b8bde -r affde38b84de OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.h --- a/OrthancStone/Sources/Volumes/DicomStructureSetSlicer2.h Tue Feb 01 07:19:15 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program. If not, see - * . - **/ - - -#pragma once - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "../Toolbox/DicomStructureSet2.h" -#include "../Volumes/IVolumeSlicer.h" - -#include -#include - -namespace OrthancStone -{ - class DicomStructureSetSlice2 : public IVolumeSlicer::IExtractedSlice - { - public: - DicomStructureSetSlice2( - boost::weak_ptr structureSet, - uint64_t revision, - const CoordinateSystem3D& cuttingPlane); - - virtual bool IsValid() ORTHANC_OVERRIDE - { - return isValid_; - } - - virtual uint64_t GetRevision() ORTHANC_OVERRIDE - { - return revision_; - } - - virtual ISceneLayer* CreateSceneLayer( - const ILayerStyleConfigurator* configurator, // possibly absent - const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; - - private: - boost::shared_ptr structureSet_; - bool isValid_; - uint64_t revision_; - }; - - class DicomStructureSetSlicer2 : public IVolumeSlicer - { - public: - DicomStructureSetSlicer2(boost::shared_ptr structureSet); - - /** IVolumeSlicer impl */ - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; - private: - boost::weak_ptr structureSet_; - }; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - diff -r 0208f99b8bde -r affde38b84de OrthancStone/UnitTestsSources/TestStructureSet.cpp --- a/OrthancStone/UnitTestsSources/TestStructureSet.cpp Tue Feb 01 07:19:15 2022 +0100 +++ b/OrthancStone/UnitTestsSources/TestStructureSet.cpp Tue Feb 01 08:38:32 2022 +0100 @@ -21,1169 +21,20 @@ **/ -// working around a bug where the Visual C++ compiler would get -// stuck trying to compile this cpp file in release mode -// (versions: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B) -#ifdef _MSC_VER -# pragma optimize("", off) -// warning C4748: /GS can not protect parameters and local variables from -// local buffer overrun because optimizations are disabled in function -# pragma warning(disable: 4748) -#endif - #include "../Sources/Toolbox/DicomStructureSet.h" -#include "../Sources/Toolbox/DicomStructureSet2.h" -#include "../Sources/Toolbox/DicomStructureSetUtils.h" -#include "../Sources/Toolbox/DisjointDataSet.h" #include -#include -#include - #include -#include - -using namespace OrthancStone; - -static const double DELTA_MAX = 10.0 * std::numeric_limits::epsilon(); - - - -#define STONE_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -static void CheckGroundTruth( - const std::vector& structures, - const size_t structureIndex, - const size_t sliceIndex, - std::vector groundTruth) -{ - const std::vector& polygonsForThisStruct = structures.at(structureIndex).GetPolygons(); - const DicomStructurePolygon2& polygon = polygonsForThisStruct.at(sliceIndex); - - //double groundTruth[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 }; - size_t groundTruthItems = groundTruth.size(); - - size_t pointCount = 3 * polygon.GetPointCount(); - - EXPECT_EQ(groundTruthItems, pointCount); - - for (size_t i = 0; i < polygon.GetPointCount(); ++i) - { - const Vector& point = polygon.GetPoint(i); - - // loop over X, Y then Z. - for (size_t j = 0; j < 3; ++j) - { - size_t index = 3 * i + j; - ASSERT_LT(index, groundTruthItems); - bool isNear = LinearAlgebra::IsNear(groundTruth[index], point[j]); - EXPECT_TRUE(isNear); - } - } -} - - -#include - -TEST(StructureSet2, ReadFromJsonThatsAll) -{ - /* - The "RT_STRUCT_00" string is the reply to the following Orthanc request: - - http://localhost:8042/instances/1aa5f84b-c32a03b4-3c1857da-da2e69f3-3ef6e2b3/tags?ignore-length=3006-0050 - - The tag hierarchy can be found here: https://dicom.innolitics.com/ciods/rt-dose - */ - - DicomStructureSet2 structureSet; - - FullOrthancDataset dicom(Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00), - Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00)); - structureSet.Clear(); - - structureSet.FillStructuresFromDataset(dicom); - structureSet.ComputeDependentProperties(); - - const std::vector& structures = structureSet.structures_; - - /* - - ██████╗ █████╗ ███████╗██╗ ██████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗███████╗ - ██╔══██╗██╔══██╗██╔════╝██║██╔════╝ ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝ - ██████╔╝███████║███████╗██║██║ ██║ ███████║█████╗ ██║ █████╔╝ ███████╗ - ██╔══██╗██╔══██║╚════██║██║██║ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ╚════██║ - ██████╔╝██║ ██║███████║██║╚██████╗ ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗███████║ - ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ - http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS - */ - - // (0x3006, 0x0080) seq. size - EXPECT_EQ(7u, structures.size()); - - // (0x3006, 0x0080)[i]/(0x3006, 0x00a4) - for (size_t i = 0; i < 5; ++i) - { - EXPECT_EQ(std::string("ORGAN"), structures[i].interpretation_); - } - EXPECT_EQ(std::string("EXTERNAL"), structures[5].interpretation_); - EXPECT_EQ(std::string("PTV"), structures[6].interpretation_); - - // (0x3006, 0x0020)[i]/(0x3006, 0x0026) - EXPECT_EQ(std::string("LN300"), structures[0].name_); - EXPECT_EQ(std::string("Cortical Bone"), structures[1].name_); - EXPECT_EQ(std::string("Adipose"), structures[2].name_); - EXPECT_EQ(std::string("CB2-50%"), structures[3].name_); - EXPECT_EQ(std::string("Water"), structures[4].name_); - EXPECT_EQ(std::string("External"), structures[5].name_); - EXPECT_EQ(std::string("PTV"), structures[6].name_); - - // (0x3006, 0x0039)[i]/(0x3006, 0x002a) - EXPECT_EQ(0xff, structures[0].red_); - EXPECT_EQ(0x00, structures[0].green_); - EXPECT_EQ(0x00, structures[0].blue_); - - EXPECT_EQ(0x00, structures[1].red_); - EXPECT_EQ(0xff, structures[1].green_); - EXPECT_EQ(0xff, structures[1].blue_); - - // ... - - EXPECT_EQ(0x00, structures[5].red_); - EXPECT_EQ(0x80, structures[5].green_); - EXPECT_EQ(0x00, structures[5].blue_); - - EXPECT_EQ(0xff, structures[6].red_); - EXPECT_EQ(0x00, structures[6].green_); - EXPECT_EQ(0xff, structures[6].blue_); - - /* - - ██████╗ ███████╗ ██████╗ ███╗ ███╗███████╗████████╗██████╗ ██╗ ██╗ - ██╔════╝ ██╔════╝██╔═══██╗████╗ ████║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝ - ██║ ███╗█████╗ ██║ ██║██╔████╔██║█████╗ ██║ ██████╔╝ ╚████╔╝ - ██║ ██║██╔══╝ ██║ ██║██║╚██╔╝██║██╔══╝ ██║ ██╔══██╗ ╚██╔╝ - ╚██████╔╝███████╗╚██████╔╝██║ ╚═╝ ██║███████╗ ██║ ██║ ██║ ██║ - ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ - http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=BASIC%20CHECKS - */ - - - { - double groundTruthRaw[] = { 7.657838, 108.2725, 304.01, 6.826687, 107.4413, 304.01, 6.152492, 106.4785, 304.01, 5.655735, 105.4132, 304.01, 5.351513, 104.2778, 304.01, 5.249068, 103.1069, 304.01, 5.351513, 101.9359, 304.01, 5.655735, 100.8005, 304.01, 6.152492, 99.73524, 304.01, 6.826687, 98.77239, 304.01, 7.657838, 97.94124, 304.01, 8.620689, 97.26704, 304.01, 9.685987, 96.77029, 304.01, 10.82136, 96.46606, 304.01, 11.99231, 96.36362, 304.01, 13.16326, 96.46606, 304.01, 14.29864, 96.77029, 304.01, 15.36393, 97.26704, 304.01, 16.32678, 97.94124, 304.01, 17.15794, 98.77239, 304.01, 17.83213, 99.73524, 304.01, 18.32889, 100.8005, 304.01, 18.63311, 101.9359, 304.01, 18.73555, 103.1069, 304.01, 18.63311, 104.2778, 304.01, 18.32889, 105.4132, 304.01, 17.83213, 106.4785, 304.01, 17.15794, 107.4413, 304.01, 16.32678, 108.2725, 304.01, 15.36393, 108.9467, 304.01, 14.29864, 109.4434, 304.01, 13.16326, 109.7477, 304.01, 11.99231, 109.8501, 304.01, 10.82136, 109.7477, 304.01, 9.685987, 109.4434, 304.01, 8.620689, 108.9467, 304.01 }; - size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); - std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); - CheckGroundTruth(structures, 0, 0, groundTruth); - } - { - double groundTruthRaw[] = { 7.657838, 108.2725, 310.01, 6.826687, 107.4413, 310.01, 6.152492, 106.4785, 310.01, 5.655735, 105.4132, 310.01, 5.351513, 104.2778, 310.01, 5.249068, 103.1069, 310.01, 5.351513, 101.9359, 310.01, 5.655735, 100.8005, 310.01, 6.152492, 99.73524, 310.01, 6.826687, 98.77239, 310.01, 7.657838, 97.94124, 310.01, 8.620689, 97.26704, 310.01, 9.685987, 96.77029, 310.01, 10.82136, 96.46606, 310.01, 11.99231, 96.36362, 310.01, 13.16326, 96.46606, 310.01, 14.29864, 96.77029, 310.01, 15.36393, 97.26704, 310.01, 16.32678, 97.94124, 310.01, 17.15794, 98.77239, 310.01, 17.83213, 99.73524, 310.01, 18.32889, 100.8005, 310.01, 18.63311, 101.9359, 310.01, 18.73555, 103.1069, 310.01, 18.63311, 104.2778, 310.01, 18.32889, 105.4132, 310.01, 17.83213, 106.4785, 310.01, 17.15794, 107.4413, 310.01, 16.32678, 108.2725, 310.01, 15.36393, 108.9467, 310.01, 14.29864, 109.4434, 310.01, 13.16326, 109.7477, 310.01, 11.99231, 109.8501, 310.01, 10.82136, 109.7477, 310.01, 9.685987, 109.4434, 310.01, 8.620689, 108.9467, 310.01 }; - size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); - std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); - CheckGroundTruth(structures, 0, 2, groundTruth); - } - { - double groundTruthRaw[] = { -37.967, 161.9664, 304.01, -39.10237, 161.6622, 304.01, -40.16767, 161.1655, 304.01, -41.13052, 160.4913, 304.01, -41.96167, 159.6601, 304.01, -42.63587, 158.6973, 304.01, -43.13263, 157.632, 304.01, -43.43685, 156.4966, 304.01, -43.53929, 155.3257, 304.01, -43.43685, 154.1547, 304.01, -43.13263, 153.0193, 304.01, -42.63587, 151.954, 304.01, -41.96167, 150.9912, 304.01, -41.13052, 150.16, 304.01, -40.16767, 149.4858, 304.01, -39.10237, 148.9891, 304.01, -37.967, 148.6849, 304.01, -36.79605, 148.5824, 304.01, -35.6251, 148.6849, 304.01, -34.48972, 148.9891, 304.01, -33.42443, 149.4858, 304.01, -32.46157, 150.16, 304.01, -31.63042, 150.9912, 304.01, -30.95623, 151.954, 304.01, -30.45947, 153.0193, 304.01, -30.15525, 154.1547, 304.01, -30.0528, 155.3257, 304.01, -30.15525, 156.4966, 304.01, -30.45947, 157.632, 304.01, -30.95623, 158.6973, 304.01, -31.63042, 159.6601, 304.01, -32.46157, 160.4913, 304.01, -33.42443, 161.1655, 304.01, -34.48972, 161.6622, 304.01, -35.6251, 161.9664, 304.01, -36.79605, 162.0689, 304.01 }; - size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); - std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); - CheckGroundTruth(structures, 1, 0, groundTruth); - } - { - double groundTruthRaw[] = { 69.4042, 150.7324, 307.01, 69.70842, 151.8678, 307.01, 69.81087, 153.0387, 307.01, 69.70842, 154.2097, 307.01, 69.4042, 155.345, 307.01, 68.90745, 156.4103, 307.01, 68.23325, 157.3732, 307.01, 67.4021, 158.2043, 307.01, 66.43925, 158.8785, 307.01, 65.37395, 159.3753, 307.01, 64.23858, 159.6795, 307.01, 63.06762, 159.7819, 307.01, 61.89667, 159.6795, 307.01, 60.7613, 159.3753, 307.01, 59.696, 158.8785, 307.01, 58.73315, 158.2043, 307.01, 57.902, 157.3732, 307.01, 57.22781, 156.4103, 307.01, 56.73105, 155.345, 307.01, 56.42683, 154.2097, 307.01, 56.32438, 153.0387, 307.01, 56.42683, 151.8678, 307.01, 56.73105, 150.7324, 307.01, 57.22781, 149.6671, 307.01, 57.902, 148.7042, 307.01, 58.73315, 147.8731, 307.01, 59.696, 147.1989, 307.01, 60.7613, 146.7021, 307.01, 61.89667, 146.3979, 307.01, 63.06762, 146.2955, 307.01, 64.23858, 146.3979, 307.01, 65.37395, 146.7021, 307.01, 66.43925, 147.1989, 307.01, 67.4021, 147.8731, 307.01, 68.23325, 148.7042, 307.01, 68.90745, 149.6671, 307.01 }; - size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); - std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); - CheckGroundTruth(structures, 2, 1, groundTruth); - } - - { - double groundTruthRaw[] = { 108.3984, 232.7406, 274.01, 106.0547, 231.7948, 274.01, 103.7109, 232.8407, 274.01, 96.67969, 232.8757, 274.01, 77.92969, 232.887, 274.01, 47.46094, 232.8902, 274.01, 38.08594, 232.7537, 274.01, 37.6668, 232.3734, 274.01, 38.08594, 231.9774, 274.01, 40.42969, 231.8475, 274.01, 41.76413, 230.0297, 274.01, 42.77344, 229.1388, 274.01, 45.11719, 228.5069, 274.01, 47.46094, 227.1533, 274.01, 49.80469, 226.3505, 274.01, 52.14844, 224.6564, 274.01, 54.49219, 223.923, 274.01, 56.83594, 222.0692, 274.01, 59.17969, 220.3438, 274.01, 61.52344, 219.3888, 274.01, 63.86719, 217.1287, 274.01, 65.83488, 215.9672, 274.01, 68.55469, 213.2383, 274.01, 70.89844, 211.2328, 274.01, 72.8125, 208.9359, 274.01, 75.58594, 206.3615, 274.01, 76.91445, 204.2484, 274.01, 78.89509, 201.9047, 274.01, 80.51276, 199.5609, 274.01, 81.51955, 197.2172, 274.01, 83.67448, 194.8734, 274.01, 84.60938, 192.5297, 274.01, 85.86986, 190.1859, 274.01, 86.57623, 187.8422, 274.01, 88.30051, 185.4984, 274.01, 88.94002, 183.1547, 274.01, 89.23261, 180.8109, 274.01, 89.64844, 180.3263, 274.01, 90.71885, 178.4672, 274.01, 90.97656, 176.1234, 274.01, 91.99219, 174.4794, 274.01, 92.56773, 173.7797, 274.01, 92.80016, 171.4359, 274.01, 93.23473, 169.0922, 274.01, 93.37606, 166.7484, 274.01, 93.60748, 157.3734, 274.01, 93.6341, 152.6859, 274.01, 93.35742, 140.9672, 274.01, 92.89317, 138.6234, 274.01, 92.7069, 136.2797, 274.01, 92.03726, 133.9359, 274.01, 90.84009, 131.5922, 274.01, 90.3769, 129.2484, 274.01, 89.09074, 126.9047, 274.01, 88.13225, 122.2172, 274.01, 86.17828, 119.8734, 274.01, 84.96094, 117.4163, 274.01, 83.99619, 115.1859, 274.01, 83.13079, 112.8422, 274.01, 82.61719, 112.2984, 274.01, 80.27344, 108.8454, 274.01, 79.64514, 108.1547, 274.01, 77.21497, 105.8109, 274.01, 76.47787, 103.4672, 274.01, 75.58594, 102.6177, 274.01, 73.24219, 100.0077, 274.01, 69.54492, 96.43594, 274.01, 67.34096, 94.09219, 274.01, 64.66306, 91.74844, 274.01, 63.86719, 90.92619, 274.01, 61.52344, 90.20454, 274.01, 59.17969, 87.78574, 274.01, 56.83594, 86.48566, 274.01, 54.49219, 84.31388, 274.01, 52.14844, 83.44438, 274.01, 49.80469, 82.75121, 274.01, 49.37617, 82.37344, 274.01, 47.46094, 81.26244, 274.01, 45.71391, 80.02969, 274.01, 45.11719, 79.45415, 274.01, 42.77344, 79.08185, 274.01, 40.42969, 78.51941, 274.01, 38.08594, 78.27534, 274.01, 37.36932, 77.68594, 274.01, 35.74219, 76.67624, 274.01, 33.39844, 76.49941, 274.01, 31.05469, 76.03495, 274.01, 28.71094, 74.83174, 274.01, 26.36719, 74.62859, 274.01, 24.02344, 74.55463, 274.01, 21.67969, 74.22861, 274.01, 19.33594, 74.05312, 274.01, 12.30469, 73.99397, 274.01, 5.273438, 74.0736, 274.01, 2.929688, 74.55463, 274.01, 0.5859375, 74.68513, 274.01, -1.757813, 74.914, 274.01, -2.319131, 75.34219, 274.01, -4.101563, 76.31516, 274.01, -8.789063, 76.74514, 274.01, -11.13281, 78.39038, 274.01, -13.47656, 78.6124, 274.01, -15.82031, 79.19784, 274.01, -18.16406, 81.11024, 274.01, -20.50781, 82.03296, 274.01, -22.85156, 83.13991, 274.01, -25.19531, 83.70732, 274.01, -27.53906, 85.85863, 274.01, -29.88281, 87.03368, 274.01, -32.22656, 88.3274, 274.01, -34.57031, 90.53674, 274.01, -36.91406, 92.5602, 274.01, -39.25781, 93.55952, 274.01, -41.60156, 95.74537, 274.01, -43.94531, 98.26609, 274.01, -46.28906, 100.3701, 274.01, -47.02621, 101.1234, 274.01, -47.86611, 103.4672, 274.01, -49.83594, 105.8109, 274.01, -51.98182, 108.1547, 274.01, -53.06448, 110.4984, 274.01, -53.32031, 110.7675, 274.01, -54.53804, 112.8422, 274.01, -55.66406, 114.273, 274.01, -56.55722, 115.1859, 274.01, -57.13953, 117.5297, 274.01, -58.29264, 119.8734, 274.01, -59.26869, 122.2172, 274.01, -60.35156, 124.0119, 274.01, -60.84229, 124.5609, 274.01, -61.54484, 126.9047, 274.01, -61.71691, 129.2484, 274.01, -63.62281, 131.5922, 274.01, -63.81256, 133.9359, 274.01, -64.12511, 136.2797, 274.01, -64.84515, 138.6234, 274.01, -65.13599, 140.9672, 274.01, -65.33604, 143.3109, 274.01, -65.87358, 145.6547, 274.01, -66.10577, 147.9984, 274.01, -66.17618, 155.0297, 274.01, -66.09933, 162.0609, 274.01, -65.40382, 164.4047, 274.01, -65.24833, 166.7484, 274.01, -64.71442, 171.4359, 274.01, -63.88171, 173.7797, 274.01, -63.69299, 176.1234, 274.01, -61.79081, 178.4672, 274.01, -61.59269, 180.8109, 274.01, -61.19405, 183.1547, 274.01, -60.35156, 185.2055, 274.01, -59.08288, 187.8422, 274.01, -58.00781, 189.3499, 274.01, -57.25858, 190.1859, 274.01, -56.64558, 192.5297, 274.01, -55.29191, 194.8734, 274.01, -54.28698, 197.2172, 274.01, -52.28595, 199.5609, 274.01, -51.47569, 201.9047, 274.01, -48.63281, 204.6417, 274.01, -47.10181, 206.5922, 274.01, -44.64154, 208.9359, 274.01, -42.38504, 211.2797, 274.01, -39.25781, 214.4025, 274.01, -37.42723, 215.9672, 274.01, -34.57031, 218.9107, 274.01, -32.22656, 219.7277, 274.01, -29.88281, 221.6934, 274.01, -27.53906, 222.852, 274.01, -25.19531, 224.5168, 274.01, -22.85156, 225.9419, 274.01, -20.50781, 226.7359, 274.01, -18.16406, 228.3332, 274.01, -15.82031, 229.065, 274.01, -13.47656, 229.267, 274.01, -12.63854, 230.0297, 274.01, -11.13281, 231.9201, 274.01, -10.65505, 232.3734, 274.01, -11.13281, 232.7794, 274.01, -15.82031, 232.792, 274.01, -18.16406, 232.8902, 274.01, -36.91406, 232.9015, 274.01, -39.25781, 232.8902, 274.01, -50.97656, 232.9236, 274.01, -60.35156, 232.9126, 274.01, -67.38281, 232.8407, 274.01, -72.07031, 232.8642, 274.01, -79.10156, 232.8555, 274.01, -83.78906, 232.8788, 274.01, -95.50781, 232.8902, 274.01, -97.85156, 233.4886, 274.01, -100.1953, 233.647, 274.01, -102.5391, 232.9858, 274.01, -104.8828, 233.6969, 274.01, -109.5703, 233.722, 274.01, -125.9766, 233.7086, 274.01, -128.3203, 233.2849, 274.01, -130.6641, 233.702, 274.01, -135.3516, 233.727, 274.01, -149.4141, 233.7135, 274.01, -156.4453, 233.727, 274.01, -163.4766, 233.7119, 274.01, -168.1641, 233.7643, 274.01, -191.6016, 233.7809, 274.01, -210.3516, 233.7716, 274.01, -224.4141, 233.7998, 274.01, -233.7891, 233.7647, 274.01, -243.1641, 233.7785, 274.01, -247.8516, 233.7378, 274.01, -254.8828, 233.8578, 274.01, -257.2266, 235.2519, 274.01, -259.5703, 236.0817, 274.01, -260.7617, 237.0609, 274.01, -261.9141, 238.2262, 274.01, -262.8989, 239.4047, 274.01, -262.9743, 241.7484, 274.01, -262.5977, 244.0922, 274.01, -260.6675, 246.4359, 274.01, -259.6161, 248.7797, 274.01, -257.2266, 251.0035, 274.01, -255.0361, 253.4672, 274.01, -252.5391, 256.0995, 274.01, -251.2277, 258.1547, 274.01, -246.7444, 262.8422, 274.01, -243.1641, 266.3515, 274.01, -239.7411, 269.8734, 274.01, -238.4766, 270.9495, 274.01, -237.2269, 272.2172, 274.01, -236.1328, 273.5215, 274.01, -235.0934, 274.5609, 274.01, -233.7891, 275.6655, 274.01, -232.5319, 276.9047, 274.01, -231.4453, 278.1693, 274.01, -227.917, 281.5922, 274.01, -224.4141, 285.1802, 274.01, -222.0703, 287.4025, 274.01, -218.6841, 290.9672, 274.01, -217.3828, 291.9709, 274.01, -215.0391, 293.1788, 274.01, -212.6953, 294.5138, 274.01, -210.3516, 295.2614, 274.01, -209.8994, 295.6547, 274.01, -208.0078, 296.7083, 274.01, -203.3203, 296.9372, 274.01, -196.2891, 296.9317, 274.01, -193.9453, 296.8988, 274.01, -172.8516, 296.8482, 274.01, -161.1328, 296.843, 274.01, -137.6953, 296.8542, 274.01, -130.6641, 296.8378, 274.01, -107.2266, 296.8379, 274.01, -93.16406, 296.8208, 274.01, -74.41406, 296.838, 274.01, -65.03906, 296.8609, 274.01, -50.97656, 296.8556, 274.01, -46.28906, 296.9051, 274.01, -41.60156, 298.5331, 274.01, -39.25781, 298.5624, 274.01, -36.91406, 297.1455, 274.01, -34.57031, 297.0498, 274.01, -32.22656, 298.5589, 274.01, -25.19531, 298.5624, 274.01, -22.85156, 297.2842, 274.01, -20.50781, 298.5624, 274.01, -1.757813, 298.5624, 274.01, 0.5859375, 297.2104, 274.01, 2.929688, 298.5624, 274.01, 5.273438, 297.6946, 274.01, 7.617188, 298.5168, 274.01, 9.960938, 298.5512, 274.01, 12.30469, 296.937, 274.01, 14.64844, 298.5478, 274.01, 16.99219, 298.5478, 274.01, 19.33594, 297.0782, 274.01, 21.67969, 296.844, 274.01, 23.54531, 297.9984, 274.01, 24.02344, 298.4023, 274.01, 24.50156, 297.9984, 274.01, 26.36719, 296.844, 274.01, 38.08594, 296.8381, 274.01, 52.14844, 296.8033, 274.01, 59.17969, 296.8033, 274.01, 73.24219, 296.7682, 274.01, 99.02344, 296.7566, 274.01, 117.7734, 296.7216, 274.01, 129.4922, 296.7152, 274.01, 131.8359, 295.9083, 274.01, 134.1797, 295.5245, 274.01, 138.8672, 295.4763, 274.01, 155.2734, 295.4763, 274.01, 176.3672, 295.3861, 274.01, 190.4297, 295.3718, 274.01, 197.4609, 295.4763, 274.01, 202.1484, 295.4454, 274.01, 204.4922, 295.3438, 274.01, 206.8359, 295.0757, 274.01, 209.1797, 294.4124, 274.01, 211.5234, 292.3133, 274.01, 213.8672, 291.0809, 274.01, 216.2109, 289.6743, 274.01, 217.3081, 288.6234, 274.01, 219.3558, 286.2797, 274.01, 221.8608, 283.9359, 274.01, 225.5859, 280.045, 274.01, 227.9297, 277.8885, 274.01, 230.2734, 275.2857, 274.01, 232.6172, 273.2225, 274.01, 233.6225, 272.2172, 274.01, 234.9609, 270.5822, 274.01, 238.2254, 267.5297, 274.01, 240.3691, 265.1859, 274.01, 244.3359, 261.3326, 274.01, 246.6797, 258.8034, 274.01, 249.0234, 256.7196, 274.01, 251.3672, 254.0746, 274.01, 254.5313, 251.1234, 274.01, 255.333, 248.7797, 274.01, 257.3723, 246.4359, 274.01, 259.7201, 244.0922, 274.01, 260.106, 241.7484, 274.01, 261.6423, 239.4047, 274.01, 261.0804, 237.0609, 274.01, 259.3552, 234.7172, 274.01, 258.3984, 233.7696, 274.01, 256.0547, 232.8757, 274.01, 253.7109, 232.792, 274.01, 251.3672, 232.8161, 274.01, 246.6797, 232.6981, 274.01, 244.3359, 232.725, 274.01, 239.6484, 232.9137, 274.01, 234.9609, 232.8525, 274.01, 225.5859, 232.8757, 274.01, 209.1797, 232.8757, 274.01, 204.4922, 232.7537, 274.01, 195.1172, 232.7794, 274.01, 171.6797, 232.792, 274.01, 164.6484, 232.7666, 274.01, 152.9297, 232.7666, 274.01, 148.2422, 232.792, 274.01, 138.8672, 232.7406, 274.01 }; - size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); - std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); - EXPECT_EQ(340u * 3, groundTruth.size()); - CheckGroundTruth(structures, 5, 0, groundTruth); - } - - { - double groundTruthRaw[] = { -18.16406, 233.0632, 298.01, -27.53906, 233.1042, 298.01, -29.88281, 233.0819, 298.01, -34.57031, 233.131, 298.01, -43.94531, 233.1221, 298.01, -50.97656, 233.1736, 298.01, -62.69531, 233.1397, 298.01, -65.03906, 232.8376, 298.01, -69.72656, 232.9839, 298.01, -79.10156, 233.0245, 298.01, -90.82031, 233.0382, 298.01, -93.16406, 233.0859, 298.01, -109.5703, 233.1132, 298.01, -111.9141, 233.1791, 298.01, -114.2578, 233.7139, 298.01, -118.9453, 233.9793, 298.01, -128.3203, 234.0284, 298.01, -130.6641, 233.9793, 298.01, -135.3516, 234.0591, 298.01, -137.6953, 234.0284, 298.01, -142.3828, 234.0855, 298.01, -144.7266, 234.0284, 298.01, -151.7578, 234.002, 298.01, -158.7891, 234.0263, 298.01, -163.4766, 233.9784, 298.01, -165.8203, 234.0072, 298.01, -168.1641, 234.1756, 298.01, -170.5078, 234.2214, 298.01, -179.8828, 234.1934, 298.01, -186.9141, 234.2721, 298.01, -189.2578, 234.2289, 298.01, -193.9453, 234.2431, 298.01, -198.6328, 234.1692, 298.01, -200.9766, 234.2326, 298.01, -205.6641, 234.1271, 298.01, -212.6953, 234.2224, 298.01, -215.0391, 234.1992, 298.01, -222.0703, 234.3115, 298.01, -224.4141, 234.2224, 298.01, -226.7578, 234.2502, 298.01, -233.7891, 234.0906, 298.01, -238.4766, 234.0329, 298.01, -243.1641, 234.0283, 298.01, -247.8516, 233.7949, 298.01, -250.1953, 233.8681, 298.01, -252.5391, 234.7626, 298.01, -254.3469, 237.0609, 298.01, -255.6034, 239.4047, 298.01, -254.5181, 241.7484, 298.01, -254.2274, 244.0922, 298.01, -254.181, 248.7797, 298.01, -253.9355, 251.1234, 298.01, -253.5926, 253.4672, 298.01, -252.7483, 255.8109, 298.01, -250.8092, 258.1547, 298.01, -248.713, 260.4984, 298.01, -246.263, 262.8422, 298.01, -244.1406, 265.1859, 298.01, -241.6671, 267.5297, 298.01, -239.4754, 269.8734, 298.01, -237.0156, 272.2172, 298.01, -233.7891, 275.382, 298.01, -231.4453, 277.8249, 298.01, -229.1016, 279.9981, 298.01, -226.7578, 282.5281, 298.01, -224.4141, 284.6784, 298.01, -222.0703, 287.2355, 298.01, -220.5414, 288.6234, 298.01, -218.2745, 290.9672, 298.01, -217.3828, 291.6508, 298.01, -212.6953, 294.5949, 298.01, -210.3516, 295.3142, 298.01, -208.0078, 296.4674, 298.01, -205.6641, 296.8852, 298.01, -203.3203, 297.1563, 298.01, -196.2891, 297.1488, 298.01, -193.9453, 297.0597, 298.01, -182.2266, 296.9529, 298.01, -168.1641, 296.8576, 298.01, -154.1016, 296.9249, 298.01, -149.4141, 296.8921, 298.01, -128.3203, 296.9228, 298.01, -121.2891, 296.8623, 298.01, -111.9141, 296.8549, 298.01, -107.2266, 296.8266, 298.01, -102.5391, 296.8731, 298.01, -95.50781, 296.8453, 298.01, -88.47656, 296.9218, 298.01, -83.78906, 296.9016, 298.01, -69.72656, 296.979, 298.01, -67.38281, 296.9514, 298.01, -65.03906, 297.2199, 298.01, -62.69531, 296.9622, 298.01, -55.66406, 296.9926, 298.01, -50.97656, 296.9467, 298.01, -48.63281, 297.3652, 298.01, -46.28906, 297.0439, 298.01, -43.94531, 297.2875, 298.01, -39.25781, 297.0121, 298.01, -34.57031, 297.1564, 298.01, -32.22656, 297.3612, 298.01, -29.88281, 297.4229, 298.01, -27.53906, 297.1687, 298.01, -25.19531, 297.4334, 298.01, -18.16406, 297.3612, 298.01, -15.82031, 297.4441, 298.01, -13.47656, 297.4125, 298.01, -11.13281, 297.2468, 298.01, -8.789063, 297.4125, 298.01, -6.445313, 297.373, 298.01, -4.101563, 297.4195, 298.01, -1.757813, 297.077, 298.01, 0.5859375, 297.4229, 298.01, 2.929688, 297.4125, 298.01, 5.273438, 296.9489, 298.01, 7.617188, 297.3168, 298.01, 9.960938, 296.9377, 298.01, 12.30469, 296.8998, 298.01, 14.64844, 297.1975, 298.01, 16.99219, 296.8579, 298.01, 28.71094, 296.878, 298.01, 40.42969, 296.8163, 298.01, 42.77344, 296.8369, 298.01, 49.80469, 296.734, 298.01, 59.17969, 296.6906, 298.01, 61.52344, 296.6365, 298.01, 68.55469, 296.6278, 298.01, 73.24219, 296.5777, 298.01, 75.58594, 296.6191, 298.01, 84.96094, 296.5284, 298.01, 96.67969, 296.5538, 298.01, 103.7109, 296.479, 298.01, 115.4297, 296.4259, 298.01, 122.4609, 296.3434, 298.01, 129.4922, 296.3495, 298.01, 131.8359, 295.9141, 298.01, 136.5234, 296.2256, 298.01, 138.8672, 295.833, 298.01, 143.5547, 295.9857, 298.01, 145.8984, 295.8791, 298.01, 152.9297, 295.833, 298.01, 164.6484, 295.6819, 298.01, 171.6797, 295.6819, 298.01, 181.0547, 295.5401, 298.01, 185.7422, 295.5742, 298.01, 192.7734, 295.557, 298.01, 197.4609, 295.8012, 298.01, 202.1484, 295.6819, 298.01, 204.4922, 295.3698, 298.01, 206.8359, 294.803, 298.01, 209.1797, 294.3656, 298.01, 211.5234, 292.4764, 298.01, 213.8672, 291.1765, 298.01, 216.2109, 289.5873, 298.01, 217.229, 288.6234, 298.01, 218.5547, 287.0752, 298.01, 221.7097, 283.9359, 298.01, 225.5859, 279.8775, 298.01, 227.9297, 277.5633, 298.01, 230.2734, 275.0808, 298.01, 233.1989, 272.2172, 298.01, 234.9609, 270.2887, 298.01, 237.7384, 267.5297, 298.01, 241.9922, 263.0843, 298.01, 244.3359, 260.7643, 298.01, 246.788, 258.1547, 298.01, 249.0234, 255.451, 298.01, 250.3651, 253.4672, 298.01, 251.5297, 251.1234, 298.01, 252.1947, 248.7797, 298.01, 252.4915, 246.4359, 298.01, 252.5755, 241.7484, 298.01, 252.8592, 239.4047, 298.01, 252.9236, 237.0609, 298.01, 252.2924, 234.7172, 298.01, 251.3672, 233.4697, 298.01, 249.0234, 232.882, 298.01, 244.3359, 232.9048, 298.01, 241.9922, 233.0145, 298.01, 232.6172, 232.9048, 298.01, 227.9297, 233.0007, 298.01, 216.2109, 233.0632, 298.01, 211.5234, 233.0537, 298.01, 206.8359, 232.9699, 298.01, 204.4922, 232.7322, 298.01, 199.8047, 232.7186, 298.01, 190.4297, 232.7719, 298.01, 183.3984, 232.7719, 298.01, 181.0547, 232.7322, 298.01, 174.0234, 232.7048, 298.01, 171.6797, 232.7322, 298.01, 166.9922, 232.6908, 298.01, 157.6172, 232.7975, 298.01, 155.2734, 232.7588, 298.01, 148.2422, 232.7875, 298.01, 143.5547, 232.7614, 298.01, 138.8672, 232.6477, 298.01, 124.8047, 232.6179, 298.01, 122.4609, 232.6477, 298.01, 113.0859, 232.6027, 298.01, 110.7422, 232.4552, 298.01, 108.3984, 232.2192, 298.01, 106.0547, 231.6764, 298.01, 103.7109, 231.8559, 298.01, 102.8237, 232.3734, 298.01, 101.3672, 232.9839, 298.01, 99.02344, 233.0951, 298.01, 87.30469, 233.0819, 298.01, 84.96094, 233.1091, 298.01, 80.27344, 233.0726, 298.01, 77.92969, 233.1132, 298.01, 70.89844, 233.1397, 298.01, 68.55469, 233.1132, 298.01, 52.14844, 233.131, 298.01, 45.11719, 233.0859, 298.01, 44.16726, 232.3734, 298.01, 42.77344, 231.0206, 298.01, 42.04498, 230.0297, 298.01, 42.77344, 229.2462, 298.01, 45.11719, 228.5664, 298.01, 47.46094, 227.0695, 298.01, 49.80469, 226.0552, 298.01, 52.14844, 224.5723, 298.01, 54.49219, 223.6857, 298.01, 56.83594, 221.8519, 298.01, 59.17969, 220.2086, 298.01, 61.52344, 218.8854, 298.01, 64.94469, 215.9672, 298.01, 66.21094, 215.0191, 298.01, 67.72036, 213.6234, 298.01, 68.55469, 212.6986, 298.01, 70.89844, 210.5055, 298.01, 74.53191, 206.5922, 298.01, 76.54903, 204.2484, 298.01, 78.26105, 201.9047, 298.01, 80.27344, 198.9262, 298.01, 82.61719, 195.2822, 298.01, 82.98087, 194.8734, 298.01, 84.96094, 190.9255, 298.01, 85.43701, 190.1859, 298.01, 86.33423, 187.8422, 298.01, 87.78722, 185.4984, 298.01, 88.60233, 183.1547, 298.01, 89.10253, 180.8109, 298.01, 90.17504, 178.4672, 298.01, 90.88959, 176.1234, 298.01, 91.43783, 173.7797, 298.01, 92.39601, 171.4359, 298.01, 92.95762, 169.0922, 298.01, 93.55695, 159.7172, 298.01, 93.65527, 157.3734, 298.01, 93.67542, 152.6859, 298.01, 93.61213, 150.3422, 298.01, 93.22542, 143.3109, 298.01, 93.06345, 140.9672, 298.01, 92.77563, 138.6234, 298.01, 91.21714, 133.9359, 298.01, 90.67235, 131.5922, 298.01, 89.88776, 129.2484, 298.01, 88.8737, 126.9047, 298.01, 88.44087, 124.5609, 298.01, 86.09712, 119.8734, 298.01, 85.05786, 117.5297, 298.01, 83.87151, 115.1859, 298.01, 82.22388, 112.8422, 298.01, 81.09117, 110.4984, 298.01, 77.92969, 106.4052, 298.01, 77.3894, 105.8109, 298.01, 75.94332, 103.4672, 298.01, 71.71799, 98.77969, 298.01, 68.55469, 95.65721, 298.01, 63.86719, 91.54878, 298.01, 61.52344, 90.1121, 298.01, 59.17969, 88.15762, 298.01, 56.83594, 86.51503, 298.01, 54.49219, 85.42721, 298.01, 52.14844, 83.64907, 298.01, 49.80469, 82.89023, 298.01, 47.46094, 81.50237, 298.01, 45.11719, 80.62591, 298.01, 42.77344, 79.18153, 298.01, 40.42969, 78.7203, 298.01, 38.08594, 78.1349, 298.01, 35.74219, 77.11755, 298.01, 33.39844, 76.51949, 298.01, 31.05469, 76.07934, 298.01, 26.36719, 74.67744, 298.01, 24.02344, 74.42056, 298.01, 14.64844, 74.07317, 298.01, 9.960938, 74.11538, 298.01, 2.929688, 74.40105, 298.01, 0.5859375, 74.67952, 298.01, -1.757813, 75.31406, 298.01, -4.101563, 76.07065, 298.01, -6.445313, 76.49051, 298.01, -8.789063, 77.17276, 298.01, -11.13281, 78.20097, 298.01, -15.82031, 79.31967, 298.01, -18.16406, 80.76948, 298.01, -20.50781, 81.64266, 298.01, -22.85156, 83.0305, 298.01, -25.19531, 83.7937, 298.01, -27.53906, 85.63515, 298.01, -29.88281, 86.7363, 298.01, -32.22656, 88.36089, 298.01, -34.57031, 90.3302, 298.01, -36.56719, 91.74844, 298.01, -41.60156, 95.93605, 298.01, -46.58845, 101.1234, 298.01, -50.17995, 105.8109, 298.01, -52.10386, 108.1547, 298.01, -53.63992, 110.4984, 298.01, -54.95532, 112.8422, 298.01, -56.64794, 115.1859, 298.01, -57.4403, 117.5297, 298.01, -58.91927, 119.8734, 298.01, -59.78655, 122.2172, 298.01, -61.11754, 124.5609, 298.01, -61.58921, 126.9047, 298.01, -62.38012, 129.2484, 298.01, -63.49118, 131.5922, 298.01, -64.02599, 133.9359, 298.01, -64.3932, 136.2797, 298.01, -65.11897, 138.6234, 298.01, -65.64544, 140.9672, 298.01, -66.23938, 147.9984, 298.01, -66.46289, 152.6859, 298.01, -66.48911, 155.0297, 298.01, -66.34437, 159.7172, 298.01, -65.99894, 164.4047, 298.01, -65.49149, 169.0922, 298.01, -64.6875, 171.4359, 298.01, -63.7739, 176.1234, 298.01, -62.9398, 178.4672, 298.01, -61.86011, 180.8109, 298.01, -61.33423, 183.1547, 298.01, -60.43332, 185.4984, 298.01, -58.00781, 190.0632, 298.01, -56.85406, 192.5297, 298.01, -55.66406, 194.7283, 298.01, -54.11692, 197.2172, 298.01, -50.97656, 201.8369, 298.01, -47.36435, 206.5922, 298.01, -45.04395, 208.9359, 298.01, -42.83026, 211.2797, 298.01, -39.25781, 214.7435, 298.01, -34.57031, 218.4974, 298.01, -32.22656, 219.9595, 298.01, -28.02053, 222.9984, 298.01, -27.53906, 223.4238, 298.01, -25.19531, 224.4187, 298.01, -22.85156, 225.8252, 298.01, -20.50781, 226.9067, 298.01, -18.16406, 228.4286, 298.01, -15.82031, 229.1235, 298.01, -14.9447, 230.0297, 298.01, -15.82031, 231.3969, 298.01, -16.94484, 232.3734, 298.01 }; - size_t n = sizeof(groundTruthRaw) / sizeof(groundTruthRaw[0]); - std::vector groundTruth(groundTruthRaw, groundTruthRaw+n); - EXPECT_EQ(358u * 3, groundTruth.size()); - CheckGroundTruth(structures, 5, 8, groundTruth); - } -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#if 0 - -TEST(StructureSet2, ReadFromJsonAndCompute1) -{ - DicomStructureSet2 structureSet; - - OrthancPlugins::FullOrthancDataset dicom(GetTestJson()); - structureSet.Clear(); - - structureSet.FillStructuresFromDataset(dicom); - - structureSet.ComputeDependentProperties(); -} - -TEST(StructureSet2, ReadFromJsonAndCompute2) -{ - DicomStructureSet2 structureSet; - - OrthancPlugins::FullOrthancDataset dicom(GetTestJson()); - structureSet.Clear(); - - structureSet.SetContents(dicom); -} -#endif - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -static bool CutStructureWithPlane( - std::vector< std::pair >& segments, - const DicomStructure2& structure, - const double originX, const double originY, const double originZ, - const double axisX_X, const double axisX_Y, const double axisX_Z, - const double axisY_X, const double axisY_Y, const double axisY_Z -) -{ - // create an AXIAL cutting plane, too far away from the volume - // (> sliceThickness/2) - Vector origin, axisX, axisY; - LinearAlgebra::AssignVector(origin, originX, originY, originZ); - LinearAlgebra::AssignVector(axisX, axisX_X, axisX_Y, axisX_Z); - LinearAlgebra::AssignVector(axisY, axisY_X, axisY_Y, axisY_Z); - CoordinateSystem3D cuttingPlane(origin, axisX, axisY); - - // compute intersection - bool ok = structure.Project(segments, cuttingPlane); - return ok; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -static double pointsCoord1[] = { 2, 2, 3, 3, 6, 8, 8, 7, 8, 8, 6 }; -static double pointsCoord2[] = { 2, 6, 8, 10, 12, 10, 8, 6, 4, 2, 4 }; -static const size_t pointsCoord1Count = STONE_ARRAY_SIZE(pointsCoord1); -static const size_t pointsCoord2Count = STONE_ARRAY_SIZE(pointsCoord2); -const size_t POLYGON_POINT_COUNT = pointsCoord1Count; - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -static void CreateBasicStructure(DicomStructure2& structure) -{ - // see https://www.dropbox.com/s/1o1vg53hsbvx4cc/test-rtstruct-polygons.jpg?dl=0 - EXPECT_EQ(pointsCoord1Count, pointsCoord2Count); - EXPECT_EQ(11u, pointsCoord2Count); - - for (size_t slice = 0; slice < 3; ++slice) - { - DicomStructurePolygon2 polygon("Oblomptu", "CLOSED_PLANAR"); - for (size_t ip = 0; ip < pointsCoord1Count; ++ip) - { - Vector pt; - double pt0 = pointsCoord1[ip]; - double pt1 = pointsCoord2[ip]; - double pt2 = 4 * (static_cast(slice) - 1); // -4, 0, 4 - LinearAlgebra::AssignVector(pt, pt0, pt1, pt2); - polygon.AddPoint(pt); - } - structure.AddPolygon(polygon); - } - structure.ComputeDependentProperties(); -} - - -TEST(StructureSet2, CutAxialOutsideTop) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an AXIAL cutting plane, too far away from the volume - // (> sliceThickness/2) - bool ok = CutStructureWithPlane(segments, structure, - 0, 0, 7, - 1, 0, 0, - 0, 1, 0); - EXPECT_FALSE(ok); -} - - -TEST(StructureSet2, CutAxialOutsideBottom) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an AXIAL cutting plane, too far away from the volume - // (> sliceThickness/2) - bool ok = CutStructureWithPlane(segments, structure, - 0, 0, -6.66, - 1, 0, 0, - 0, 1, 0); - EXPECT_FALSE(ok); -} - -TEST(StructureSet2, CutAxialInsideClose) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an AXIAL cutting plane in the volume - bool ok = CutStructureWithPlane(segments, structure, - 0, 0, 1.1, - 1, 0, 0, - 0, 1, 0); - EXPECT_TRUE(ok); - EXPECT_EQ(POLYGON_POINT_COUNT, segments.size()); - - for (size_t i = 0; i < segments.size(); ++i) - { - EXPECT_LT(i, POLYGON_POINT_COUNT); - EXPECT_LT(i, POLYGON_POINT_COUNT); - - const ScenePoint2D& pt = segments[i].first; - - // ...should be at the same location as the 3D coords since the plane - // is rooted at 0,0,0 with normal 0,0,1 - EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX); - EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX); - } -} - - -TEST(StructureSet2, CutAxialInsideFar) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an AXIAL cutting plane - Vector origin, axisX, axisY; - LinearAlgebra::AssignVector(origin, 0, 0, 0); - LinearAlgebra::AssignVector(axisX, 1, 0, 0); - LinearAlgebra::AssignVector(axisY, 0, 1, 0); - CoordinateSystem3D cuttingPlane(origin, axisX, axisY); - - // compute intersection - bool ok = structure.Project(segments, cuttingPlane); - EXPECT_TRUE(ok); - - EXPECT_EQ(11u, segments.size()); - for (size_t i = 0; i < segments.size(); ++i) - { - EXPECT_LT(i, pointsCoord1Count); - EXPECT_LT(i, pointsCoord2Count); - - // the 2D points of the projected polygon - const ScenePoint2D& pt = segments[i].first; - - // ...should be at the same location as the 3D coords since the plane - // is rooted at 0,0,0 with normal 0,0,1 - EXPECT_NEAR(pt.GetX(), pointsCoord1[i], DELTA_MAX); - EXPECT_NEAR(pt.GetY(), pointsCoord2[i], DELTA_MAX); - } -} - -TEST(StructureSet2, CutCoronalOutsideClose) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an X,Z cutting plane, outside of the volume - Vector origin, axisX, axisY; - LinearAlgebra::AssignVector(origin, 0, 0, 0); - LinearAlgebra::AssignVector(axisX, 1, 0, 0); - LinearAlgebra::AssignVector(axisY, 0, 0, 1); - CoordinateSystem3D cuttingPlane(origin, axisX, axisY); - - // compute intersection - bool ok = structure.Project(segments, cuttingPlane); - EXPECT_FALSE(ok); -} - -TEST(StructureSet2, CutCoronalInsideClose1DTest) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - - // create an X,Z cutting plane, outside of the volume - Vector origin, axisX, axisY; - LinearAlgebra::AssignVector(origin, 0, 3, 0); - LinearAlgebra::AssignVector(axisX, 1, 0, 0); - LinearAlgebra::AssignVector(axisY, 0, 0, 1); - CoordinateSystem3D cuttingPlane(origin, axisX, axisY); - - ASSERT_EQ(3u, structure.GetPolygons().size()); - - for (int i = 0; i < 3; ++i) - { - double polygonZ = static_cast(i - 1) * 4.0; - - const DicomStructurePolygon2& topSlab = structure.GetPolygons()[i]; - - // let's compute the intersection between the polygon and the plane - // intersections are in plane coords - std::vector intersects; - topSlab.ProjectOnConstantPlane(intersects, cuttingPlane); - - ASSERT_EQ(4u, intersects.size()); - - EXPECT_NEAR(2, intersects[0].GetX(), DELTA_MAX); - EXPECT_NEAR(4, intersects[1].GetX(), DELTA_MAX); - EXPECT_NEAR(7, intersects[2].GetX(), DELTA_MAX); - EXPECT_NEAR(8, intersects[3].GetX(), DELTA_MAX); - - for (size_t i = 0; i < 4u; ++i) - { - EXPECT_NEAR(polygonZ, intersects[i].GetY(), DELTA_MAX); - } - } -} - -TEST(StructureSet2, CutCoronalInsideClose) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an X,Z cutting plane, outside of the volume - Vector origin, axisX, axisY; - LinearAlgebra::AssignVector(origin, 0, 3, 0); - LinearAlgebra::AssignVector(axisX, 1, 0, 0); - LinearAlgebra::AssignVector(axisY, 0, 0, 1); - CoordinateSystem3D cuttingPlane(origin, axisX, axisY); - - // compute intersection - ASSERT_TRUE(structure.Project(segments, cuttingPlane)); - EXPECT_EQ(24u, segments.size()); - - size_t numberOfVeryShortSegments = 0; - for (size_t iSegment = 0; iSegment < segments.size(); ++iSegment) - { - // count the NON vertical very short segments - if (LinearAlgebra::IsNear(segments[iSegment].first.GetX(), segments[iSegment].second.GetX())) - { - if (LinearAlgebra::IsNear(segments[iSegment].first.GetY(), segments[iSegment].second.GetY())) - { - numberOfVeryShortSegments++; - } - } - } - EXPECT_EQ(8u, numberOfVeryShortSegments); -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - -TEST(DisjointDataSet, BasicTest) -{ - const size_t ITEM_COUNT = 10; - DisjointDataSet ds(ITEM_COUNT); - - for (size_t i = 0; i < ITEM_COUNT; ++i) - { - EXPECT_EQ(i, ds.Find(i)); - } - - ds.Union(0, 4); - EXPECT_EQ(0u, ds.Find(0)); - EXPECT_EQ(0u, ds.Find(4)); - - ds.Union(4, 6); - ds.Union(8, 9); - ds.Union(0, 8); - - for (size_t i = 0; i < ITEM_COUNT; ++i) - { - size_t parent = ds.Find(i); - EXPECT_TRUE(0 == parent || 1 == parent || 2 == parent || 3 == parent || 5 == parent || 7 == parent); - } - - ds.Union(1, 2); - ds.Union(1, 7); - for (size_t i = 0; i < ITEM_COUNT; ++i) - { - size_t parent = ds.Find(i); - EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent || 5 == parent); - } - - ds.Union(3, 5); - for (size_t i = 0; i < ITEM_COUNT; ++i) - { - size_t parent = ds.Find(i); - EXPECT_TRUE(0 == parent || 1 == parent || 3 == parent); - } - - EXPECT_EQ(ds.Find(0), ds.Find(0)); - EXPECT_EQ(ds.Find(0), ds.Find(4)); - EXPECT_EQ(ds.Find(0), ds.Find(6)); - EXPECT_EQ(ds.Find(0), ds.Find(8)); - EXPECT_EQ(ds.Find(0), ds.Find(8)); - - EXPECT_EQ(ds.Find(1), ds.Find(7)); - EXPECT_EQ(ds.Find(2), ds.Find(1)); - EXPECT_EQ(ds.Find(7), ds.Find(2)); - - EXPECT_EQ(ds.Find(3), ds.Find(5)); - EXPECT_EQ(ds.Find(5), ds.Find(3)); - - ds.Union(0, 1); - ds.Union(3, 1); - for (size_t i = 0; i < ITEM_COUNT; ++i) - { - EXPECT_EQ(ds.Find(0), ds.Find(i)); - } -} - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -TEST(StructureSet2, CutSagittalInsideClose) -{ - DicomStructure2 structure; - CreateBasicStructure(structure); - std::vector< std::pair > segments; - - // create an X,Z cutting plane, inside of the volume - Vector origin, axisX, axisY; - LinearAlgebra::AssignVector(origin, 0, 3, 0); - LinearAlgebra::AssignVector(axisX, 1, 0, 0); - LinearAlgebra::AssignVector(axisY, 0, 0, 1); - CoordinateSystem3D cuttingPlane(origin, axisX, axisY); - - // compute intersection - bool ok = structure.Project(segments, cuttingPlane); - EXPECT_TRUE(ok); -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - -static size_t ConvertListOfSlabsToSegments_Add(RtStructRectanglesInSlab& rectangles, int row, double xmin, double xmax) -{ - double ymin = static_cast(row) * 5.0; - double ymax = static_cast(row + 1) * 5.0; - - RtStructRectangleInSlab rectangle; - rectangle.xmin = xmin; - rectangle.xmax = xmax; - rectangle.ymin = ymin; - rectangle.ymax = ymax; - - rectangles.push_back(rectangle); - - return 1u; -} - -static size_t FillTestRectangleList(std::vector< RtStructRectanglesInSlab >& rectanglesForEachSlab) -{ - // ConvertListOfSlabsToSegments - size_t rectCount = 0; - - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 5, 31); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 0, 36, 50); - - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 20, 45); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 1, 52, 70); - - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 0, 32); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 35, 44); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 2, 60, 75); - - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 10, 41); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 3, 46, 80); - - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 34, 42); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 4, 90, 96); - - rectanglesForEachSlab.push_back(RtStructRectanglesInSlab()); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 1, 33); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 40, 43); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 51, 61); - rectCount += ConvertListOfSlabsToSegments_Add(rectanglesForEachSlab.back(), 5, 76, 95); - - return rectCount; -} - -/* -void AddSlabBoundaries( - std::vector >& boundaries, - const std::vector& slabCuts, size_t iSlab) -*/ - - -/* -void ProcessBoundaryList( - std::vector< std::pair >& segments, - const std::vector >& boundaries, - double y) -*/ - - -TEST(StructureSet2, ProcessBoundaryList_Empty) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - - boundaries.clear(); - EXPECT_NO_THROW(AddSlabBoundaries(boundaries, slabCuts, 0)); - ASSERT_EQ(0u, boundaries.size()); -} - -TEST(StructureSet2, ProcessBoundaryListTopRow) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - FillTestRectangleList(slabCuts); - - boundaries.clear(); - AddSlabBoundaries(boundaries, slabCuts, 0); - - { - size_t i = 0; - ASSERT_EQ(4u, boundaries.size()); - - ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); - ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); - ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); - ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); - ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX); - i++; - } -} - -TEST(StructureSet2, ProcessBoundaryListRows_0_and_1) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - FillTestRectangleList(slabCuts); - - boundaries.clear(); - AddSlabBoundaries(boundaries, slabCuts, 0); - AddSlabBoundaries(boundaries, slabCuts, 1); - - ASSERT_EQ(8u, boundaries.size()); - - { - size_t i = 0; - - ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); - ASSERT_NEAR(5, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); - ASSERT_NEAR(20, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); - ASSERT_NEAR(31, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); - ASSERT_NEAR(36, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); - ASSERT_NEAR(45, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); - ASSERT_NEAR(50, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_Start, boundaries[i].second); - ASSERT_NEAR(52, boundaries[i].first, DELTA_MAX); - i++; - - ASSERT_EQ(RectangleBoundaryKind_End, boundaries[i].second); - ASSERT_NEAR(70, boundaries[i].first, DELTA_MAX); - i++; - } -} - -TEST(StructureSet2, ConvertListOfSlabsToSegments_EmptyBoundaries) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - FillTestRectangleList(slabCuts); - boundaries.clear(); - std::vector< std::pair > segments; - ASSERT_NO_THROW(ProcessBoundaryList(segments, boundaries, 42.0)); - ASSERT_EQ(0u, segments.size()); -} - -TEST(StructureSet2, ConvertListOfSlabsToSegments_TopRow_Horizontal) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - FillTestRectangleList(slabCuts); - - // top row - { - std::vector< std::pair > segments; - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, 0); - ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin); - - ASSERT_EQ(2u, segments.size()); - - ASSERT_NEAR( 5.0, segments[0].first.GetX(), DELTA_MAX); - ASSERT_NEAR(31.0, segments[0].second.GetX(), DELTA_MAX); - ASSERT_NEAR( 0.0, segments[0].first.GetY(), DELTA_MAX); - ASSERT_NEAR( 0.0, segments[0].second.GetY(), DELTA_MAX); - - ASSERT_NEAR(36.0, segments[1].first.GetX(), DELTA_MAX); - ASSERT_NEAR(50.0, segments[1].second.GetX(), DELTA_MAX); - ASSERT_NEAR( 0.0, segments[1].first.GetY(), DELTA_MAX); - ASSERT_NEAR( 0.0, segments[1].second.GetY(), DELTA_MAX); - } -} - -TEST(StructureSet2, ConvertListOfSlabsToSegments_All_Horizontal) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - FillTestRectangleList(slabCuts); - - // top row - { - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, 0); - std::vector< std::pair > segments; - ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymin); - } - - // mids - { - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, 0); - AddSlabBoundaries(boundaries, slabCuts, 1); - std::vector< std::pair > segments; - ProcessBoundaryList(segments, boundaries, slabCuts[0][0].ymax); - - ASSERT_EQ(4u, segments.size()); - - ASSERT_NEAR(05.0, segments[0].first.GetX(), DELTA_MAX); - ASSERT_NEAR(20.0, segments[0].second.GetX(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[0].first.GetY(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[0].second.GetY(), DELTA_MAX); - - ASSERT_NEAR(31.0, segments[1].first.GetX(), DELTA_MAX); - ASSERT_NEAR(36.0, segments[1].second.GetX(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[1].first.GetY(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[1].second.GetY(), DELTA_MAX); - - ASSERT_NEAR(45.0, segments[2].first.GetX(), DELTA_MAX); - ASSERT_NEAR(50.0, segments[2].second.GetX(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[2].first.GetY(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[2].second.GetY(), DELTA_MAX); - - ASSERT_NEAR(52.0, segments[3].first.GetX(), DELTA_MAX); - ASSERT_NEAR(70.0, segments[3].second.GetX(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[3].first.GetY(), DELTA_MAX); - ASSERT_NEAR(05.0, segments[3].second.GetY(), DELTA_MAX); - } - - // bottom row - { - std::vector > boundaries; - AddSlabBoundaries(boundaries, slabCuts, 1); - std::vector< std::pair > segments; - ProcessBoundaryList(segments, boundaries, slabCuts[1][0].ymax); - - ASSERT_EQ(2u, segments.size()); - - ASSERT_NEAR(20.0, segments[0].first.GetX(), DELTA_MAX); - ASSERT_NEAR(45.0, segments[0].second.GetX(), DELTA_MAX); - ASSERT_NEAR(10.0, segments[0].first.GetY(), DELTA_MAX); - ASSERT_NEAR(10.0, segments[0].second.GetY(), DELTA_MAX); - - ASSERT_NEAR(52.0, segments[1].first.GetX(), DELTA_MAX); - ASSERT_NEAR(70.0, segments[1].second.GetX(), DELTA_MAX); - ASSERT_NEAR(10.0, segments[1].first.GetY(), DELTA_MAX); - ASSERT_NEAR(10.0, segments[1].second.GetY(), DELTA_MAX); - } - -} - -TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Empty) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - - std::vector< std::pair > segments; - - ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, 0)); - ASSERT_EQ(0u, segments.size()); -} - -TEST(StructureSet2, ConvertListOfSlabsToSegments_Complete_Regular) -{ - std::vector< RtStructRectanglesInSlab > slabCuts; - std::vector > boundaries; - size_t totalRectCount = FillTestRectangleList(slabCuts); - - std::vector< std::pair > segments; - - ASSERT_NO_THROW(ConvertListOfSlabsToSegments(segments, slabCuts, totalRectCount)); - ASSERT_EQ(60u, segments.size()); - - size_t i = 0; - - ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 50.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 52.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 32.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 35.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 60.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 41.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 46.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 80.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 96.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 1.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 33.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 43.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 51.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 61.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 76.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 31.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 36.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 0.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 31.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 36.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 45.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 50.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 5.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 70.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 5.0000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 44.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 45.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 52.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 70.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 75.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 10.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 0.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 32.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 35.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 44.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 60.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 75.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 15.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 15.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 10.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 34.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 41.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 42.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 46.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 80.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 90.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 20.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 20.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 34.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 40.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 42.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 90.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 95.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 25.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 96.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 25.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 1.0000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 33.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 40.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 43.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 51.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 61.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); - i++; - ASSERT_NEAR(segments[i].first.GetX(), 76.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].first.GetY(), 30.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetX(), 95.000000000000000, DELTA_MAX); - ASSERT_NEAR(segments[i].second.GetY(), 30.000000000000000, DELTA_MAX); -} - -#if defined(BGO_ENABLE_DICOMSTRUCTURESETLOADER2) && (ORTHANC_SANDBOXED != 1) - -#include - -TEST(StructureSet2, ReadFromJsonPart2) -{ - DicomStructureSet2 structureSet; - std::string jsonText; - - Orthanc::SystemToolbox::ReadFile(jsonText, "72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json"); - - FullOrthancDataset dicom(jsonText); - structureSet.Clear(); - - structureSet.FillStructuresFromDataset(dicom); - structureSet.ComputeDependentProperties(); - - //const std::vector& structures = structureSet.structures_; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 TEST(StructureSet, ReadFromJson) { - FullOrthancDataset dicom(Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00), - Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00)); + OrthancStone::FullOrthancDataset dicom( + Orthanc::EmbeddedResources::GetFileResourceBuffer(Orthanc::EmbeddedResources::RT_STRUCT_00), + Orthanc::EmbeddedResources::GetFileResourceSize(Orthanc::EmbeddedResources::RT_STRUCT_00)); - DicomStructureSet rtstruct(dicom); + OrthancStone::DicomStructureSet rtstruct(dicom); ASSERT_DOUBLE_EQ(0.0, rtstruct.GetEstimatedNormal() [0]); ASSERT_DOUBLE_EQ(0.0, rtstruct.GetEstimatedNormal() [1]); diff -r 0208f99b8bde -r affde38b84de OrthancStone/UnitTestsSources/UnitTestsSources.cmake --- a/OrthancStone/UnitTestsSources/UnitTestsSources.cmake Tue Feb 01 07:19:15 2022 +0100 +++ b/OrthancStone/UnitTestsSources/UnitTestsSources.cmake Tue Feb 01 08:38:32 2022 +0100 @@ -33,7 +33,3 @@ ${CMAKE_CURRENT_LIST_DIR}/UnitTestsMain.cpp ${CMAKE_CURRENT_LIST_DIR}/VolumeRenderingTests.cpp ) - -add_definitions( - -DBGO_ENABLE_DICOMSTRUCTURESETLOADER2=1 - )