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