diff 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
line wrap: on
line diff
--- a/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp	Wed Jan 19 12:32:15 2022 +0100
+++ b/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp	Wed Jan 19 13:50:28 2022 +0100
@@ -29,6 +29,8 @@
 
 #include "OrthancDatasets/DicomDatasetReader.h"
 
+#include "BucketAccumulator2D.h"
+
 #include <Logging.h>
 #include <OrthancException.h>
 #include <Toolbox.h>
@@ -44,6 +46,7 @@
 
 #include <limits>
 #include <stdio.h>
+#include <boost/math/constants/constants.hpp>
 #include <boost/geometry.hpp>
 #include <boost/geometry/geometries/point_xy.hpp>
 #include <boost/geometry/geometries/polygon.hpp>
@@ -58,7 +61,7 @@
 #endif
 
 
-typedef boost::geometry::model::d2::point_xy<double> BoostPoint;
+  typedef boost::geometry::model::d2::point_xy<double> BoostPoint;
 typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon;
 typedef boost::geometry::model::multi_polygon<BoostPolygon>  BoostMultiPolygon;
 
@@ -625,6 +628,9 @@
         structures_[i].polygons_.push_back(polygon);
       }
     }
+
+    EstimateGeometry();
+    
 #if STONE_TIME_BLOCKING_OPS
     boost::posix_time::ptime timerEnd = boost::posix_time::microsec_clock::universal_time();
     boost::posix_time::time_duration duration = timerEnd - timerStart;
@@ -1111,10 +1117,98 @@
   }
 
 
-  void DicomStructureSet::Test()
+  void DicomStructureSet::EstimateGeometry()
   {
-    printf("OK\n");
+    static const double PI = boost::math::constants::pi<double>();
+
+    BucketAccumulator2D accumulator(0, PI, 9,   /* for acos() */
+                                    -PI, PI, 9, /* for atan() */
+                                    true /* store values */);
+
+    unsigned int countPolygons = 0;
+    for (size_t i = 0; i < structures_.size(); i++)
+    {
+      const Polygons& polygons = structures_[i].polygons_;
+
+      for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it)
+      {
+        countPolygons++;
+        
+        const Points& points = it->GetPoints();
+        
+        if (points.size() >= 3)
+        {
+          // Compute the normal of the polygon using 3 successive points
+          Vector normal;
+          bool valid = false;
+
+          for (size_t i = 0; i + 2 < points.size(); i++)
+          {
+            const Vector& a = points[i];
+            const Vector& b = points[i + 1];
+            const Vector& c = points[i + 2];
+            LinearAlgebra::CrossProduct(normal, b - a, c - a);  // (*)
+            LinearAlgebra::NormalizeVector(normal);
+
+            if (LinearAlgebra::IsNear(boost::numeric::ublas::norm_2(normal), 1.0))  // (**)
+            {
+              valid = true;
+              break;
+            }
+          }
 
+          if (valid)
+          {
+            // Check that all the points of the polygon lie in the plane defined by the normal
+            double d1 = GeometryToolbox::ProjectAlongNormal(points[0], normal);
+          
+            for (size_t i = 1; i < points.size(); i++)
+            {
+              double d2 = GeometryToolbox::ProjectAlongNormal(points[i], normal);
+            
+              if (!LinearAlgebra::IsNear(d1, d2))
+              {
+                valid = false;
+                break;
+              }
+            }
+          }
+
+          if (valid)
+          {
+            if (normal[2] < 0)
+            {
+              normal = -normal;
+            }
+
+            // The normal is a non-zero unit vector because of (*) and (**), so "r == 1"
+            // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2
+            
+            const double theta = acos(normal[2]);
+            const double phi = atan(normal[1]);
+            accumulator.AddValue(theta, phi);
+          }
+        }
+      }
+    }
+
+    size_t bestX, bestY;
+    accumulator.FindBestBucket(bestX, bestY);
+
+    if (accumulator.GetBucketContentSize(bestX, bestY) > 0)
+    {
+      double normalTheta, normalPhi;
+      accumulator.ComputeBestMedian(normalTheta, normalPhi);
+      
+      // Back to (x,y,z) normal, taking "r == 1"
+      // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2
+      double sinTheta = sin(normalTheta);
+      estimatedNormal_ = LinearAlgebra::CreateVector(sinTheta * cos(normalPhi), sinTheta * sin(normalPhi), cos(normalTheta));
+    }
+      
+    std::vector<double> polygonsProjection;
+    polygonsProjection.reserve(countPolygons);
+    
     for (size_t i = 0; i < structures_.size(); i++)
     {
       const Polygons& polygons = structures_[i].polygons_;
@@ -1122,28 +1216,31 @@
       for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it)
       {
         const Points& points = it->GetPoints();
-        
-        if (points.size() >= 3)
-        {
-          const Vector& a = points[0];
-          const Vector& b = points[1];
-          const Vector& c = points[2];
-          Vector n;
-          LinearAlgebra::CrossProduct(n, b - a, c - a);
-          LinearAlgebra::NormalizeVector(n);
-          if (n[2] < 0)
-            n = -n;
-          //LinearAlgebra::Print(n);
+        polygonsProjection.push_back(GeometryToolbox::ProjectAlongNormal(points[0], estimatedNormal_));
+      }
+    }
+
+    std::sort(polygonsProjection.begin(), polygonsProjection.end());
+
+    std::vector<double> deltas;
+    deltas.reserve(polygonsProjection.size());
 
-          // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2
-          double r = 1.0;
-          double theta = acos(n[2]);
-          double phi = atan(n[1]);
-          printf("%.02f %.02f %.02f => ", n[0], n[1], n[2]);
-          printf("%.02f %.02f =>", theta, phi);
-          printf("%.02f %.02f %.02f\n", r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta));
-        }
+    for (size_t i = 0; i + 1 < polygonsProjection.size(); i++)
+    {
+      if (!LinearAlgebra::IsNear(polygonsProjection[i], polygonsProjection[i + 1]))
+      {
+        assert(polygonsProjection[i + 1] > polygonsProjection[i]);
+        deltas.push_back(polygonsProjection[i + 1] - polygonsProjection[i]);
       }
     }
+
+    if (deltas.empty())
+    {
+      estimatedSliceThickness_ = 1;
+    }
+    else
+    {
+      estimatedSliceThickness_ = LinearAlgebra::ComputeMedian(deltas);
+    }
   }
 }