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 }