Mercurial > hg > orthanc-stone
comparison OrthancStone/Sources/Toolbox/DicomStructureSet.cpp @ 1892:cdf91ad891a5
estimated geometry of rt-struct
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 19 Jan 2022 13:50:28 +0100 |
parents | 6ce81914f7e4 |
children | 90b5e116a5f9 |
comparison
equal
deleted
inserted
replaced
1891:3716d72161d2 | 1892:cdf91ad891a5 |
---|---|
27 #include "GeometryToolbox.h" | 27 #include "GeometryToolbox.h" |
28 #include "GenericToolbox.h" | 28 #include "GenericToolbox.h" |
29 | 29 |
30 #include "OrthancDatasets/DicomDatasetReader.h" | 30 #include "OrthancDatasets/DicomDatasetReader.h" |
31 | 31 |
32 #include "BucketAccumulator2D.h" | |
33 | |
32 #include <Logging.h> | 34 #include <Logging.h> |
33 #include <OrthancException.h> | 35 #include <OrthancException.h> |
34 #include <Toolbox.h> | 36 #include <Toolbox.h> |
35 | 37 |
36 #if defined(_MSC_VER) | 38 #if defined(_MSC_VER) |
42 # include <boost/date_time/posix_time/posix_time.hpp> | 44 # include <boost/date_time/posix_time/posix_time.hpp> |
43 #endif | 45 #endif |
44 | 46 |
45 #include <limits> | 47 #include <limits> |
46 #include <stdio.h> | 48 #include <stdio.h> |
49 #include <boost/math/constants/constants.hpp> | |
47 #include <boost/geometry.hpp> | 50 #include <boost/geometry.hpp> |
48 #include <boost/geometry/geometries/point_xy.hpp> | 51 #include <boost/geometry/geometries/point_xy.hpp> |
49 #include <boost/geometry/geometries/polygon.hpp> | 52 #include <boost/geometry/geometries/polygon.hpp> |
50 #include <boost/geometry/multi/geometries/multi_polygon.hpp> | 53 #include <boost/geometry/multi/geometries/multi_polygon.hpp> |
51 | 54 |
56 #if ORTHANC_ENABLE_DCMTK == 1 | 59 #if ORTHANC_ENABLE_DCMTK == 1 |
57 # include "ParsedDicomDataset.h" | 60 # include "ParsedDicomDataset.h" |
58 #endif | 61 #endif |
59 | 62 |
60 | 63 |
61 typedef boost::geometry::model::d2::point_xy<double> BoostPoint; | 64 typedef boost::geometry::model::d2::point_xy<double> BoostPoint; |
62 typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon; | 65 typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon; |
63 typedef boost::geometry::model::multi_polygon<BoostPolygon> BoostMultiPolygon; | 66 typedef boost::geometry::model::multi_polygon<BoostPolygon> BoostMultiPolygon; |
64 | 67 |
65 | 68 |
66 static void Union(BoostMultiPolygon& output, | 69 static void Union(BoostMultiPolygon& output, |
623 } | 626 } |
624 | 627 |
625 structures_[i].polygons_.push_back(polygon); | 628 structures_[i].polygons_.push_back(polygon); |
626 } | 629 } |
627 } | 630 } |
631 | |
632 EstimateGeometry(); | |
633 | |
628 #if STONE_TIME_BLOCKING_OPS | 634 #if STONE_TIME_BLOCKING_OPS |
629 boost::posix_time::ptime timerEnd = boost::posix_time::microsec_clock::universal_time(); | 635 boost::posix_time::ptime timerEnd = boost::posix_time::microsec_clock::universal_time(); |
630 boost::posix_time::time_duration duration = timerEnd - timerStart; | 636 boost::posix_time::time_duration duration = timerEnd - timerStart; |
631 int64_t durationMs = duration.total_milliseconds(); | 637 int64_t durationMs = duration.total_milliseconds(); |
632 LOG(WARNING) << "DicomStructureSet::Setup took " << durationMs << " ms"; | 638 LOG(WARNING) << "DicomStructureSet::Setup took " << durationMs << " ms"; |
1109 } | 1115 } |
1110 } | 1116 } |
1111 } | 1117 } |
1112 | 1118 |
1113 | 1119 |
1114 void DicomStructureSet::Test() | 1120 void DicomStructureSet::EstimateGeometry() |
1115 { | 1121 { |
1116 printf("OK\n"); | 1122 static const double PI = boost::math::constants::pi<double>(); |
1117 | 1123 |
1124 BucketAccumulator2D accumulator(0, PI, 9, /* for acos() */ | |
1125 -PI, PI, 9, /* for atan() */ | |
1126 true /* store values */); | |
1127 | |
1128 unsigned int countPolygons = 0; | |
1118 for (size_t i = 0; i < structures_.size(); i++) | 1129 for (size_t i = 0; i < structures_.size(); i++) |
1119 { | 1130 { |
1120 const Polygons& polygons = structures_[i].polygons_; | 1131 const Polygons& polygons = structures_[i].polygons_; |
1121 | 1132 |
1122 for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) | 1133 for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) |
1123 { | 1134 { |
1135 countPolygons++; | |
1136 | |
1124 const Points& points = it->GetPoints(); | 1137 const Points& points = it->GetPoints(); |
1125 | 1138 |
1126 if (points.size() >= 3) | 1139 if (points.size() >= 3) |
1127 { | 1140 { |
1128 const Vector& a = points[0]; | 1141 // Compute the normal of the polygon using 3 successive points |
1129 const Vector& b = points[1]; | 1142 Vector normal; |
1130 const Vector& c = points[2]; | 1143 bool valid = false; |
1131 Vector n; | 1144 |
1132 LinearAlgebra::CrossProduct(n, b - a, c - a); | 1145 for (size_t i = 0; i + 2 < points.size(); i++) |
1133 LinearAlgebra::NormalizeVector(n); | 1146 { |
1134 if (n[2] < 0) | 1147 const Vector& a = points[i]; |
1135 n = -n; | 1148 const Vector& b = points[i + 1]; |
1136 //LinearAlgebra::Print(n); | 1149 const Vector& c = points[i + 2]; |
1137 | 1150 LinearAlgebra::CrossProduct(normal, b - a, c - a); // (*) |
1138 // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2 | 1151 LinearAlgebra::NormalizeVector(normal); |
1139 double r = 1.0; | 1152 |
1140 double theta = acos(n[2]); | 1153 if (LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(normal), 1.0)) // (**) |
1141 double phi = atan(n[1]); | 1154 { |
1142 printf("%.02f %.02f %.02f => ", n[0], n[1], n[2]); | 1155 valid = true; |
1143 printf("%.02f %.02f =>", theta, phi); | 1156 break; |
1144 printf("%.02f %.02f %.02f\n", r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta)); | 1157 } |
1145 } | 1158 } |
1146 } | 1159 |
1160 if (valid) | |
1161 { | |
1162 // Check that all the points of the polygon lie in the plane defined by the normal | |
1163 double d1 = GeometryToolbox::ProjectAlongNormal(points[0], normal); | |
1164 | |
1165 for (size_t i = 1; i < points.size(); i++) | |
1166 { | |
1167 double d2 = GeometryToolbox::ProjectAlongNormal(points[i], normal); | |
1168 | |
1169 if (!LinearAlgebra::IsNear(d1, d2)) | |
1170 { | |
1171 valid = false; | |
1172 break; | |
1173 } | |
1174 } | |
1175 } | |
1176 | |
1177 if (valid) | |
1178 { | |
1179 if (normal[2] < 0) | |
1180 { | |
1181 normal = -normal; | |
1182 } | |
1183 | |
1184 // The normal is a non-zero unit vector because of (*) and (**), so "r == 1" | |
1185 // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2 | |
1186 | |
1187 const double theta = acos(normal[2]); | |
1188 const double phi = atan(normal[1]); | |
1189 accumulator.AddValue(theta, phi); | |
1190 } | |
1191 } | |
1192 } | |
1193 } | |
1194 | |
1195 size_t bestX, bestY; | |
1196 accumulator.FindBestBucket(bestX, bestY); | |
1197 | |
1198 if (accumulator.GetBucketContentSize(bestX, bestY) > 0) | |
1199 { | |
1200 double normalTheta, normalPhi; | |
1201 accumulator.ComputeBestMedian(normalTheta, normalPhi); | |
1202 | |
1203 // Back to (x,y,z) normal, taking "r == 1" | |
1204 // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2 | |
1205 double sinTheta = sin(normalTheta); | |
1206 estimatedNormal_ = LinearAlgebra::CreateVector(sinTheta * cos(normalPhi), sinTheta * sin(normalPhi), cos(normalTheta)); | |
1207 } | |
1208 | |
1209 std::vector<double> polygonsProjection; | |
1210 polygonsProjection.reserve(countPolygons); | |
1211 | |
1212 for (size_t i = 0; i < structures_.size(); i++) | |
1213 { | |
1214 const Polygons& polygons = structures_[i].polygons_; | |
1215 | |
1216 for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) | |
1217 { | |
1218 const Points& points = it->GetPoints(); | |
1219 polygonsProjection.push_back(GeometryToolbox::ProjectAlongNormal(points[0], estimatedNormal_)); | |
1220 } | |
1221 } | |
1222 | |
1223 std::sort(polygonsProjection.begin(), polygonsProjection.end()); | |
1224 | |
1225 std::vector<double> deltas; | |
1226 deltas.reserve(polygonsProjection.size()); | |
1227 | |
1228 for (size_t i = 0; i + 1 < polygonsProjection.size(); i++) | |
1229 { | |
1230 if (!LinearAlgebra::IsNear(polygonsProjection[i], polygonsProjection[i + 1])) | |
1231 { | |
1232 assert(polygonsProjection[i + 1] > polygonsProjection[i]); | |
1233 deltas.push_back(polygonsProjection[i + 1] - polygonsProjection[i]); | |
1234 } | |
1235 } | |
1236 | |
1237 if (deltas.empty()) | |
1238 { | |
1239 estimatedSliceThickness_ = 1; | |
1240 } | |
1241 else | |
1242 { | |
1243 estimatedSliceThickness_ = LinearAlgebra::ComputeMedian(deltas); | |
1147 } | 1244 } |
1148 } | 1245 } |
1149 } | 1246 } |