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