libatomprobe
Library for Atom Probe Tomography (APT) computation
convexHull.cpp
Go to the documentation of this file.
1 /*
2  * convexHull.cpp - Wrapper for quickHull convex hull routines
3  * Copyright (C) 2015 Daniel Haley
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
20 
21 //QHull library
22 extern "C"
23 {
24  #include <libqhull/qhull_a.h>
25 }
26 
27 #include <numeric>
28 #include <limits>
29 
31 
32 namespace AtomProbe
33 {
34 
35 using std::vector;
36 
37 //Is the qhull library already initialised (true)
38 // or do we need to call qhull's init routines?
39 bool qhullInited=false;
40 //TODO: Work out where the payoff for this is
41 //grab size when doing convex hull calculations
42 const unsigned int HULL_GRAB_SIZE=4096;
43 
44 
45 
46 
48 {
49  qh_freeqhull(true);
50  qhullInited=false;
51 }
52 
53 unsigned int doHull(unsigned int bufferSize, double *buffer,
54  vector<Point3D> &resHull, Point3D &midPoint, bool freeHullOnExit)
55 {
56  if(qhullInited)
57  {
58  qh_freeqhull(true);
59  qhullInited=false;
60  }
61 
62  const int dim=3;
63  //Now compute the new hull
64  //Generate the convex hull
65  //(result is stored in qh's globals :( )
66  //note that the input is "joggled" to
67  //ensure simplicial facet generation
68 
69  //Qhull >=2012 has a "feature" where it won't accept null arguments for the output
70  // there is no clear way to shut it up.
71  FILE *outSquelch=0;
72 #if defined(__linux__) || defined(__APPLE__) || defined(__BSD__)
73  outSquelch=fopen("/dev/null","w");
74 #elif defined(__win32__) || defined(__win64__)
75  outSquelch=fopen("NUL","w");
76 #endif
77 
78  if(!outSquelch)
79  {
80  //Give up, just let qhull output random statistics to stderr
81  outSquelch=stderr;
82  }
83 
84  qh_new_qhull( dim,
85  bufferSize,
86  buffer,
87  false,
88  (char *)"qhull QJ", //Joggle the output, such that only simplical facets are generated
89  outSquelch, //QHULL's interface is bizarre, no way to set null pointer in qhull 2012 - result is inf. loop in qhull_fprintf and error reporting func.
90  outSquelch);
91  qhullInited=true;
92 
93  if(outSquelch !=stderr)
94  {
95  fclose(outSquelch);
96  }
97 
98  unsigned int numPoints=0;
99  //count points
100  //--
101  //OKay, whilst this may look like invalid syntax,
102  //qh is actually a macro from qhull
103  //that creates qh. or qh-> as needed
104  numPoints = qh num_vertices;
105  //--
106  midPoint=Point3D(0,0,0);
107 
108  if(!numPoints)
109  return 0;
110 
111  //store points in vector
112  //--
113  try
114  {
115  resHull.resize(numPoints);
116  }
117  catch(std::bad_alloc)
118  {
119  return HULL_ERR_NO_MEM;
120  }
121  //--
122 
123  //Compute mean point
124  //--
125  vertexT *vertex;
126  vertex= qh vertex_list;
127  int curPt=0;
128  while(vertex != qh vertex_tail)
129  {
130  resHull[curPt]=Point3D(vertex->point[0],
131  vertex->point[1],
132  vertex->point[2]);
133  midPoint+=resHull[curPt];
134  curPt++;
135  vertex = vertex->next;
136  }
137  midPoint*=1.0f/(float)numPoints;
138  //--
139  if(freeHullOnExit)
140  {
141  qh_freeqhull(true);
142  qhullInited=false;
143 
144  }
145 
146  return 0;
147 }
148 
149 unsigned int computeConvexHull(const vector<IonHit> &data, unsigned int *progress,
150  bool (*callback)(bool),std::vector<Point3D> &curHull, bool freeHull)
151 {
152 
153  size_t numPts;
154  numPts=data.size();
155  //Easy case of no data
156  if(numPts < 4)
157  return 0;
158 
159  double *buffer;
160  double *tmp;
161  //Use malloc so we can re-alloc
162  buffer =(double*) malloc(HULL_GRAB_SIZE*3*sizeof(double));
163 
164  if(!buffer)
165  return HULL_ERR_NO_MEM;
166 
167  size_t bufferOffset=0;
168 
169  //Do the convex hull in steps for two reasons
170  // 1) qhull chokes on large data
171  // 2) we need to run the callback every now and again, so we have to
172  // work in batches.
173  Point3D midPoint;
174  float maxSqrDist=-1;
175 
176  const size_t PROGRESS_REDUCE = 5000;
177 
178  size_t progressReduce=PROGRESS_REDUCE;
179  size_t n=0;
180  for(size_t uj=0; uj<data.size(); uj++)
181  {
182  //Do contained-in-sphere check
183  if(curHull.empty() || midPoint.sqrDist(data[uj].getPos())>= maxSqrDist)
184  {
185  //Copy point data into hull buffer
186  buffer[3*bufferOffset]=data[uj].getPos()[0];
187  buffer[3*bufferOffset+1]=data[uj].getPos()[1];
188  buffer[3*bufferOffset+2]=data[uj].getPos()[2];
189  bufferOffset++;
190 
191  //If we have hit the hull grab size, perform a hull
192 
193  if(bufferOffset == HULL_GRAB_SIZE)
194  {
195  bufferOffset+=curHull.size();
196  tmp=(double*)realloc(buffer,
197  3*bufferOffset*sizeof(double));
198  if(!tmp)
199  {
200  free(buffer);
201 
202  return HULL_ERR_NO_MEM;
203  }
204 
205  buffer=tmp;
206  //Copy in the old hull
207  for(size_t uk=0; uk<curHull.size(); uk++)
208  {
209  buffer[3*(HULL_GRAB_SIZE+uk)]=curHull[uk][0];
210  buffer[3*(HULL_GRAB_SIZE+uk)+1]=curHull[uk][1];
211  buffer[3*(HULL_GRAB_SIZE+uk)+2]=curHull[uk][2];
212  }
213 
214  unsigned int errCode=0;
215 
216  errCode=doHull(bufferOffset,buffer,curHull,midPoint,freeHull);
217  if(errCode)
218  {
219  free(buffer);
220  return errCode;
221  }
222 
223 
224  //Now compute the min sqr distance
225  //to the vertex, so we can fast-reject
226  maxSqrDist=std::numeric_limits<float>::max();
227  for(size_t ui=0; ui<curHull.size(); ui++)
228  maxSqrDist=std::min(maxSqrDist,curHull[ui].sqrDist(midPoint));
229  //reset buffer size
230  bufferOffset=0;
231  }
232 
233  }
234  n++;
235 
236  //Update the progress information, and run callback periodically
237  if(!progressReduce--)
238  {
239  if(!(*callback)(false))
240  {
241  free(buffer);
242  return HULL_ERR_USER_ABORT;
243  }
244 
245  *progress= (unsigned int)((float)(n)/((float)numPts)*100.0f);
246 
247  progressReduce=PROGRESS_REDUCE;
248  }
249  }
250 
251  //Need at least 4 objects to construct a sufficiently large buffer
252  if(bufferOffset + curHull.size() > 4)
253  {
254  //Re-allocate the buffer to determine the last hull size
255  tmp=(double*)realloc(buffer,
256  3*(bufferOffset+curHull.size())*sizeof(double));
257  if(!tmp)
258  {
259  free(buffer);
260  return HULL_ERR_NO_MEM;
261  }
262  buffer=tmp;
263 
264  #pragma omp parallel for
265  for(size_t ui=0; ui<curHull.size(); ui++)
266  {
267  buffer[3*(bufferOffset+ui)]=curHull[ui][0];
268  buffer[3*(bufferOffset+ui)+1]=curHull[ui][1];
269  buffer[3*(bufferOffset+ui)+2]=curHull[ui][2];
270  }
271 
272  unsigned int errCode=doHull(bufferOffset+curHull.size(),buffer,curHull,midPoint,freeHull);
273 
274  if(errCode)
275  {
276  free(buffer);
277  //Free the last convex hull mem
278  return errCode;
279  }
280  }
281 
282 
283  free(buffer);
284  return 0;
285 }
286 
287 
288 //FIXME: This needs to use the new qhull 2015 we have implemented
289 unsigned int GetReducedHullPts(const vector<IonHit> &points, float reductionDim,
290  unsigned int *progress, bool (*callback)(bool), vector<IonHit> &pointResult)
291 {
292  //TODO: This could be made to use a fixed amount of ram, by
293  //partitioning the input points, and then
294  //computing multiple hulls.
295  //Take the resultant hull points, then hull again. This would be
296  //much more space efficient, and more easily parallellised
297  //Alternately, compute a for a randoms K set of points, and reject
298  //points that lie in the hull from further computation
299 
300  //Need at least 4 points to define a hull in 3D
301  if(points.size() < 4)
302  return 1;
303 
304  unsigned int dummyProg;
305  vector<Point3D> theHull;
306  if(computeConvexHull(points,progress,callback,theHull,false))
307  return 2;
308 
309  Point3D midPoint(0,0,0);
310  for(size_t ui=0;ui<theHull.size();ui++)
311  midPoint+=theHull[ui];
312  midPoint *= 1.0f/(float)theHull.size();
313 
314  //Now we will find the mass/volume centroid of the hull
315  //by constructing sets of pyramids.
316  //We cannot use the simple midpoint, as point distribution
317  //around the hull surface may be uneven
318 
319  //Here the faces of the hull are the bases of
320  //pyramids, and the midpoint is the apex
321  Point3D hullCentroid(0.0f,0.0f,0.0f);
322  float massPyramids=0.0f;
323  //Run through the faced list
324  facetT *curFac = qh facet_list;
325 
326  while(curFac != qh facet_tail)
327  {
328  vertexT *vertex;
329  Point3D pyramidCentroid;
330  unsigned int ui;
331  Point3D ptArray[3];
332  float vol;
333 
334  //find the pyramid volume + centroid
335  pyramidCentroid = midPoint;
336  ui=0;
337 
338  //This assertion fails, some more processing is needed to be done to break
339  //the facet into something simplical
340  ASSERT(curFac->simplicial);
341  vertex = (vertexT *)curFac->vertices->e[ui].p;
342  while(vertex)
343  { //copy the vertex info into the pt array
344  (ptArray[ui])[0] = vertex->point[0];
345  (ptArray[ui])[1] = vertex->point[1];
346  (ptArray[ui])[2] = vertex->point[2];
347 
348  //aggregate pyramidal points
349  pyramidCentroid += ptArray[ui];
350 
351  //increment before updating vertex
352  //to allow checking for NULL termination
353  ui++;
354  vertex = (vertexT *)curFac->vertices->e[ui].p;
355 
356  }
357 
358  //note that this counter has been post incremented.
359  ASSERT(ui ==3);
360  vol = pyramidVol(ptArray,midPoint);
361 
362  ASSERT(vol>=0);
363 
364  //Find the midpoint of the pyramid, this will be the
365  //same as its centre of mass.
366  pyramidCentroid*= 0.25f;
367  hullCentroid = hullCentroid + (pyramidCentroid*vol);
368  massPyramids+=vol;
369 
370  curFac=curFac->next;
371  }
372 
373  hullCentroid *= 1.0f/massPyramids;
374 
375  size_t badFacetCount=0;
376  float minDist=std::numeric_limits<float>::max();
377  //find the smallest distance between the centroid and the
378  //convex hull
379  curFac=qh facet_list;
380  while(curFac != qh facet_tail)
381  {
382  float temp;
383  Point3D vertexPt[3];
384 
385  //The shortest distance from the plane to the point
386  //is the dot product of the UNIT normal with
387  //A-B, where B is on plane, A is point in question
388  for(unsigned int ui=0; ui<3; ui++)
389  {
390  vertexT *vertex;
391  //grab vertex
392  vertex = ((vertexT *)curFac->vertices->e[ui].p);
393  vertexPt[ui] = Point3D(vertex->point[0],vertex->point[1],vertex->point[2]);
394  }
395 
396  //Some facets can be degenerate. Skip if this occurs
397  //--
398  bool isBad;
399  isBad=triIsDegenerate(vertexPt[0],vertexPt[1],vertexPt[2]);
400 
401  if(isBad)
402  {
403  badFacetCount++;
404  curFac=curFac->next;
405  continue;
406  }
407  //--
408 
409 
410 
411  //Find the distance between hullcentroid and a given facet
412  temp = distanceToFacet(vertexPt[0],vertexPt[1],vertexPt[2],
413  Point3D(curFac->normal[0],curFac->normal[1],curFac->normal[2]),hullCentroid);
414 
415  if(temp < minDist)
416  minDist = temp;
417 
418  curFac=curFac->next;
419  }
420 
421  if(badFacetCount)
422  {
423  if(badFacetCount == qh num_facets)
424  {
426  }
427  }
428 
429  //shrink the convex hull such that it lies at
430  //least reductionDim from the original surface of
431  //the convex hull
432  float scaleFactor;
433  scaleFactor = 1 - reductionDim/ minDist;
434 
435  if(scaleFactor < 0.0f)
437 
438 
439  //now scan through the input points and see if they
440  //lie in the reduced convex hull
441  vertexT *vertex = qh vertex_list;
442 
443  unsigned int ui=0;
444  while(vertex !=qh vertex_tail)
445  {
446  //Translate around hullCentroid before scaling,
447  //then undo translation after scale
448  //Modify the vertex data such that it is scaled around the hullCentroid
449  vertex->point[0] = (vertex->point[0] - hullCentroid[0])*scaleFactor + hullCentroid[0];
450  vertex->point[1] = (vertex->point[1] - hullCentroid[1])*scaleFactor + hullCentroid[1];
451  vertex->point[2] = (vertex->point[2] - hullCentroid[2])*scaleFactor + hullCentroid[2];
452 
453  vertex = vertex->next;
454  ui++;
455  }
456 
457  //if the dot product of the normal with the point vector of the
458  //considered point P, to any vertex on all of the facets of the
459  //convex hull F1, F2, ... , Fn is negative,
460  //then P does NOT lie inside the convex hull.
461  pointResult.reserve(points.size()/2);
462  curFac = qh facet_list;
463 
464  //minimum distance from centroid to convex hull
465  for(size_t uj=points.size(); uj--;)
466  {
467  float fX,fY,fZ;
468  double *ptArr,*normalArr;
469  fX =points[uj][0];
470  fY = points[uj][1];
471  fZ = points[uj][2];
472 
473  //loop through the facets
474  curFac = qh facet_list;
475  while(curFac != qh facet_tail)
476  {
477  //Dont ask. It just grabs the first coords of the vertex
478  //associated with this facet
479  ptArr = ((vertexT *)curFac->vertices->e[0].p)->point;
480 
481  normalArr = curFac->normal;
482  //if the dotproduct is negative, then the point vector from the
483  //point in question to the surface is in opposite to the outwards facing
484  //normal, which means the point lies outside the hull
485  if (dotProduct( (float)ptArr[0] - fX,
486  (float)ptArr[1] - fY,
487  (float)ptArr[2] - fZ,
488  normalArr[0], normalArr[1],
489  normalArr[2]) >= 0)
490  {
491  curFac=curFac->next;
492  continue;
493  }
494  goto reduced_loop_next;
495  }
496  //we passed all tests, point is inside convex hull
497  pointResult.push_back(points[uj]);
498 
499 reduced_loop_next:
500  ;
501  }
502 
503  freeConvexHull();
504 
505  return 0;
506 }
507 
508 #ifdef DEBUG
509 
510 bool dummyCallback(bool b)
511 {
512  return true;
513 }
514 #include <iostream>
515 #include <sys/time.h>
516 using std::cerr;
517 using std::endl;
518 bool testConvexHull()
519 {
520  std::vector<IonHit> ions;
521 
522  unsigned int NUM_IONS=100000;
523  const float SCALE=100;
524  //initialise random number generator using
525  // current system time
526  srand(time(NULL));
527  ions.resize(NUM_IONS);
528  for(unsigned int ui=0;ui<NUM_IONS;ui++)
529  {
530 
531  float xyzm[4];
532  xyzm[0] = SCALE*((float)rand()/RAND_MAX-0.5);
533  xyzm[1] = SCALE*((float)rand()/RAND_MAX-0.5);
534  xyzm[2] = SCALE*((float)rand()/RAND_MAX-0.5);
535  xyzm[3] = SCALE*((float)rand()/RAND_MAX-0.5);
536  ions[ui].setHit(xyzm);
537  }
538 
539 
540  vector<Point3D> hullVertices;
541 
542  unsigned int dummy=0;
543  //Now compute the convex hull
544  if(computeConvexHull(ions,&dummy,dummyCallback,hullVertices,true))
545  return false;
546 
547  ions.resize(NUM_IONS);
548  for(unsigned int ui=0;ui<NUM_IONS;ui++)
549  {
550 
551  float xyzm[4];
552  xyzm[0] = SCALE*((float)rand()/RAND_MAX-0.5);
553  xyzm[1] = SCALE*((float)rand()/RAND_MAX-0.5);
554  xyzm[2] = SCALE*((float)rand()/RAND_MAX-0.5);
555  xyzm[3] = SCALE*((float)rand()/RAND_MAX-0.5);
556  ions[ui].setHit(xyzm);
557  }
558 
559  vector<IonHit> reducedHullPts;
560  if(GetReducedHullPts(ions,SCALE*0.1,&dummy,dummyCallback, reducedHullPts))
561  return false;
562 
563  if(reducedHullPts.empty())
564  return false;
565 
566 
567  return true;
568 }
569 #endif
570 
571 }
float sqrDist(const Point3D &pt) const
Returns the square of distance to another Point3D.
Definition: point3D.cpp:272
const unsigned int NUM_IONS
Definition: KDTest.cpp:26
double pyramidVol(const Point3D *planarPts, const Point3D &apex)
Definition: misc.cpp:290
bool callback()
Definition: kd-example.cpp:37
void freeConvexHull()
Definition: convexHull.cpp:47
A 3D point data class storage.
Definition: point3D.h:39
unsigned int doHull(unsigned int bufferSize, double *buffer, vector< Point3D > &resHull, Point3D &midPoint, bool freeHullOnExit)
Definition: convexHull.cpp:53
const float SCALE
Definition: KDTest.cpp:27
bool qhullInited
Definition: convexHull.cpp:39
const size_t PROGRESS_REDUCE
Definition: mesh.cpp:61
const unsigned int HULL_GRAB_SIZE
Definition: convexHull.cpp:42
float dotProduct(float a1, float a2, float a3, float b1, float b2, float b3)
Inline func for calculating a(dot)b.
Definition: misc.h:128
#define ASSERT(f)
unsigned int GetReducedHullPts(const std::vector< IonHit > &points, float reductionDim, unsigned int *progress, bool(*callback)(bool), std::vector< IonHit > &pointResult)
Obtain a set of points that are a subset of points the convex hull that are at least "reductionDim" a...
Definition: convexHull.cpp:289
bool triIsDegenerate(const Point3D &fA, const Point3D &fB, const Point3D &fC)
Definition: misc.cpp:266
unsigned int progress
Definition: kd-example.cpp:26
unsigned int computeConvexHull(const std::vector< IonHit > &data, unsigned int *progress, bool(*callback)(bool), std::vector< Point3D > &curHull, bool freeHull)
Obtain the convex hull of a set of ions.
Definition: convexHull.cpp:149
float distanceToFacet(const Point3D &fA, const Point3D &fB, const Point3D &fC, const Point3D &normal, const Point3D &p)
Definition: misc.cpp:250