LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
ConvexHull.cxx
Go to the documentation of this file.
1 
8 // Framework Includes
9 
11 
12 // LArSoft includes
13 
14 // boost includes
15 #include <boost/range/adaptor/reversed.hpp>
16 
17 // Eigen
18 #include <Eigen/Core>
19 
20 // std includes
21 #include <cmath>
22 #include <limits>
23 
24 //------------------------------------------------------------------------------------------------------------------------------------------
25 // implementation follows
26 
27 namespace lar_cluster3d {
28 
29  ConvexHull::ConvexHull(const PointList& pointListIn, float kinkAngle, float minEdgeDistance)
30  : fKinkAngle(kinkAngle)
31  , fMinEdgeDistance(minEdgeDistance)
32  , fPoints(pointListIn)
33  , fConvexHullArea(0.)
34  {
35  fConvexHull.clear();
36 
37  // Build out the convex hull around the input points
39 
40  // And the area
42  }
43 
44  //------------------------------------------------------------------------------------------------------------------------------------------
45 
47 
48  //------------------------------------------------------------------------------------------------------------------------------------------
49 
50  bool ConvexHull::isLeft(const Point& p0, const Point& p1, const Point& pCheck) const
51  {
52  // Use the cross product to determine if the check point lies to the left, on or right
53  // of the line defined by points p0 and p1
54  return crossProduct(p0, p1, pCheck) > 0;
55  }
56 
57  //------------------------------------------------------------------------------------------------------------------------------------------
58 
59  float ConvexHull::crossProduct(const Point& p0, const Point& p1, const Point& p2) const
60  {
61  // Define a quick 2D cross product here since it will used quite a bit!
62  float deltaX = std::get<0>(p1) - std::get<0>(p0);
63  float deltaY = std::get<1>(p1) - std::get<1>(p0);
64  float dCheckX = std::get<0>(p2) - std::get<0>(p0);
65  float dCheckY = std::get<1>(p2) - std::get<1>(p0);
66 
67  return ((deltaX * dCheckY) - (deltaY * dCheckX));
68  }
69 
70  //------------------------------------------------------------------------------------------------------------------------------------------
71 
72  float ConvexHull::Area() const
73  {
74  float area(0.);
75 
76  // Compute the area by taking advantage of
77  // 1) the ability to decompose a convex hull into triangles,
78  // 2) the ability to use the cross product to calculate the area
79  // So, the technique is to pick a point which we take as the center of the polygon
80  // and then sum the signed area of triangles formed from this point to two adjecent
81  // vertices on the convex hull.
82  float x = 0.;
83  float y = 0.;
84  float n = float(fConvexHull.size());
85 
86  for (const auto& point : fConvexHull) {
87  x += std::get<0>(point);
88  y += std::get<1>(point);
89  }
90 
91  Point center(x / n, y / n, 0);
92 
93  Point lastPoint = fConvexHull.front();
94 
95  for (const auto& point : fConvexHull) {
96  if (point != lastPoint) area += 0.5 * crossProduct(center, lastPoint, point);
97 
98  lastPoint = point;
99  }
100 
101  return area;
102  }
103 
104  //------------------------------------------------------------------------------------------------------------------------------------------
105 
106  void ConvexHull::getConvexHull(const PointList& pointList)
107  {
108  // Start by identifying the min/max points
109  Point pMinMin = pointList.front();
110 
111  // Find the point with maximum y sharing the same x value...
112  PointList::const_iterator pMinMaxItr =
113  std::find_if(pointList.begin(), pointList.end(), [pMinMin](const auto& elem) {
114  return std::get<0>(elem) != std::get<0>(pMinMin);
115  });
116 
117  Point pMinMax = *(--pMinMaxItr); // This could be / probably is the same point
118 
119  fMinMaxPointPair.first.first = pMinMin;
120  fMinMaxPointPair.first.second = pMinMax;
121 
122  Point pMaxMax = pointList.back(); // get the last point
123 
124  // Find the point with minimum y sharing the same x value...
125  PointList::const_reverse_iterator pMaxMinItr =
126  std::find_if(pointList.rbegin(), pointList.rend(), [pMaxMax](const auto& elem) {
127  return std::get<0>(elem) != std::get<0>(pMaxMax);
128  });
129 
130  Point pMaxMin = *(--pMaxMinItr); // This could be / probably is the same point
131 
132  fMinMaxPointPair.second.first = pMaxMin;
133  fMinMaxPointPair.second.second = pMaxMax;
134 
135  // Get the lower convex hull
136  PointList lowerHullList;
137 
138  lowerHullList.push_back(pMinMin);
139 
140  // loop over points in the set
141  for (auto curPoint : pointList) {
142  // First check that we even want to consider this point
143  if (isLeft(pMinMin, pMaxMin, curPoint)) continue;
144 
145  // In words: treat "lowerHullVec" as a stack. If there is only one entry then
146  // push the current point onto the stack. If more than one entry then check if
147  // the current point is to the left of the line from the last two points in the
148  // stack. If it is then add the current point to the stack, if it is not then
149  // pop the last point off the stack and try again.
150  while (lowerHullList.size() > 1) {
151  Point lastPoint = lowerHullList.back();
152 
153  lowerHullList.pop_back();
154 
155  Point nextToLastPoint = lowerHullList.back();
156 
157  if (isLeft(nextToLastPoint, lastPoint, curPoint)) {
158  lowerHullList.push_back(lastPoint);
159  break;
160  }
161  }
162 
163  lowerHullList.push_back(curPoint);
164  }
165 
166  // Now get the upper hull
167  PointList upperHullList;
168 
169  upperHullList.push_back(pMaxMax);
170 
171  for (auto curPoint : boost::adaptors::reverse(pointList)) {
172  // First check that we even want to consider this point
173  // Remember that we are going "backwards" so still want
174  // curPoint to lie to the "left"
175  if (isLeft(pMaxMax, pMinMax, curPoint)) continue;
176 
177  // Replicate the above but going the other direction...
178  while (upperHullList.size() > 1) {
179  Point lastPoint = upperHullList.back();
180 
181  upperHullList.pop_back();
182 
183  Point nextToLastPoint = upperHullList.back();
184 
185  if (isLeft(nextToLastPoint, lastPoint, curPoint)) {
186  upperHullList.push_back(lastPoint);
187  break;
188  }
189  }
190 
191  upperHullList.push_back(curPoint);
192  }
193 
194  // Now we merge the two lists into the output list
195  std::copy(lowerHullList.begin(), lowerHullList.end(), std::back_inserter(fConvexHull));
196 
197  if (pMaxMin == pMaxMax) upperHullList.pop_front();
198 
199  std::copy(upperHullList.begin(), upperHullList.end(), std::back_inserter(fConvexHull));
200 
201  if (pMinMin != pMinMax) fConvexHull.push_back(pMinMin);
202 
203  return;
204  }
205 
207  {
208  PointList::const_iterator nextPointItr = fConvexHull.begin();
209  PointList::const_iterator firstPointItr = nextPointItr++;
210 
211  float maxSeparation(0.);
212 
213  // Make sure the current list has been cleared
214  fExtremePoints.clear();
215 
216  // For finding the two farthest points
217  PointPair extremePoints(Point(0, 0, NULL), Point(0, 0, NULL));
218 
219  while (nextPointItr != fConvexHull.end()) {
220  // Get a vector representing the edge from the first to the current next point
221  Point firstPoint = *firstPointItr++;
222  Point nextPoint = *nextPointItr;
223  Eigen::Vector2f firstEdge(std::get<0>(*firstPointItr) - std::get<0>(firstPoint),
224  std::get<1>(*firstPointItr) - std::get<1>(firstPoint));
225 
226  // normalize it
227  firstEdge.normalize();
228 
229  PointList::const_iterator endPointItr = nextPointItr;
230 
231  while (++endPointItr != fConvexHull.end()) {
232  Point endPoint = *endPointItr;
233  Eigen::Vector2f nextEdge(std::get<0>(endPoint) - std::get<0>(nextPoint),
234  std::get<1>(endPoint) - std::get<1>(nextPoint));
235 
236  // normalize it
237  nextEdge.normalize();
238 
239  // Have we found the turnaround point?
240  if (firstEdge.dot(nextEdge) < 0.) {
241  Eigen::Vector2f separation(std::get<0>(nextPoint) - std::get<0>(firstPoint),
242  std::get<1>(nextPoint) - std::get<1>(firstPoint));
243  float separationDistance = separation.norm();
244 
245  if (separationDistance > maxSeparation) {
246  extremePoints.first = firstPoint;
247  extremePoints.second = nextPoint;
248  maxSeparation = separationDistance;
249  }
250 
251  // Regardless of thise being the maximum distance we have hit a turnaround point so
252  // we need to break out of this loop
253  break;
254  }
255 
256  nextPointItr = endPointItr;
257  nextPoint = endPoint;
258  }
259 
260  // If we have hit the end of the convex hull without finding a turnaround point then we are not
261  // going to find one so break out of the main loop
262  if (endPointItr == fConvexHull.end()) break;
263 
264  // Need to make sure we don't overrun the next point
265  if (firstPointItr == nextPointItr) nextPointItr++;
266  }
267 
268  fExtremePoints.push_back(extremePoints.first);
269  fExtremePoints.push_back(extremePoints.second);
270 
271  return fExtremePoints;
272  }
273 
275  {
276  // Goal here is to isolate the points where we see a large deviation in the contour defined by the
277  // convex hull. The complications are numerous, chief is that some deviations can be artificially
278  // "rounded" by a series of very small steps around a large corner. So we need to protect against that.
279 
280  // Make sure the current list has been cleared
281  fKinkPoints.clear();
282 
283  // Need a mininum number of points/edges
284  if (fConvexHull.size() > 3) {
285  // Idea will be to traverse the convex hull and keep track of all points where there is a
286  // "kink" which will be defined as a "large" angle between adjacent edges.
287  // Recall that construxtion of the convex hull results in the same point at the start and
288  // end of the list
289  // Getting the initial point requires some contortions because the convex hull point list will
290  // contain the same point at both ends of the list (why?)
291  PointList::iterator pointItr = fConvexHull.begin();
292 
293  // Advance to the second to last element
294  std::advance(pointItr, fConvexHull.size() - 2);
295 
296  Point lastPoint = *pointItr++;
297 
298  // Reset pointer to the first element
299  pointItr = fConvexHull.begin();
300 
301  Point curPoint = *pointItr++;
302  Eigen::Vector2f lastEdge(std::get<0>(curPoint) - std::get<0>(lastPoint),
303  std::get<1>(curPoint) - std::get<1>(lastPoint));
304 
305  lastEdge.normalize();
306 
307  while (pointItr != fConvexHull.end()) {
308  Point& nextPoint = *pointItr++;
309 
310  Eigen::Vector2f nextEdge(std::get<0>(nextPoint) - std::get<0>(curPoint),
311  std::get<1>(nextPoint) - std::get<1>(curPoint));
312 
313  if (nextEdge.norm() > fMinEdgeDistance) {
314  nextEdge.normalize();
315 
316  if (lastEdge.dot(nextEdge) < fKinkAngle)
317  fKinkPoints.emplace_back(curPoint, lastEdge, nextEdge);
318 
319  lastEdge = nextEdge;
320  }
321 
322  curPoint = nextPoint;
323  }
324  }
325 
326  return fKinkPoints;
327  }
328 
330  float& closestDistance) const
331  {
332  // The idea is to find the nearest edge of the convex hull, defined by
333  // two adjacent vertices of the hull, to the input point.
334  // As near as I can tell, the best way to do this is to apply brute force...
335  // Idea will be to iterate over pairs of points
336  PointList::const_iterator curPointItr = fConvexHull.begin();
337  Point prevPoint = *curPointItr++;
338  Point curPoint = *curPointItr;
339 
340  // Set up the winner
341  PointPair closestEdge(prevPoint, curPoint);
342 
343  closestDistance = std::numeric_limits<float>::max();
344 
345  // curPointItr is meant to point to the second point
346  while (curPointItr != fConvexHull.end()) {
347  if (curPoint != prevPoint) {
348  // Dereference some stuff
349  float xPrevToPoint = (std::get<0>(point) - std::get<0>(prevPoint));
350  float yPrevToPoint = (std::get<1>(point) - std::get<1>(prevPoint));
351  float xPrevToCur = (std::get<0>(curPoint) - std::get<0>(prevPoint));
352  float yPrevToCur = (std::get<1>(curPoint) - std::get<1>(prevPoint));
353  float edgeLength = std::sqrt(xPrevToCur * xPrevToCur + yPrevToCur * yPrevToCur);
354 
355  // Find projection onto convex hull edge
356  float projection = ((xPrevToPoint * xPrevToCur) + (yPrevToPoint * yPrevToCur)) / edgeLength;
357 
358  // DOCA point
359  Point docaPoint(std::get<0>(prevPoint) + projection * xPrevToCur / edgeLength,
360  std::get<1>(prevPoint) + projection * yPrevToCur / edgeLength,
361  0);
362 
363  if (projection > edgeLength) docaPoint = curPoint;
364  if (projection < 0) docaPoint = prevPoint;
365 
366  float xDocaDist = std::get<0>(point) - std::get<0>(docaPoint);
367  float yDocaDist = std::get<1>(point) - std::get<1>(docaPoint);
368  float docaDist = xDocaDist * xDocaDist + yDocaDist * yDocaDist;
369 
370  if (docaDist < closestDistance) {
371  closestEdge = PointPair(prevPoint, curPoint);
372  closestDistance = docaDist;
373  }
374  }
375 
376  prevPoint = curPoint;
377  curPoint = *curPointItr++;
378  }
379 
380  closestDistance = std::sqrt(closestDistance);
381 
382  // Convention is convex hull vertices sorted in counter clockwise fashion so if the point
383  // lays to the left of the nearest edge then it must be an interior point
384  if (isLeft(closestEdge.first, closestEdge.second, point)) closestDistance = -closestDistance;
385 
386  return closestEdge;
387  }
388 
389  float ConvexHull::findNearestDistance(const Point& point) const
390  {
391  float closestDistance;
392 
393  findNearestEdge(point, closestDistance);
394 
395  return closestDistance;
396  }
397 
398 } // namespace lar_cluster3d
Float_t x
Definition: compare.C:6
intermediate_table::iterator iterator
std::tuple< float, float, const reco::ClusterHit3D * > Point
projected x,y position and 3D hit
Definition: ConvexHull.h:32
MinMaxPointPair fMinMaxPointPair
Definition: ConvexHull.h:117
Float_t y
Definition: compare.C:6
std::list< Point > PointList
The list of the projected points.
Definition: ConvexHull.h:33
float crossProduct(const Point &p0, const Point &p1, const Point &p2) const
Gets the cross product of line from p0 to p1 and p0 to p2.
Definition: ConvexHull.cxx:59
intermediate_table::const_iterator const_iterator
std::pair< Point, Point > PointPair
Definition: ConvexHull.h:34
Implements a ConvexHull for use in clustering.
float Area() const
Computes the area of the enclosed convext hull.
Definition: ConvexHull.cxx:72
PointPair findNearestEdge(const Point &, float &) const
Given an input Point, find the nearest edge.
Definition: ConvexHull.cxx:329
float findNearestDistance(const Point &) const
Given an input point, find the distance to the nearest edge/point.
Definition: ConvexHull.cxx:389
const reco::ConvexHullKinkTupleList & getKinkPoints()
Find the points with the largest angles.
Definition: ConvexHull.cxx:274
std::list< ConvexHullKinkTuple > ConvexHullKinkTupleList
Definition: Cluster3D.h:349
const PointList & getConvexHull() const
recover the list of convex hull vertices
Definition: ConvexHull.h:57
reco::ConvexHullKinkTupleList fKinkPoints
Definition: ConvexHull.h:120
const PointList & getExtremePoints()
Find the two points on the hull which are furthest apart.
Definition: ConvexHull.cxx:206
bool isLeft(const Point &p0, const Point &p1, const Point &pCheck) const
Determines whether a point is to the left, right or on line specifiec by p0 and p1.
Definition: ConvexHull.cxx:50
ConvexHull(const PointList &, float=0.85, float=0.35)
Constructor.
Definition: ConvexHull.cxx:29
const PointList & fPoints
Definition: ConvexHull.h:115
Char_t n[5]
~ConvexHull()
Destructor.
Definition: ConvexHull.cxx:46