ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
kdTreeForFacetExtraction.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 // CV_CORE_LIB
12 #include <ParallelSort.h>
13 
14 // CV_DB_LIB
15 #include <ecvPointCloud.h>
16 
17 // Qt
18 #include <QApplication>
19 
20 // static bool AscendingLeafErrorComparison(const ccKdTree::Leaf* a, const
21 // ccKdTree::Leaf* b)
22 //{
23 // return a->error < b->error;
24 // }
25 
27  const ccKdTree::Leaf* b) {
28  return a->points->size() > b->points->size();
29 }
30 
31 struct Candidate {
36 
37  Candidate() : leaf(0), dist(PC_NAN), radius(0) {}
39  if (leaf && leaf->points) {
43  }
44  }
45 };
46 
48  const Candidate& b) {
49  return a.dist < b.dist;
50 }
51 
53  ccKdTree* kdTree,
54  double maxError,
56  double maxAngle_deg,
57  PointCoordinateType overlapCoef /*=1*/,
58  bool closestFirst /*=true*/,
59  cloudViewer::GenericProgressCallback* progressCb /*=0*/) {
60  if (!kdTree) return false;
61 
62  ccGenericPointCloud* associatedGenericCloud =
63  kdTree->associatedGenericCloud();
64  if (!associatedGenericCloud ||
65  !associatedGenericCloud->isA(CV_TYPES::POINT_CLOUD) || maxError < 0.0)
66  return false;
67 
68  // get leaves
69  std::vector<ccKdTree::Leaf*> leaves;
70  if (!kdTree->getLeaves(leaves) || leaves.empty()) return false;
71 
72  // progress notification
74  progressCb, static_cast<unsigned>(leaves.size()));
75  if (progressCb) {
76  progressCb->update(0);
77  if (progressCb->textCanBeEdited()) {
78  progressCb->setMethodTitle("Fuse Kd-tree cells");
79  progressCb->setInfo(qPrintable(QString("Cells: %1\nMax error: %2")
80  .arg(leaves.size())
81  .arg(maxError)));
82  }
83  progressCb->start();
84  }
85 
86  ccPointCloud* pc = static_cast<ccPointCloud*>(associatedGenericCloud);
87 
88  // sort cells based on their population size (we start by the biggest ones)
89  ParallelSort(leaves.begin(), leaves.end(), DescendingLeafSizeComparison);
90 
91  // set all 'userData' to -1 (i.e. unfused cells)
92  {
93  for (size_t i = 0; i < leaves.size(); ++i) {
94  leaves[i]->userData = -1;
95  // check by the way that the plane normal is unit!
96  assert(static_cast<double>(
97  fabs(CCVector3(leaves[i]->planeEq).norm2()) - 1.0) <
98  1.0e-6);
99  }
100  }
101 
102  // cosine of the max angle between fused 'planes'
103  const double c_minCosNormAngle =
104  cos(cloudViewer::DegreesToRadians(maxAngle_deg));
105 
106  // fuse all cells, starting from the ones with the best error
107  const int unvisitedNeighborValue = -1;
108  bool cancelled = false;
109  int macroIndex = 1; // starts at 1 (0 is reserved for cells already above
110  // the max error)
111  {
112  for (size_t i = 0; i < leaves.size(); ++i) {
113  ccKdTree::Leaf* currentCell = leaves[i];
114  if (currentCell->error >= maxError)
115  currentCell->userData =
116  0; // 0 = special group for cells already above the
117  // user defined threshold!
118 
119  // already fused?
120  if (currentCell->userData != -1) {
121  if (progressCb &&
122  !nProgress.oneStep()) // process canceled by user
123  {
124  cancelled = true;
125  break;
126  }
127  continue;
128  }
129 
130  // we create a new "macro cell" index
131  currentCell->userData = macroIndex++;
132 
133  // we init the current set of 'fused' points with the cell's points
134  cloudViewer::ReferenceCloud* currentPointSet = currentCell->points;
135  // get current fused set centroid and normal
136  CCVector3 currentCentroid =
137  *cloudViewer::Neighbourhood(currentPointSet)
138  .getGravityCenter();
139  CCVector3 currentNormal(currentCell->planeEq);
140 
141  // visited neighbors
142  ccKdTree::LeafSet visitedNeighbors;
143  // set of candidates
144  std::list<Candidate> candidates;
145 
146  // we are going to iteratively look for neighbor cells that could be
147  // fused to this one
148  ccKdTree::LeafVector cellsToTest;
149  cellsToTest.push_back(currentCell);
150 
151  if (progressCb && !nProgress.oneStep()) // process canceled by user
152  {
153  cancelled = true;
154  break;
155  }
156 
157  while (!cellsToTest.empty() || !candidates.empty()) {
158  // get all neighbors around the 'waiting' cell(s)
159  if (!cellsToTest.empty()) {
160  ccKdTree::LeafSet neighbors;
161  while (!cellsToTest.empty()) {
162  if (!kdTree->getNeighborLeaves(
163  cellsToTest.back(), neighbors,
164  &unvisitedNeighborValue)) // we only
165  // consider
166  // unvisited
167  // cells!
168  {
169  // an error occurred
170  return false;
171  }
172  cellsToTest.pop_back();
173  }
174 
175  // add those (new) neighbors to the 'visitedNeighbors' set
176  // and to the candidates set by the way if they are not yet
177  // there
178  for (ccKdTree::LeafSet::iterator it = neighbors.begin();
179  it != neighbors.end(); ++it) {
180  ccKdTree::Leaf* neighbor = *it;
181  std::pair<ccKdTree::LeafSet::iterator, bool> ret =
182  visitedNeighbors.insert(neighbor);
183  // neighbour not already in the set?
184  if (ret.second) {
185  // we create the corresponding candidate
186  try {
187  candidates.push_back(Candidate(neighbor));
188  } catch (const std::bad_alloc&) {
189  // not enough memory!
191  "[ccKdTreeForFacetExtraction] Not "
192  "enough memory!");
193  return false;
194  }
195  }
196  }
197  }
198 
199  // is there remaining candidates?
200  if (!candidates.empty()) {
201  // update the set of candidates
202  if (closestFirst && candidates.size() > 1) {
203  for (std::list<Candidate>::iterator it =
204  candidates.begin();
205  it != candidates.end(); ++it)
206  it->dist = (it->centroid - currentCentroid).norm2();
207 
208  // sort candidates by their distance
209  candidates.sort(CandidateDistAscendingComparison);
210  }
211 
212  // we will keep track of the best fused 'couple' at each
213  // pass
214  std::list<Candidate>::iterator bestIt = candidates.end();
215  cloudViewer::ReferenceCloud* bestFused = 0;
216  CCVector3 bestNormal(0, 0, 0);
217  double bestError = -1.0;
218 
219  unsigned skipCount = 0;
220  for (std::list<Candidate>::iterator it = candidates.begin();
221  it != candidates.end();
222  /*++it*/) {
223  assert(it->leaf && it->leaf->points);
224  assert(currentPointSet->getAssociatedCloud() ==
225  it->leaf->points->getAssociatedCloud());
226 
227  // if the leaf orientation is too different
228  if (fabs(CCVector3(it->leaf->planeEq)
229  .dot(currentNormal)) <
230  c_minCosNormAngle) {
231  it = candidates.erase(it);
232  //++it;
233  //++skipCount;
234  continue;
235  }
236 
237  // compute the minimum distance between the candidate
238  // centroid and the 'currentPointSet'
239  PointCoordinateType minDistToMainSet = 0.0;
240  {
241  for (unsigned j = 0; j < currentPointSet->size();
242  ++j) {
243  const CCVector3* P =
244  currentPointSet->getPoint(j);
246  (*P - it->centroid).norm2();
247  if (d2 < minDistToMainSet || j == 0)
248  minDistToMainSet = d2;
249  }
250  minDistToMainSet = sqrt(minDistToMainSet);
251  }
252 
253  // if the leaf is too far
254  if (it->radius < minDistToMainSet / overlapCoef) {
255  ++it;
256  ++skipCount;
257  continue;
258  }
259 
260  // fuse the main set with the current candidate
263  *currentPointSet);
264  if (!fused->add(*(it->leaf->points))) {
265  // not enough memory!
267  "[ccKdTreeForFacetExtraction] Not enough "
268  "memory!");
269  delete fused;
270  if (currentPointSet != currentCell->points)
271  delete currentPointSet;
272  return false;
273  }
274 
275  // fit a plane and estimate the resulting error
276  double error = -1.0;
277  const PointCoordinateType* planeEquation =
279  if (planeEquation)
282  fused, planeEquation, errorMeasure);
283 
284  if (error < 0.0 || error > maxError) {
285  // candidate is rejected
286  it = candidates.erase(it);
287  } else {
288  // otherwise we keep track of the best one!
289  if (bestError < 0.0 || error < bestError) {
290  bestIt = it;
291  bestError = error;
292  if (bestFused) delete bestFused;
293  bestFused = fused;
294  bestNormal = CCVector3(planeEquation);
295  fused = 0;
296 
297  if (closestFirst)
298  break; // if we have found a good
299  // candidate, we stop here (closest
300  // first ;)
301  }
302  ++it;
303  }
304 
305  if (fused) {
306  delete fused;
307  fused = 0;
308  }
309  }
310 
311  // we have a (best) candidate for this pass?
312  if (bestIt != candidates.end()) {
313  assert(bestFused && bestError >= 0.0);
314  if (currentPointSet != currentCell->points)
315  delete currentPointSet;
316  currentPointSet = bestFused;
317  {
318  // update infos
319  cloudViewer::Neighbourhood N(currentPointSet);
320  // currentCentroid = *N.getGravityCenter(); //if we
321  // update it, the search will naturally shift along
322  // one dimension! currentNormal = bestNormal; //same
323  // thing here for normals
324  }
325 
326  bestIt->leaf->userData = currentCell->userData;
327  // bestIt->leaf->userData = macroIndex++; //FIXME TEST
328 
329  // we will test this cell's neighbors as well
330  cellsToTest.push_back(bestIt->leaf);
331 
332  if (progressCb &&
333  !nProgress.oneStep()) // process canceled by user
334  {
335  // premature end!
336  candidates.clear();
337  cellsToTest.clear();
338  cancelled = true;
339  break;
340  }
341  QApplication::processEvents();
342 
343  // we also remove it from the candidates list
344  candidates.erase(bestIt);
345  }
346 
347  if (skipCount == candidates.size() && cellsToTest.empty()) {
348  // only far leaves remain...
349  candidates.clear();
350  }
351  }
352 
353  } // no more candidates or cells to test
354 
355  // end of the fusion process for the current leaf
356  if (currentPointSet != currentCell->points) delete currentPointSet;
357  currentPointSet = 0;
358 
359  if (cancelled) break;
360  }
361  }
362 
363  // convert fused indexes to SF
364  if (!cancelled) {
365  pc->enableScalarField();
366 
367  for (size_t i = 0; i < leaves.size(); ++i) {
368  cloudViewer::ReferenceCloud* subset = leaves[i]->points;
369  if (subset) {
370  ScalarType scalar = (ScalarType)leaves[i]->userData;
371  if (leaves[i]->userData <=
372  0) // for unfused cells, we create new individual groups
373  scalar = static_cast<ScalarType>(macroIndex++);
374  // scalar = NAN_VALUE; //FIXME TEST
375  for (unsigned j = 0; j < subset->size(); ++j)
376  subset->setPointScalarValue(j, scalar);
377  }
378  }
379 
380  // pc->setCurrentDisplayedScalarField(sfIdx);
381  }
382 
383  return !cancelled;
384 }
constexpr PointCoordinateType PC_NAN
'NaN' as a PointCoordinateType value
Definition: CVConst.h:71
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
#define ParallelSort
Definition: ParallelSort.h:33
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
A 3D cloud interface with associated features (color, normals, octree, etc.)
static bool FuseCells(ccKdTree *kdTree, double maxError, cloudViewer::DistanceComputationTools::ERROR_MEASURES errorMeasure, double maxAngle_deg, PointCoordinateType overlapCoef=1, bool closestFirst=true, cloudViewer::GenericProgressCallback *progressCb=0)
Fuses cells.
KD-tree structure.
Definition: ecvKdTree.h:25
bool getNeighborLeaves(BaseNode *cell, ccKdTree::LeafSet &neighbors, const int *userDataFilter=0)
Returns the neighbor leaves around a given cell.
std::unordered_set< Leaf * > LeafSet
A set of leaves.
Definition: ecvKdTree.h:64
ccGenericPointCloud * associatedGenericCloud() const
Returns associated (generic) point cloud.
Definition: ecvKdTree.h:72
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
static ScalarType ComputeCloud2PlaneDistance(cloudViewer::GenericCloud *cloud, const PointCoordinateType *planeEquation, ERROR_MEASURES measureType)
virtual void setInfo(const char *infoStr)=0
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle)=0
Notifies the algorithm title.
virtual bool textCanBeEdited() const
Returns whether the dialog title and info can be updated or not.
virtual void update(float percent)=0
Notifies the algorithm progress.
PointCoordinateType computeLargestRadius()
const PointCoordinateType * getLSPlane()
Returns best interpolating plane equation (Least-square)
const CCVector3 * getGravityCenter()
Returns gravity center.
bool oneStep()
Increments total progress value of a single unit.
bool enableScalarField() override
Definition: PointCloudTpl.h:77
A very simple point cloud (no point duplication)
void setPointScalarValue(unsigned pointIndex, ScalarType value) override
Sets the ith point associated scalar value.
virtual GenericIndexedCloudPersist * getAssociatedCloud()
Returns the associated (source) cloud.
unsigned size() const override
Returns the number of points.
bool add(const ReferenceCloud &cloud)
Add another reference cloud.
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
ReferenceCloud * points
Definition: TrueKdTree.h:83
PointCoordinateType planeEq[4]
Definition: TrueKdTree.h:84
bool getLeaves(LeafVector &leaves) const
Returns all leaf nodes.
Definition: TrueKdTree.cpp:321
std::vector< Leaf * > LeafVector
A vector of leaves.
Definition: TrueKdTree.h:105
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
static bool CandidateDistAscendingComparison(const Candidate &a, const Candidate &b)
static bool DescendingLeafSizeComparison(const ccKdTree::Leaf *a, const ccKdTree::Leaf *b)
static void error(char *msg)
Definition: lsd.c:159
@ POINT_CLOUD
Definition: CVTypes.h:104
float DegreesToRadians(int degrees)
Convert degrees to radians.
Definition: CVMath.h:98
cloudViewer::NormalizedProgress * nProgress
PointCoordinateType radius
Candidate(ccKdTree::Leaf *l)
PointCoordinateType dist
ccKdTree::Leaf * leaf