ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qM3C2Tools.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 
8 #include "qM3C2Tools.h"
9 
10 // CV_CORE_LIB
12 #include <Jacobi.h>
13 #include <Neighbourhood.h>
14 
15 // CV_DB_LIB
16 #include <ecvAdvancedTypes.h>
17 #include <ecvGenericPointCloud.h>
18 #include <ecvMainAppInterface.h>
19 #include <ecvNormalVectors.h>
20 #include <ecvOctree.h>
21 #include <ecvOctreeProxy.h>
22 #include <ecvPointCloud.h>
23 #include <ecvProgressDialog.h>
24 #include <ecvScalarField.h>
25 
26 // Qt
27 #include <QApplication>
28 #include <QMainWindow>
29 #include <QProgressDialog>
30 #include <QtConcurrentMap>
31 #include <QtCore>
32 
33 // system
34 #include <vector>
35 
36 // ComputeCorePointNormal parameters
37 static struct {
41  unsigned char octreeLevel;
42  std::vector<PointCoordinateType> radii;
46 
49 
51 
52 void ComputeCorePointNormal(unsigned index) {
53  if (s_corePointsNormalsParams.processCanceled) return;
54 
55  CCVector3 bestNormal(0, 0, 0);
56  ScalarType bestScale = NAN_VALUE;
57 
58  const CCVector3* P = s_corePointsNormalsParams.corePoints->getPoint(index);
61 
62  int n = s_corePointsNormalsParams.octree->getPointsInSphericalNeighbourhood(
63  *P,
65  .back(), // we use the biggest neighborhood
66  neighbours, s_corePointsNormalsParams.octreeLevel);
67 
68  // if the widest neighborhood has less than 3 points in it, there's nothing
69  // we can do for this core point!
70  if (n >= 3) {
71  size_t radiiCount = s_corePointsNormalsParams.radii.size();
72 
73  double bestPlanarityCriterion = 0;
74  unsigned bestSamplePointCount = 0;
75 
76  for (size_t i = 0; i < radiiCount; ++i) {
77  double radius = s_corePointsNormalsParams
78  .radii[radiiCount - 1 -
79  i]; // we start from the biggest
80  double squareRadius = radius * radius;
81 
82  subset.clear(false);
83  for (unsigned j = 0; j < static_cast<unsigned>(n); ++j)
84  if (neighbours[j].squareDistd <= squareRadius)
85  subset.addPointIndex(neighbours[j].pointIndex);
86 
87  // as we start from the biggest neighborhood, if we have less than 3
88  // points for the current radius it won't be better for the next
89  // one(s)!
90  if (subset.size() < 3) break;
91  // see the 'M3C2' article:
92  //<< we ensure that a minimum of 10 points is used to compute the
93  // normal at Dopt otherwise we choose the scale immediatly larger
94  // that fulfils this requirement >> if (subset.size() < 10) //DGM ->
95  // not implemented in Brodu's code?! break;
96 
97  cloudViewer::Neighbourhood Z(&subset);
98 
99  /*** we manually compute the least squares best fitting plane (so as
100  * to get the PCA eigen values) ***/
101 
102  // we determine the plane normal by computing the smallest eigen
103  // value of M = 1/n * S[(p-?*(p-?']
104  cloudViewer::SquareMatrixd eigVectors;
105  std::vector<double> eigValues;
107  Z.computeCovarianceMatrix(), eigVectors, eigValues,
108  true)) {
109  /*** code and comments below are from the original 'm3c2' code
110  * (N. Brodu) ***/
111 
112  // The most 2D scale. For the criterion for how "2D" a scale is,
113  // see canupo Ideally first and second eigenvalue are equal
114  // convert to percent variance explained by each dim
115  double totalvar = 0;
116  CCVector3d svalues;
117  for (unsigned k = 0; k < 3; ++k) {
118  // singular values are squared roots of eigenvalues
119  svalues.u[k] = eigValues[k];
120  svalues.u[k] = svalues.u[k] * svalues.u[k];
121  totalvar += svalues.u[k];
122  }
123  svalues /= totalvar;
124  // sort eigenvalues
125  std::sort(svalues.u, svalues.u + 3);
126  std::swap(svalues.x, svalues.z);
127 
128  // ideally, 2D means first and second entries are both 1/2 and
129  // third is 0 convert to barycentric coordinates and take the
130  // coefficient of the 2D corner as a quality measure. Use
131  // barycentric coordinates : a for 1D, b for 2D and c for 3D
132  // Formula on wikipedia page for barycentric coordinates
133  // using directly the triangle in %variance space, they simplify
134  // a lot double a = svalues[0] - svalues[1];
135  double b = 2 * svalues[0] + 4 * svalues[1] - 2;
136  // double c = 1 - a - b; // they sum to 1
137  if (bestSamplePointCount == 0 || b > bestPlanarityCriterion) {
138  bestPlanarityCriterion = b;
139  bestSamplePointCount = subset.size();
140 
141  // the smallest eigen vector corresponds to the "least
142  // square best fitting plane" normal
143  double vec[3];
144  double minEigValue = 0;
146  eigVectors, eigValues, minEigValue, vec);
147 
149  N.normalize();
150 
151  bestNormal = N;
152  bestScale = static_cast<ScalarType>(radius * 2);
153  }
154  }
155  }
156 
157  if (bestSamplePointCount < 3) {
158  s_corePointsNormalsParams.invalidNormals = true;
159  }
160  } else {
161  s_corePointsNormalsParams.invalidNormals = true;
162  }
163 
164  // compress the best normal and store it
165  CompressedNormType normCode = ccNormalVectors::GetNormIndex(bestNormal.u);
166  s_corePointsNormalsParams.normCodes->setValue(index, normCode);
167 
168  // if necessary, store 'best radius'
169  if (s_corePointsNormalsParams.normalScale)
170  s_corePointsNormalsParams.normalScale->setValue(index, bestScale);
171 
172  // progress notification
173  if (s_corePointsNormalsParams.nProgress &&
174  !s_corePointsNormalsParams.nProgress->oneStep()) {
175  s_corePointsNormalsParams.processCanceled = true;
176  }
177 }
178 
181  NormsIndexesTableType* corePointsNormals,
183  const std::vector<PointCoordinateType>& sortedRadii,
184  bool& invalidNormals,
185  int maxThreadCount /*=0*/,
186  ccScalarField* normalScale /*=0*/,
187  cloudViewer::GenericProgressCallback* progressCb /*=0*/,
188  cloudViewer::DgmOctree* inputOctree /*=0*/) {
189  assert(corePoints && sourceCloud && corePointsNormals);
190  assert(!sortedRadii.empty());
191 
192  invalidNormals = true;
193 
194  unsigned corePtsCount = corePoints->size();
195  if (corePtsCount == 0) return false;
196 
197  if (normalScale) {
198  if (normalScale->currentSize() != corePtsCount &&
199  !normalScale->resizeSafe(corePtsCount)) {
200  // not enough memory
201  return false;
202  }
204  }
205 
206  cloudViewer::DgmOctree* theOctree = inputOctree;
207  if (!theOctree) {
208  theOctree = new cloudViewer::DgmOctree(sourceCloud);
209  if (theOctree->build() == 0) {
210  delete theOctree;
211  return false;
212  }
213  }
214 
215  cloudViewer::NormalizedProgress nProgress(progressCb, corePtsCount);
216  if (progressCb) {
217  if (progressCb->textCanBeEdited()) {
218  progressCb->setInfo(
219  qPrintable(QString("Core points: %1\nSource points: %2")
220  .arg(corePtsCount)
221  .arg(sourceCloud->size())));
222  progressCb->setMethodTitle("Computing normals");
223  }
224  progressCb->start();
225  }
226 
227  // reserve memory for normals (codes) storage
228  if (!corePointsNormals->isAllocated() ||
229  corePointsNormals->currentSize() < corePtsCount) {
230  if (!corePointsNormals->resizeSafe(corePtsCount)) {
231  if (!inputOctree) delete theOctree;
232  return false;
233  }
234  }
235  PointCoordinateType biggestRadius =
236  sortedRadii.back(); // we extract the biggest neighborhood
237  unsigned char octreeLevel =
239  biggestRadius);
240 
242  s_corePointsNormalsParams.normCodes = corePointsNormals;
244  s_corePointsNormalsParams.radii = sortedRadii;
245  s_corePointsNormalsParams.octree = theOctree;
247  s_corePointsNormalsParams.nProgress = progressCb ? &nProgress : 0;
248  s_corePointsNormalsParams.processCanceled = false;
249  s_corePointsNormalsParams.invalidNormals = false;
251 
252  // we try the parallel way (if we have enough memory)
253  bool useParallelStrategy = true;
254 #ifdef _DEBUG
255  useParallelStrategy = false;
256 #endif
257 
258  std::vector<unsigned> corePointsIndexes;
259  if (useParallelStrategy) {
260  try {
261  corePointsIndexes.resize(corePtsCount);
262  } catch (const std::bad_alloc&) {
263  // not enough memory
264  useParallelStrategy = false;
265  }
266  }
267 
268  if (useParallelStrategy) {
269  for (unsigned i = 0; i < corePtsCount; ++i) {
270  corePointsIndexes[i] = i;
271  }
272 
273  if (maxThreadCount == 0) {
274  maxThreadCount = QThread::idealThreadCount();
275  }
276  QThreadPool::globalInstance()->setMaxThreadCount(maxThreadCount);
277  QtConcurrent::blockingMap(corePointsIndexes, ComputeCorePointNormal);
278  } else {
279  // manually call the static per-point method!
280  for (unsigned i = 0; i < corePtsCount; ++i) {
282  }
283  }
284 
285  // output flags
286  bool wasCanceled = s_corePointsNormalsParams.processCanceled;
287  invalidNormals = s_corePointsNormalsParams.invalidNormals;
288 
289  if (progressCb) {
290  progressCb->stop();
291  }
292 
293  if (!inputOctree) delete theOctree;
294 
295  return !wasCanceled;
296 }
297 
298 static struct {
302 
304  bool processCanceled;
305 
307 
308 static void OrientPointNormalWithCloud(unsigned index) {
309  if (s_normOriWithCloud.processCanceled) return;
310 
311  const CompressedNormType& nCode =
312  s_normOriWithCloud.normsCodes->getValue(index);
314 
315  // corresponding point
316  const CCVector3* P = s_normOriWithCloud.normCloud->getPoint(index);
317 
318  // find nearest point in 'orientation cloud'
319  //(brute force: we don't expect much points!)
320  CCVector3 orientation(0, 0, 1);
321  PointCoordinateType minSquareDist = 0;
322  for (unsigned j = 0; j < s_normOriWithCloud.orientationCloud->size(); ++j) {
323  const CCVector3* Q = s_normOriWithCloud.orientationCloud->getPoint(j);
324  CCVector3 PQ = (*Q - *P);
325  PointCoordinateType squareDist = PQ.norm2();
326  if (j == 0 || squareDist < minSquareDist) {
327  orientation = PQ;
328  minSquareDist = squareDist;
329  }
330  }
331 
332  // we check sign
333  if (N.dot(orientation) < 0) {
334  // inverse normal and re-compress it
335  N *= -1;
336  s_normOriWithCloud.normsCodes->setValue(
337  index, ccNormalVectors::GetNormIndex(N.u));
338  }
339 
340  if (s_normOriWithCloud.nProgress &&
341  !s_normOriWithCloud.nProgress->oneStep()) {
342  s_normOriWithCloud.processCanceled = true;
343  }
344 }
345 
350  int maxThreadCount /*=0*/,
351  cloudViewer::GenericProgressCallback* progressCb /*=nullptr*/) {
352  // input normals
353  unsigned count = normsCodes.currentSize();
354  if (!normCloud || normCloud->size() != count) {
355  assert(false);
357  "[qM3C2Tools::UpdateNormalOrientationsWithCloud] Cloud/normals "
358  "set mismatch!");
359  return false;
360  }
361 
362  // orientation points
363  unsigned orientationCount = orientationCloud ? orientationCloud->size() : 0;
364  if (orientationCount == 0) {
365  // nothing to do?
366  assert(false);
367  return true;
368  }
369 
371  if (progressCb) {
372  if (progressCb->textCanBeEdited()) {
373  progressCb->setInfo(
374  qPrintable(QString("Normals: %1\nOrientation points: %2")
375  .arg(count)
376  .arg(orientationCloud->size())));
377  progressCb->setMethodTitle("Orienting normals");
378  }
379  progressCb->start();
380  }
381 
382  s_normOriWithCloud.normCloud = normCloud;
383  s_normOriWithCloud.orientationCloud = orientationCloud;
384  s_normOriWithCloud.normsCodes = &normsCodes;
385  s_normOriWithCloud.nProgress = &nProgress;
386  s_normOriWithCloud.processCanceled = false;
387 
388  // we check each normal's orientation
389  {
390  std::vector<unsigned> pointIndexes;
391  bool useParallelStrategy = true;
392 #ifdef _DEBUG
393  useParallelStrategy = false;
394 #endif
395  if (useParallelStrategy) {
396  try {
397  pointIndexes.resize(count);
398  } catch (const std::bad_alloc&) {
399  // not enough memory
400  useParallelStrategy = false;
401  }
402  }
403 
404  if (useParallelStrategy) {
405  for (unsigned i = 0; i < count; ++i) {
406  pointIndexes[i] = i;
407  }
408 
409  if (maxThreadCount == 0) {
410  maxThreadCount = QThread::idealThreadCount();
411  }
412  QThreadPool::globalInstance()->setMaxThreadCount(maxThreadCount);
413  QtConcurrent::blockingMap(pointIndexes, OrientPointNormalWithCloud);
414  } else {
415  // manually call the static per-point method!
416  for (unsigned i = 0; i < count; ++i) {
418  }
419  }
420  }
421 
422  if (progressCb) {
423  progressCb->stop();
424  }
425 
426  return true;
427 }
428 
430  // for each normal
431  unsigned count = normsCodes.currentSize();
432  for (unsigned i = 0; i < count; i++) {
433  const CompressedNormType& nCode = normsCodes.getValue(i);
434 
435  // de-compress the normal
437 
438  N.z = 0;
439  N.normalize();
440 
441  // re-compress the normal
443  }
444 }
445 
447 
451  size_t begin = 0,
452  size_t count = 0) {
453  if (count == 0) {
454  count = set.size();
455  if (count == 0) return NAN_VALUE;
456  }
457 
458  size_t nd2 = count / 2;
459  double midValue = set[begin + nd2].squareDistd;
460 
461  if ((count & 1) == 0) // even case
462  {
463  midValue = (midValue + set[begin + nd2 - 1].squareDistd) / 2;
464  }
465 
466  return midValue;
467 }
468 
470 
475  if (set.empty()) return NAN_VALUE;
476 
477  size_t num = set.size();
478  size_t num_pts_each_half = (num + 1) / 2;
479  size_t offset_second_half = num / 2;
480 
481  double q1 = Median(set, 0, num_pts_each_half);
482  double q3 = Median(set, offset_second_half, num_pts_each_half);
483 
484  return q3 - q1;
485 }
486 
488  bool useMedian,
489  double& meanOrMedian,
490  double& stdDevOrIQR) {
491  size_t count = set.size();
492  if (count == 0) {
493  meanOrMedian = NAN_VALUE;
494  stdDevOrIQR = 0;
495  return;
496  } else if (count == 1) {
497  meanOrMedian = set.back().squareDistd;
498  stdDevOrIQR = 0;
499  return;
500  }
501 
502  if (useMedian) {
503  // sort neighbors by distance
504  std::sort(set.begin(), set.end(),
506 
507  meanOrMedian = Median(set);
508  stdDevOrIQR = Interquartile(set);
509  } else {
510  // otherwise we proceed with the 'standard' mean
511  double sum = 0;
512  double sum2 = 0;
513  for (size_t i = 0; i < count; ++i) {
514  const ScalarType& dist = set[i].squareDistd;
515  sum += static_cast<double>(
516  dist); // should be the projected dist in fact!
517  sum2 += static_cast<double>(dist) * dist;
518  }
519 
520  assert(count > 1);
521  sum /= count;
522  sum2 = sqrt(fabs(sum2 / count - sum * sum));
523 
524  meanOrMedian = static_cast<ScalarType>(sum);
525  stdDevOrIQR = static_cast<ScalarType>(sum2);
526  }
527 }
528 
530  ccPointCloud* cloud2,
531  unsigned minPoints4Stats,
533  bool fastMode,
534  ecvMainAppInterface* app /*=nullptr*/,
535  unsigned probingCount /*=1000*/) {
536  // invalid parameters?
537  if (!cloud1 || !cloud2 || minPoints4Stats == 0) return false;
538 
539  // no need to bother for very small clouds
540  unsigned count1 = cloud1->size();
541  if (count1 < 50) {
542  params.projDepth = params.normScale = params.projScale =
543  cloud1->getOwnBB().getDiagNorm() / 2;
544  return true;
545  }
546 
547  // first we can do a very simple guess for normal and projection scales
548  // (default value)
549  {
550  double d1 = cloud1->getOwnBB().getDiagNormd();
551  double d2 = cloud2->getOwnBB().getDiagNormd();
552  double baseRadius = std::min(d1, d2) * 0.01;
553  params.normScale = params.projScale = baseRadius;
554  }
555 
556  // we could guess the max depth with a Distance Transform (as in the
557  // C2C distances computation tool of CLOUDVIEWER ) but it's only
558  // valid for nearest neighbor distance (and not normal-based NN
559  // search). Moreover it would be a bit overkill, especially as the
560  // neighbor search is progressive by default...
561  params.projDepth = 5 * params.projScale;
562 
563  // if possible, for normal scale we are going to look for a low roughness
564  // (1st cloud only) and for projection scale we are going to look for a good
565  // density (1st cloud only)
566  bool success = true;
567  if (!fastMode) {
568  ccOctree::Shared octree1 = cloud1->getOctree();
569  if (!octree1) {
570  ecvProgressDialog pDlg(true, app ? app->getMainWindow() : 0);
571  octree1 = cloud1->computeOctree(&pDlg);
572  if (!octree1) {
573  // failed to compute octree (not enough memory?)
574  return true; // we have the default values
575  } else if (app && cloud1->getParent()) {
576  app->addToDB(cloud1->getOctreeProxy());
577  }
578  }
579 
580  QProgressDialog pDlg("Please wait...", "Cancel", 0, 0);
581  pDlg.setWindowTitle("M3C2");
582  // pDlg.setModal(true);
583  pDlg.show();
584  QApplication::processEvents();
585 
586  // can't probe with less than 10 points
587  probingCount = std::max<unsigned>(probingCount, 10);
588 
589  // starting scale = smallest octree cell size!
590  PointCoordinateType baseScale =
591  octree1->getCellSize(ccOctree::MAX_OCTREE_LEVEL);
592  PointCoordinateType scale = baseScale;
593 
594  bool hasBestNormLevel = false;
595  bool hasBestProjLevel = false;
596  double bestMeanRoughness = -1.0;
597  std::vector<unsigned> populations;
598  populations.resize(probingCount, 0);
599 
600  while (!hasBestNormLevel || !hasBestProjLevel) {
601  unsigned char level =
602  octree1->findBestLevelForAGivenNeighbourhoodSizeExtraction(
603  scale);
604  if (app) {
605  app->dispToConsole(QString("[M3C2::auto] Test scale: %1 (level "
606  "%2, %3 samples)")
607  .arg(scale)
608  .arg(level)
609  .arg(probingCount));
610  }
611 
612  double sumPopulation = 0;
613  double sumPopulation2 = 0;
614  unsigned validLSPlanes = 0;
615  double sumRoughness = 0;
616  CCVector3d meanNormal(0, 0, 0);
617  for (unsigned i = 0; i < probingCount; ++i) {
618  unsigned pointIndex = static_cast<unsigned>(
619  ceil(static_cast<double>(rand()) / RAND_MAX *
620  static_cast<double>(count1)));
621  if (count1 == pointIndex) count1--;
622 
623  const CCVector3* P = cloud1->getPoint(pointIndex);
624  ccOctree::NeighboursSet neighbors;
625  octree1->getPointsInSphericalNeighbourhood(*P, scale, neighbors,
626  level);
627 
628  size_t n = neighbors.size();
629  populations[i] = static_cast<unsigned>(n);
630  sumPopulation += static_cast<double>(n);
631  sumPopulation2 += static_cast<double>(n * n);
632 
633  if (!hasBestNormLevel && n >= 12) {
634  cloudViewer::ReferenceCloud neighborCloud(cloud1);
635  if (!neighborCloud.resize(static_cast<unsigned>(n))) {
636  // not enough memory!
637  success = false;
638  // for early stop
639  hasBestNormLevel = hasBestProjLevel = true;
640  break;
641  }
642 
643  for (unsigned j = 0; j < n; ++j) {
644  neighborCloud.setPointIndex(j, neighbors[j].pointIndex);
645  }
646 
647  cloudViewer::Neighbourhood Yk(&neighborCloud);
648  const PointCoordinateType* lsPlane = Yk.getLSPlane();
649  if (lsPlane) {
651  computePoint2PlaneDistance(P, lsPlane);
652  // we compute relative roughness
653  d /= scale;
654  sumRoughness += d * d; // fabs(d);
655  meanNormal += CCVector3d::fromArray(lsPlane);
656  validLSPlanes++;
657  }
658  }
659 
660  if (i % 100 == 0) {
661  if (pDlg.wasCanceled()) {
662  // early stop
663  hasBestProjLevel = true;
664  hasBestNormLevel = true;
665  success = false;
666  break;
667  }
668  pDlg.setValue(pDlg.value() + 1);
669  QApplication::processEvents();
670  }
671  }
672 
673  // population stats
674  {
675  double meanPopulation =
676  sumPopulation / static_cast<double>(probingCount);
677  double stdDevPopulation = sqrt(fabs(
678  sumPopulation2 / static_cast<double>(probingCount) -
679  meanPopulation * meanPopulation));
680 
681  if (app)
682  app->dispToConsole(
683  QString("[M3C2::auto] \tPopulation per cell: %1 "
684  "+/- %2")
685  .arg(meanPopulation, 0, 'f', 1)
686  .arg(stdDevPopulation, 0, 'f', 1));
687 
688  if (!hasBestProjLevel) {
689  std::sort(populations.begin(),
690  populations.begin() + probingCount);
691  unsigned pop97 = populations[static_cast<unsigned>(
692  floor(probingCount * 0.03))];
693  if (app)
694  app->dispToConsole(QString("[M3C2::auto] \t97% of "
695  "cells above: %1 +/- %2")
696  .arg(pop97));
697  if (pop97 /*meanPopulation - 2 * stdDevPopulation*/ >=
698  minPoints4Stats) {
699  if (app)
700  app->dispToConsole(
701  QString("[M3C2::auto] \tThis scale seems "
702  "ok for projection!"));
703  params.projScale = scale;
704  hasBestProjLevel = true;
705  }
706  }
707  }
708 
709  if (!hasBestNormLevel) {
710  if (app)
711  app->dispToConsole(
712  QString("[M3C2::auto] \tValid normals: %1/%2")
713  .arg(validLSPlanes)
714  .arg(probingCount));
715 
716  double meanRoughness = sqrt(
717  sumRoughness / static_cast<double>(std::max<unsigned>(
718  validLSPlanes, 1)));
719  if (app)
720  app->dispToConsole(QString("[M3C2::auto] \tMean relative "
721  "roughness: %1")
722  .arg(meanRoughness));
723 
724  if (validLSPlanes >
725  0.99 * static_cast<double>(
726  probingCount)) // almost all neighbourhood
727  // must be large enough to
728  // fit a plane!
729  {
730  if (bestMeanRoughness < 0 ||
731  (meanRoughness < bestMeanRoughness &&
732  (!hasBestProjLevel ||
733  scale < 2.1 * params.projScale)) // DGM: don't
734  // increase the
735  // normal scale more
736  // than 2 times the
737  // projection scale
738  // (if possible)
739  ) {
740  bestMeanRoughness = meanRoughness;
741  params.normScale = scale;
742 
743  // mean normal
744  meanNormal.normalize();
745  unsigned char maxDim = 0;
746  if (fabs(meanNormal.x) < fabs(meanNormal.y)) maxDim = 1;
747  if (fabs(meanNormal.u[maxDim]) < fabs(meanNormal.z))
748  maxDim = 2;
749 
750  params.preferredDimension = maxDim;
751  } else {
752  // this scale is worst than the previous one, so we stop
753  // here
754  hasBestNormLevel = true;
755  if (app)
756  app->dispToConsole(
757  QString("[M3C2::auto] \tThe previous scale "
758  "was better for normals!"));
759  }
760  }
761  }
762 
763  if (level == 6 && (!hasBestNormLevel || !hasBestProjLevel)) {
764  // if we have reach a big radius already and we don't have
765  // good scales, we stop anyway!
766  if (bestMeanRoughness < 0) params.normScale = scale;
767  if (!hasBestProjLevel) params.projScale = scale;
768  hasBestProjLevel = true;
769  hasBestNormLevel = true;
770  if (app)
771  app->dispToConsole(
772  QString("[M3C2::auto] We failed to converge for "
773  "the best projection level, so we will "
774  "stop here!"),
776  break;
777  }
778 
779  // scale += baseScale;
780  scale *= 2;
781  probingCount =
782  std::max<unsigned>(10, probingCount - (probingCount / 10));
783  }
784  }
785 
786  return success;
787 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int count
cmdLineReadable * params[]
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Jacobi eigen vectors/values decomposition.
Definition: Jacobi.h:23
static bool GetMinEigenValueAndVector(const SquareMatrix &eigenVectors, const EigenValues &eigenValues, Scalar &minEigenValue, Scalar minEigenVector[])
Returns the smallest eigenvalue and its associated eigenvector.
Definition: Jacobi.h:410
Array of compressed 3D normals (single index)
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Type & getValue(size_t index)
Definition: ecvArray.h:100
bool isAllocated() const
Returns whether some memory has been allocated or not.
Definition: ecvArray.h:67
bool resizeSafe(size_t count, bool initNewElements=false, const Type *valueForNewElements=nullptr)
Resizes memory (no exception thrown)
Definition: ecvArray.h:70
void setValue(size_t index, const Type &value)
Definition: ecvArray.h:102
unsigned currentSize() const
Definition: ecvArray.h:112
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccOctreeProxy * getOctreeProxy() const
Returns the associated octree proxy (if any)
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
static const CCVector3 & GetNormal(unsigned normIndex)
Static access to ccNormalVectors::getNormal.
static CompressedNormType GetNormIndex(const PointCoordinateType N[])
Returns the compressed index corresponding to a normal vector.
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
A scalar field associated to display-related parameters.
double getDiagNormd() const
Returns diagonal length (double precision)
Definition: BoundingBox.h:175
T getDiagNorm() const
Returns diagonal length.
Definition: BoundingBox.h:172
The octree structure used throughout the library.
Definition: DgmOctree.h:39
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
Definition: DgmOctree.h:67
unsigned char findBestLevelForAGivenNeighbourhoodSizeExtraction(PointCoordinateType radius) const
Definition: DgmOctree.cpp:2664
int build(GenericProgressCallback *progressCb=nullptr)
Builds the structure.
Definition: DgmOctree.cpp:196
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
static ScalarType computePoint2PlaneDistance(const CCVector3 *P, const PointCoordinateType *planeEquation)
Computes the (signed) distance between a point and a plane.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based point access.
virtual void stop()=0
Notifies the fact that the process has ended.
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.
cloudViewer::SquareMatrixd computeCovarianceMatrix()
Computes the covariance matrix.
const PointCoordinateType * getLSPlane()
Returns best interpolating plane equation (Least-square)
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
unsigned size() const override
Returns the number of points.
virtual void setPointIndex(unsigned localIndex, unsigned globalIndex)
Sets global index for a given element.
virtual bool resize(unsigned n)
Presets the size of the vector used to store point references.
virtual void clear(bool releaseMemory=false)
Clears the cloud.
void fill(ScalarType fillValue=0)
Fills the array with a particular value.
Definition: ScalarField.h:77
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Definition: ScalarField.cpp:81
unsigned currentSize() const
Definition: ScalarField.h:100
Main application interface (for plugins)
virtual QMainWindow * getMainWindow()=0
Returns main window.
virtual void addToDB(ccHObject *obj, bool updateZoom=false, bool autoExpandDBTree=true, bool checkDimensions=false, bool autoRedraw=true)=0
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
Graphical progress indicator (thread-safe)
static void MakeNormalsHorizontal(NormsIndexesTableType &normsCodes)
Makes all normals horizontal.
Definition: qM3C2Tools.cpp:429
static bool ComputeCorePointsNormals(cloudViewer::GenericIndexedCloud *corePoints, NormsIndexesTableType *corePointsNormals, ccGenericPointCloud *sourceCloud, const std::vector< PointCoordinateType > &sortedRadii, bool &invalidNormals, int maxThreadCount=0, ccScalarField *normalScale=nullptr, cloudViewer::GenericProgressCallback *progressCb=nullptr, cloudViewer::DgmOctree *inputOctree=nullptr)
Computes normals on core points only.
Definition: qM3C2Tools.cpp:179
static bool UpdateNormalOrientationsWithCloud(cloudViewer::GenericIndexedCloud *normCloud, NormsIndexesTableType &normsCodes, cloudViewer::GenericIndexedCloud *orientationCloud, int maxThreadCount=0, cloudViewer::GenericProgressCallback *progressCb=nullptr)
Definition: qM3C2Tools.cpp:346
static bool GuessBestParams(ccPointCloud *cloud1, ccPointCloud *cloud2, unsigned minPoints4Stats, GuessedParams &params, bool fastMode, ecvMainAppInterface *app=nullptr, unsigned probingCount=1000)
Tries to guess some M3C2 parameters by randomly 'probing' the cloud.
Definition: qM3C2Tools.cpp:529
static void ComputeStatistics(cloudViewer::DgmOctree::NeighboursSet &set, bool useMedian, double &meanOrMedian, double &stdDevOrIQR)
Computes statistics on a neighbors set.
Definition: qM3C2Tools.cpp:487
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
unsigned int CompressedNormType
Compressed normals type.
Definition: ecvBasicTypes.h:16
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
cloudViewer::GenericIndexedCloud * normCloud
Definition: qM3C2Tools.cpp:300
double Interquartile(const cloudViewer::DgmOctree::NeighboursSet &set)
Computes the interquartile range of a (sorted) neighbors set.
Definition: qM3C2Tools.cpp:474
static struct @100 s_corePointsNormalsParams
cloudViewer::NormalizedProgress * nProgress
Definition: qM3C2Tools.cpp:47
NormsIndexesTableType * normsCodes
Definition: qM3C2Tools.cpp:299
ccScalarField * normalScale
Definition: qM3C2Tools.cpp:44
NormsIndexesTableType * normCodes
Definition: qM3C2Tools.cpp:43
void ComputeCorePointNormal(unsigned index)
Definition: qM3C2Tools.cpp:52
static struct @101 s_normOriWithCloud
std::vector< PointCoordinateType > radii
Definition: qM3C2Tools.cpp:42
cloudViewer::GenericIndexedCloud * orientationCloud
Definition: qM3C2Tools.cpp:301
bool invalidNormals
Definition: qM3C2Tools.cpp:45
cloudViewer::DgmOctree * octree
Definition: qM3C2Tools.cpp:40
static void OrientPointNormalWithCloud(unsigned index)
Definition: qM3C2Tools.cpp:308
cloudViewer::GenericIndexedCloud * corePoints
Definition: qM3C2Tools.cpp:38
bool processCanceled
Definition: qM3C2Tools.cpp:48
static double Median(const cloudViewer::DgmOctree::NeighboursSet &set, size_t begin=0, size_t count=0)
Computes the median distance of a (sorted) neighbors set.
Definition: qM3C2Tools.cpp:450
unsigned char octreeLevel
Definition: qM3C2Tools.cpp:41
ccGenericPointCloud * sourceCloud
Definition: qM3C2Tools.cpp:39
static bool distComp(const PointDescriptor &a, const PointDescriptor &b)
Comparison operator.
Definition: DgmOctree.h:126
M3C2 parameters that can be guessed automatically by 'probing'.
Definition: qM3C2Tools.h:78