ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvComparisonDlg.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 "ecvComparisonDlg.h"
9 
10 // Qt
11 #include <QHeaderView>
12 #include <QMessageBox>
13 
14 // cloudViewer
15 #include <DgmOctree.h>
17 #include <MeshSamplingTools.h>
18 #include <ScalarField.h>
19 #include <ScalarFieldTools.h>
20 
21 // CV_DB_LIB
22 #include <CVLog.h>
23 #include <ecvDisplayTools.h>
24 #include <ecvGenericMesh.h>
25 #include <ecvHObject.h>
26 #include <ecvOctree.h>
27 #include <ecvPointCloud.h>
28 #include <ecvProgressDialog.h>
29 
30 // Local
31 #include "MainWindow.h"
32 #include "ecvCommon.h"
33 #include "ecvHistogramWindow.h"
34 
35 // Qt
36 #include <QElapsedTimer>
37 #include <QThreadPool>
38 
39 // System
40 #include <assert.h>
41 
42 const unsigned char DEFAULT_OCTREE_LEVEL = 7;
43 
45  ccHObject* refEntity,
46  CC_COMPARISON_TYPE cpType,
47  QWidget* parent /*=nullptr*/,
48  bool noDisplay /*=false*/)
49  : QDialog(parent, Qt::Tool),
50  Ui::ComparisonDialog(),
51  m_compEnt(compEntity),
52  m_compCloud(nullptr),
53  m_compOctree(nullptr),
54  m_compOctreeIsPartial(false),
55  m_compSFVisibility(false),
56  m_refEnt(refEntity),
57  m_refCloud(nullptr),
58  m_refMesh(nullptr),
59  m_refOctree(nullptr),
60  m_refOctreeIsPartial(false),
61  m_refVisibility(false),
62  m_compType(cpType),
63  m_noDisplay(noDisplay),
64  m_bestOctreeLevel(0) {
65  setupUi(this);
66 
67  int maxThreadCount = QThread::idealThreadCount();
68  maxThreadCountSpinBox->setRange(1, maxThreadCount);
69  maxThreadCountSpinBox->setSuffix(QString(" / %1").arg(maxThreadCount));
70  maxThreadCountSpinBox->setValue(
71  QThreadPool::globalInstance()->maxThreadCount());
72 
73  // populate the combo-boxes
74  {
75  // octree level
76  octreeLevelComboBox->addItem("AUTO");
77  for (int i = 1; i <= cloudViewer::DgmOctree::MAX_OCTREE_LEVEL; ++i)
78  octreeLevelComboBox->addItem(QString::number(i));
79 
80  // local model
81  localModelComboBox->addItem("NONE");
82  localModelComboBox->addItem("Least Square Plane");
83  localModelComboBox->addItem("2D1/2 Triangulation");
84  localModelComboBox->addItem("Quadric");
85  localModelComboBox->setCurrentIndex(0);
86  }
87 
88  signedDistCheckBox->setChecked(false);
89  split3DCheckBox->setEnabled(false);
90  okButton->setEnabled(false);
91 
92  compName->setText(m_compEnt->getName());
93  refName->setText(m_refEnt->getName());
94  preciseResultsTabWidget->setCurrentIndex(0);
95 
96  m_refVisibility = (m_refEnt ? m_refEnt->isVisible() : false);
98 
99  if (!prepareEntitiesForComparison()) return;
100 
101  assert(compEntity);
102  ccBBox compEntBBox = compEntity->getOwnBB();
103  maxSearchDistSpinBox->setValue(compEntBBox.getDiagNorm());
104 
105  if (m_refMesh) {
106  localModelingTab->setEnabled(false);
107  signedDistCheckBox->setEnabled(true);
108  signedDistCheckBox->setChecked(true);
109  filterVisibilityCheckBox->setEnabled(false);
110  filterVisibilityCheckBox->setVisible(false);
111  } else {
112  signedDistCheckBox->setEnabled(false);
113  split3DCheckBox->setEnabled(true);
114  lmRadiusDoubleSpinBox->setValue(compEntBBox.getDiagNorm() / 200.0);
115  filterVisibilityCheckBox->setEnabled(
117  static_cast<ccPointCloud*>(m_refCloud)->hasSensor());
118  }
119 
120  connect(cancelButton, &QPushButton::clicked, this,
122  connect(okButton, &QPushButton::clicked, this,
124  connect(computeButton, &QPushButton::clicked, this,
126  connect(histoButton, &QPushButton::clicked, this,
128  connect(maxDistCheckBox, &QCheckBox::toggled, this,
130  connect(localModelComboBox,
131  static_cast<void (QComboBox::*)(int)>(
132  &QComboBox::currentIndexChanged),
134  connect(maxSearchDistSpinBox,
135  static_cast<void (QDoubleSpinBox::*)(double)>(
136  &QDoubleSpinBox::valueChanged),
138 
139  // be sure to show the dialog before computing the approx distances
140  //(otherwise the progress bars appear anywhere!)
141  if (!m_noDisplay) {
142  show();
143  QCoreApplication::processEvents();
144  }
145 
146  // compute approximate results and unlock GUI
148 }
149 
151 
153  if (!m_compEnt || !m_refEnt) return false;
154 
155  // compared entity
156  if (!m_compEnt->isA(CV_TYPES::POINT_CLOUD)) // TODO --> pas possible avec
157  // des GenericPointCloud ? :(
158  {
159  if (m_compType == CLOUDCLOUD_DIST ||
162  CVLog::Error("Dialog initialization error! (bad entity type)");
163  return false;
164  }
166  if (!compMesh->getAssociatedCloud()->isA(
167  CV_TYPES::POINT_CLOUD)) // TODO
168  {
169  CVLog::Error(
170  "Dialog initialization error! (bad entity type - works "
171  "only with real point clouds [todo])");
172  return false;
173  }
174  m_compCloud =
175  static_cast<ccPointCloud*>(compMesh->getAssociatedCloud());
176  } else {
177  m_compCloud = static_cast<ccPointCloud*>(m_compEnt);
178  }
179 
180  // whatever the case, we always need the compared cloud's octree
182  if (!m_compOctree) {
184  }
185  m_compOctreeIsPartial = false;
186 
187  // backup currently displayed SF (on compared cloud)
189  if (oldSfIdx >= 0)
190  m_oldSfName = QString(m_compCloud->getScalarFieldName(oldSfIdx));
191 
192  // reference entity
196  CVLog::Error("Dialog initialization error! (bad entity type)");
197  return false;
198  }
199 
200  if (m_compType == CLOUDMESH_DIST) {
203  m_refOctree.clear();
204  } else /*if (m_compType == CLOUDCLOUD_DIST)*/
205  {
207 
208  // for computing cloud/cloud distances we need also the reference
209  // cloud's octree
211  if (!m_refOctree) {
213  }
214  }
215  m_refOctreeIsPartial = false;
216 
217  return true;
218 }
219 
221  // the current 'best octree level' is depreacted
222  m_bestOctreeLevel = 0;
223 }
224 
226  if (m_bestOctreeLevel == 0) {
227  double maxDistance =
228  (maxDistCheckBox->isChecked() ? maxSearchDistSpinBox->value()
229  : 0);
230 
231  int bestOctreeLevel = determineBestOctreeLevel(maxDistance);
232  if (bestOctreeLevel <= 0) {
233  CVLog::Error(
234  "Can't evaluate best octree level! Try to set it manually "
235  "...");
236  return -1;
237  }
238 
239  m_bestOctreeLevel = bestOctreeLevel;
240  }
241 
242  return m_bestOctreeLevel;
243 }
244 
246  localModelParamsFrame->setEnabled(index != 0);
247 
248  if (index != 0) {
249  unsigned minKNN = CV_LOCAL_MODEL_MIN_SIZE[index];
250  lmKNNSpinBox->setMinimum(minKNN);
251  }
252 }
253 
255  if (m_compOctree && m_compCloud) {
256  m_compOctree.clear();
257  m_compOctreeIsPartial = false;
258  }
259 
260  if (m_refOctree && m_refCloud) {
261  m_refOctree.clear();
262  m_refOctreeIsPartial = false;
263  }
264 }
265 
266 void ccComparisonDlg::updateDisplay(bool showSF, bool showRef) {
267  if (m_noDisplay) return;
269  if (m_compEnt) {
270  m_compEnt->setVisible(true);
271  m_compEnt->setEnabled(true);
272  m_compEnt->showSF(showSF);
274  }
275 
276  if (m_refEnt) {
277  m_refEnt->setVisible(showRef);
278  }
279 
282 }
283 
285  if (!m_compCloud || !m_compOctree || (!m_refMesh && !m_refCloud) ||
286  (!m_refMesh && !m_refOctree)) {
287  CVLog::Error("Dialog initialization error! (void entity)");
288  return false;
289  }
290 
291  return true;
292 }
293 
295  histoButton->setEnabled(false);
296  preciseResultsTabWidget->widget(2)->setEnabled(false);
297 
298  if (!isValid()) return false;
299 
300  // create the approximate dist. SF if necessary
303  if (sfIdx < 0) {
304  sfIdx = m_compCloud->addScalarField(
306  if (sfIdx < 0) {
307  CVLog::Error(
308  "Failed to allocate a new scalar field for computing "
309  "approx. distances! Try to free some memory ...");
310  return false;
311  }
312  }
313 
316  assert(sf);
317 
318  // prepare the octree structures
319  QScopedPointer<ecvProgressDialog> progressDlg;
320  if (parentWidget()) {
321  progressDlg.reset(new ecvProgressDialog(true, this));
322  progressDlg->show();
323  }
324 
325  int approxResult = -1;
326  QElapsedTimer eTimer;
327  eTimer.start();
328  switch (m_compType) {
329  case CLOUDCLOUD_DIST: // cloud-cloud
330  {
334  progressDlg.data(), m_compOctree.data(),
335  m_refOctree.data());
336  } break;
337 
338  case CLOUDMESH_DIST: // cloud-mesh
339  {
340  cloudViewer::DistanceComputationTools::
341  Cloud2MeshDistancesComputationParams c2mParams;
342  {
343  c2mParams.octreeLevel = DEFAULT_OCTREE_LEVEL;
344  c2mParams.maxSearchDist = 0;
345  c2mParams.useDistanceMap = true;
346  c2mParams.signedDistances = false;
347  c2mParams.flipNormals = false;
348  c2mParams.multiThread = false;
349  }
352  c2mParams, progressDlg.data(),
353  m_compOctree.data());
354  } break;
355 
356  default:
357  assert(false);
358  break;
359  }
360  qint64 elapsedTime_ms = eTimer.elapsed();
361 
362  if (progressDlg) {
363  progressDlg->stop();
364  }
365 
366  // if the approximate distances comptation failed...
367  if (approxResult < 0) {
369  "[computeApproxDistances] Computation failed (error code %i)",
370  approxResult);
372  sfIdx = -1;
373  } else {
374  CVLog::Print("[computeApproxDistances] Time: %3.2f s.",
375  elapsedTime_ms / 1.0e3);
376 
377  // display approx. dist. statistics
378  ScalarType mean, variance;
379  sf->computeMinAndMax();
380  sf->computeMeanAndVariance(mean, &variance);
381 
382  approxStats->setColumnCount(2);
383  approxStats->setRowCount(5);
384  approxStats->setColumnWidth(1, 200);
385  approxStats->horizontalHeader()->hide();
386  int curRow = 0;
387 
388  // min dist
389  approxStats->setItem(curRow, 0, new QTableWidgetItem("Min dist."));
390  approxStats->setItem(
391  curRow++, 1,
392  new QTableWidgetItem(QString("%1").arg(sf->getMin())));
393 
394  // max dist
395  approxStats->setItem(curRow, 0, new QTableWidgetItem("Max dist."));
396  approxStats->setItem(
397  curRow++, 1,
398  new QTableWidgetItem(QString("%1").arg(sf->getMax())));
399 
400  // mean dist
401  approxStats->setItem(curRow, 0, new QTableWidgetItem("Avg dist."));
402  approxStats->setItem(curRow++, 1,
403  new QTableWidgetItem(QString("%1").arg(mean)));
404 
405  // sigma
406  approxStats->setItem(curRow, 0, new QTableWidgetItem("Sigma"));
407  approxStats->setItem(
408  curRow++, 1,
409  new QTableWidgetItem(QString("%1").arg(
410  variance >= 0.0 ? sqrt(variance) : variance)));
411 
412  // Max relative error
414  m_compOctree->getCellSize(DEFAULT_OCTREE_LEVEL);
415  double e = cs / 2.0;
416  approxStats->setItem(curRow, 0, new QTableWidgetItem("Max error"));
417  approxStats->setItem(curRow++, 1,
418  new QTableWidgetItem(QString("%1").arg(e)));
419 
420  for (int i = 0; i < curRow; ++i) {
421  approxStats->setRowHeight(i, 20);
422  }
423 
424  approxStats->setEditTriggers(QAbstractItemView::NoEditTriggers);
425 
426  // enable the corresponding UI items
427  preciseResultsTabWidget->widget(2)->setEnabled(true);
428  histoButton->setEnabled(true);
429 
430  // init the max search distance
431  maxSearchDistSpinBox->setValue(sf->getMax());
432 
433  // update display
435  m_compCloud->showSF(sfIdx >= 0);
436  }
437 
438  computeButton->setEnabled(true);
439  preciseGroupBox->setEnabled(true);
440  // we don't let the user leave with approximate distances!!!
441  okButton->setEnabled(false);
442 
443  updateDisplay(sfIdx >= 0, false);
444 
445  return true;
446 }
447 
448 int ccComparisonDlg::determineBestOctreeLevel(double maxSearchDist) {
449  if (!isValid()) {
450  return -1;
451  }
452 
453  // make sure a the temporary dist. SF is activated
456  if (sfIdx < 0) {
457  // we must compute approx. results again
458  if (!computeApproxDistances()) {
459  // failed to compute approx distances?!
460  return -1;
461  }
464  }
465 
466  const cloudViewer::ScalarField* approxDistances =
467  m_compCloud->getScalarField(sfIdx);
468  if (!approxDistances) {
469  assert(sfIdx >= 0);
470  return -1;
471  }
472 
473  // evalutate the theoretical time for each octree level
474  const int MAX_OCTREE_LEVEL =
475  m_refMesh ? 9
477  MAX_OCTREE_LEVEL; // DGM: can't go higher than
478  // level 9 with a mesh as the
479  // grid is 'plain' and would
480  // take too much memory!
481  std::vector<double> timings;
482  try {
483  timings.resize(MAX_OCTREE_LEVEL, 0);
484  } catch (const std::bad_alloc&) {
485  CVLog::Warning("Can't determine best octree level: not enough memory!");
486  return -1;
487  }
488 
489  // if the reference is a mesh
490  double meanTriangleSurface = 1.0;
492  if (!m_refOctree) {
493  if (!m_refMesh) {
494  CVLog::Error("Internal error: reference entity should be a mesh!");
495  return -1;
496  }
497  mesh = static_cast<cloudViewer::GenericIndexedMesh*>(m_refMesh);
498  if (!mesh || mesh->size() == 0) {
499  CVLog::Warning("Can't determine best octree level: mesh is empty!");
500  return -1;
501  }
502  // total mesh surface
503  double meshSurface =
505  // average triangle surface
506  if (meshSurface > 0) {
507  meanTriangleSurface = meshSurface / mesh->size();
508  }
509  }
510 
511  // we skip the lowest subdivision levels (useless + incompatible with below
512  // formulas ;)
513  static const int s_minOctreeLevel = 6;
514  int theBestOctreeLevel = s_minOctreeLevel;
515 
516  // we don't test the very first and very last level
517  QScopedPointer<ecvProgressDialog> progressDlg;
518  if (parentWidget()) {
519  progressDlg.reset(new ecvProgressDialog(false, this));
520  progressDlg->setMethodTitle(tr("Determining optimal octree level"));
521  progressDlg->setInfo(tr("Testing %1 levels...")
522  .arg(MAX_OCTREE_LEVEL)); // we lie here ;)
523  progressDlg->start();
524  }
525  cloudViewer::NormalizedProgress nProgress(progressDlg.data(),
526  MAX_OCTREE_LEVEL - 2);
527  QApplication::processEvents();
528 
529  bool maxDistanceDefined = maxDistCheckBox->isChecked();
530  PointCoordinateType maxDistance = static_cast<PointCoordinateType>(
531  maxDistanceDefined ? maxSearchDistSpinBox->value() : 0);
532 
533  // for each level
534  for (int level = s_minOctreeLevel; level < MAX_OCTREE_LEVEL; ++level) {
535  const unsigned char bitDec =
537  unsigned numberOfPointsInCell = 0;
538  unsigned index = 0;
539  double cellDist = -1;
540  // unsigned skippedCells = 0;
541 
542  // we compute a 'correction factor' that converts an approximate
543  // distance into an approximate size of the neighborhood (in terms of
544  // cells)
545  PointCoordinateType cellSize =
546  m_compOctree->getCellSize(static_cast<unsigned char>(level));
547 
548  // we also use the reference cloud density (points/cell) if we have the
549  // info
550  double refListDensity = 1.0;
551  if (m_refOctree) {
552  refListDensity = m_refOctree->computeMeanOctreeDensity(
553  static_cast<unsigned char>(level));
554  }
555 
556  cloudViewer::DgmOctree::CellCode tempCode = 0xFFFFFFFF;
557 
558  // scan the octree structure
559  const cloudViewer::DgmOctree::cellsContainer& compCodes =
560  m_compOctree->pointsAndTheirCellCodes();
561  for (cloudViewer::DgmOctree::cellsContainer::const_iterator c =
562  compCodes.begin();
563  c != compCodes.end(); ++c) {
564  cloudViewer::DgmOctree::CellCode truncatedCode =
565  (c->theCode >> bitDec);
566 
567  // new cell?
568  if (truncatedCode != tempCode) {
569  // if it's a real cell
570  if (numberOfPointsInCell != 0) {
571  // if 'maxSearchDist' has been defined by the user, we must
572  // take it into account! (in this case we skip the cell if
573  // its approx. distance is superior)
574  if (maxSearchDist <= 0 || cellDist <= maxSearchDist) {
575  // approx. neighborhood radius
576  cellDist /= cellSize;
577 
578  // approx. neighborhood width (in terms of cells)
579  double neighbourSize = 2.0 * cellDist + 1.0;
580 
581  // if the reference is a mesh
582  if (mesh) {
583  //(integer) approximation of the neighborhood size
584  //(in terms of cells)
585  int nCell = static_cast<int>(ceil(cellDist));
586 
587  // Probable mesh surface in this neighborhood
588  double crossingMeshSurface =
589  (2.0 * nCell + 1.0) * cellSize;
590  // squared surface!
591  crossingMeshSurface *= crossingMeshSurface;
592 
593  // neighborhood "volume" (in terms of cells)
594  double neighbourSize3 = neighbourSize *
595  neighbourSize *
596  neighbourSize;
597 
598  // TIME = NEIGHBORS SEARCH + proportional factor *
599  // POINTS/TRIANGLES COMPARISONS
600  timings[level] += neighbourSize3 +
601  0.5 * numberOfPointsInCell *
602  crossingMeshSurface /
603  meanTriangleSurface;
604  } else {
605  // we ignore the "central" cell
606  neighbourSize -= 1.0;
607  // neighborhood "volume" (in terms of cells)
608  double neighbourSize3 = neighbourSize *
609  neighbourSize *
610  neighbourSize;
611  // volume of the last "slice" (in terms of cells)
612  //=V(n)-V(n-1) = (2*n+1)^3 - (2*n-1)^3 = 24 * n^2 +
613  // 2 (si n>0)
614  double lastSliceCellNumber =
615  (cellDist > 0
616  ? cellDist * cellDist * 24.0 + 2.0
617  : 1.0);
618  // TIME = NEIGHBORS SEARCH + proportional factor *
619  // POINTS/TRIANGLES COMPARISONS (we admit that the
620  // filled cells roughly correspond to the sqrt of
621  // the total number of cells)
622  timings[level] +=
623  neighbourSize3 +
624  0.1 * numberOfPointsInCell *
625  sqrt(lastSliceCellNumber) *
626  refListDensity;
627  }
628  }
629  // else
630  //{
631  // ++skippedCells;
632  // }
633  }
634 
635  numberOfPointsInCell = 0;
636  cellDist = 0;
637  tempCode = truncatedCode;
638  }
639 
640  ScalarType pointDist = approxDistances->getValue(index);
641  if (maxDistanceDefined && pointDist > maxDistance) {
642  pointDist = maxDistance;
643  }
644 
645  // cellDist += pointDist;
646  cellDist = std::max<double>(cellDist, pointDist);
647  ++index;
648  ++numberOfPointsInCell;
649  }
650 
652  //{
653  // double levelModifier = level < 12 ? 1.0 :
654  // exp(-pow(level-12,2)/(20*20)); timings[level] /= levelModifier;
655 
656  // CVLog::PrintDebug(QString("[Distances] Level %1 - timing = %2
657  //(modifier = %3)").arg(level).arg(timings[level]).arg(levelModifier));
658  //}
659 
660  // CVLog::Print("[Timing] Level %i --> %f",level,timings[level]);
661  // timings[level] +=
662  // (static_cast<qreal>(skippedCells)/1000)*skippedCells; //empirical
663  // correction for skipped cells (not taken into account while they
664  // actually require some processing time!)
665  if (timings[level] < timings[theBestOctreeLevel]) {
666  theBestOctreeLevel = level;
667  }
668 
669  nProgress.oneStep();
670  }
671 
672  CVLog::PrintDebug("[Distances] Best level: %i (maxSearchDist = %f)",
673  theBestOctreeLevel, maxSearchDist);
674 
675  return theBestOctreeLevel;
676 }
677 
679  if (!isValid()) return false;
680 
681  int octreeLevel = octreeLevelComboBox->currentIndex();
683 
684  if (octreeLevel == 0) {
685  // we'll try to guess the best octree level
687  if (octreeLevel <= 0) {
688  // best octree level computation failed?!
689  return false;
690  }
691  CVLog::Print(QString("[Distances] Octree level (auto): %1")
692  .arg(octreeLevel));
693  }
694 
695  // options
696  bool signedDistances =
697  signedDistCheckBox->isEnabled() && signedDistCheckBox->isChecked();
698  bool flipNormals =
699  (signedDistances ? flipNormalsCheckBox->isChecked() : false);
700  bool split3D = split3DCheckBox->isEnabled() && split3DCheckBox->isChecked();
701 
702  // does the cloud has already a temporary scalar field that we can use?
705  if (sfIdx < 0) {
706  // we need to create a new scalar field
708  if (sfIdx < 0) {
709  CVLog::Error(
710  "Couldn't allocate a new scalar field for computing "
711  "distances! Try to free some memory ...");
712  return false;
713  }
714  }
715 
718  assert(sf);
719 
720  // max search distance
721  ScalarType maxSearchDist = static_cast<ScalarType>(
722  maxDistCheckBox->isChecked() ? maxSearchDistSpinBox->value() : 0);
723  // multi-thread
724  bool multiThread = multiThreadedCheckBox->isChecked();
725 
727  c2cParams;
729  c2mParams;
730  c2cParams.maxThreadCount = c2mParams.maxThreadCount =
731  maxThreadCountSpinBox->value();
732 
733  int result = -1;
734  QScopedPointer<ecvProgressDialog> progressDlg;
735  if (parentWidget()) {
736  progressDlg.reset(new ecvProgressDialog(true, this));
737  }
738 
739  QElapsedTimer eTimer;
740  eTimer.start();
741  switch (m_compType) {
742  case CLOUDCLOUD_DIST: // cloud-cloud
743 
744  if (split3D) {
745  // we create 3 new scalar fields, one for each dimension
746  unsigned count = m_compCloud->size();
747 
748  bool success = true;
749  for (unsigned j = 0; j < 3; ++j) {
750  ccScalarField* sfDim = new ccScalarField();
751  if (sfDim->resizeSafe(count)) {
752  sfDim->link();
753  c2cParams.splitDistances[j] = sfDim;
754  } else {
755  success = false;
756  break;
757  }
758  }
759 
760  if (!success) {
761  CVLog::Error(
762  "[ComputeDistances] Not enough memory to generate "
763  "3D split fields!");
764 
765  for (unsigned j = 0; j < 3; ++j) {
766  if (c2cParams.splitDistances[j]) {
767  c2cParams.splitDistances[j]->release();
768  c2cParams.splitDistances[j] = nullptr;
769  }
770  }
771  }
772  }
773 
775  ccPointCloud* pc = static_cast<ccPointCloud*>(m_refCloud);
776 
777  // we enable the visibility checking if the user asked for it
778  bool filterVisibility = filterVisibilityCheckBox->isEnabled() &&
779  filterVisibilityCheckBox->isChecked();
780  if (filterVisibility) {
781  size_t validDB = 0;
782  // we also make sure that the sensors have valid depth
783  // buffer!
784 #if 0
785  for (unsigned i = 0; i < pc->getChildrenNumber(); ++i)
786  {
787  ccHObject* child = pc->getChild(i);
788  if (child && child->isA(CV_TYPES::GBL_SENSOR))
789  {
790  ccGBLSensor* sensor = static_cast<ccGBLSensor*>(child);
791  if (sensor->getDepthBuffer().zBuff.empty())
792  {
793  int errorCode;
794  if (!sensor->computeDepthBuffer(pc, errorCode))
795  {
796  CVLog::Warning(QString("[ComputeDistances] ") + ccGBLSensor::GetErrorString(errorCode));
797  }
798  else
799  {
800  ++validDB;
801  }
802  }
803  else
804  {
805  ++validDB;
806  }
807  }
808  }
809 #endif
810 
811  if (validDB == 0) {
812  filterVisibilityCheckBox->setChecked(false);
813  if (QMessageBox::warning(
814  this, "Error",
815  "Failed to find/init the depth buffer(s) "
816  "on the associated sensor! Do you want to "
817  "continue?",
818  QMessageBox::Yes,
819  QMessageBox::No) == QMessageBox::No) {
820  break;
821  }
822  filterVisibility = false;
823  }
824  }
825  pc->enableVisibilityCheck(filterVisibility);
826  }
827 
828  // setup parameters
829  {
830  c2cParams.octreeLevel = static_cast<unsigned char>(octreeLevel);
831  if (localModelingTab->isEnabled()) {
832  c2cParams.localModel =
834  localModelComboBox->currentIndex();
835  if (c2cParams.localModel != NO_MODEL) {
837  lmRadiusRadioButton->isChecked();
838  c2cParams.kNNForLocalModel = static_cast<unsigned>(
839  std::max(0, lmKNNSpinBox->value()));
840  c2cParams.radiusForLocalModel = static_cast<ScalarType>(
841  lmRadiusDoubleSpinBox->value());
842  c2cParams.reuseExistingLocalModels =
843  lmOptimizeCheckBox->isChecked();
844  }
845  }
846  c2cParams.maxSearchDist = maxSearchDist;
847  c2cParams.multiThread = multiThread;
848  c2cParams.CPSet = 0;
849  }
850 
853  c2cParams, progressDlg.data(),
854  m_compOctree.data(),
855  m_refOctree.data());
856  break;
857 
858  case CLOUDMESH_DIST: // cloud-mesh
859 
860  if (multiThread && maxDistCheckBox->isChecked()) {
862  "[Cloud/Mesh comparison] Max search distance is not "
863  "supported in multi-thread mode! Switching to single "
864  "thread mode...");
865  }
866 
867  // setup parameters
868  {
869  c2mParams.octreeLevel = static_cast<unsigned char>(octreeLevel);
870  c2mParams.maxSearchDist = maxSearchDist;
871  c2mParams.useDistanceMap = false;
872  c2mParams.signedDistances = signedDistances;
873  c2mParams.flipNormals = flipNormals;
874  c2mParams.multiThread = multiThread;
875  }
876 
879  c2mParams, progressDlg.data(),
880  m_compOctree.data());
881  break;
882  }
883  qint64 elapsedTime_ms = eTimer.elapsed();
884 
885  if (progressDlg) {
886  progressDlg->stop();
887  }
888 
889  if (result >= 0) {
890  CVLog::Print("[ComputeDistances] Time: %3.2f s.",
891  static_cast<double>(elapsedTime_ms) / 1.0e3);
892 
893  // display some statics about the computed distances
894  ScalarType mean, variance;
895  sf->computeMinAndMax();
896  sf->computeMeanAndVariance(mean, &variance);
897  CVLog::Print(
898  "[ComputeDistances] Mean distance = %f / std deviation = %f",
899  mean, sqrt(variance));
900 
902  m_compCloud->showSF(sfIdx >= 0);
903 
904  // restore UI items
905  okButton->setEnabled(true);
906 
907  m_sfName.clear();
908  switch (m_compType) {
909  case CLOUDCLOUD_DIST: // hausdorff
911  break;
912  case CLOUDMESH_DIST: // cloud-mesh
913  m_sfName = QString(
914  signedDistances
917  break;
918  }
919 
920  if (c2cParams.localModel != NO_MODEL) {
921  m_sfName += QString("[%1]").arg(localModelComboBox->currentText());
922  if (c2cParams.useSphericalSearchForLocalModel)
923  m_sfName +=
924  QString("[r=%1]").arg(c2cParams.radiusForLocalModel);
925  else
926  m_sfName += QString("[k=%1]").arg(c2cParams.kNNForLocalModel);
927  if (c2cParams.reuseExistingLocalModels)
928  m_sfName += QString("[fast]");
929  }
930 
931  if (flipNormals) {
932  m_sfName += QString("[-]");
933  }
934 
935  if (maxSearchDist > 0) {
936  m_sfName += QString("[<%1]").arg(maxSearchDist);
937  }
938 
939  if (split3D) {
940  // we add the corresponding scalar fields (one for each dimension)
941  static const QChar charDim[3] = {'X', 'Y', 'Z'};
942  for (unsigned j = 0; j < 3; ++j) {
943  cloudViewer::ScalarField* sf = c2cParams.splitDistances[j];
944  if (sf) {
945  sf->setName(qPrintable(m_sfName +
946  QString(" (%1)").arg(charDim[j])));
947  sf->computeMinAndMax();
948  // check that SF doesn't already exist
950  sf->getName());
951  if (sfExit >= 0) m_compCloud->deleteScalarField(sfExit);
952  int sfEnter = m_compCloud->addScalarField(
953  static_cast<ccScalarField*>(sf));
954  assert(sfEnter >= 0);
955  }
956  }
958  "[ComputeDistances] Result has been split along each "
959  "dimension (check the 3 other scalar fields with '_X', "
960  "'_Y' and '_Z' suffix!)");
961  }
962  } else {
963  CVLog::Error("[ComputeDistances] Error (%i)", result);
964 
966  m_compCloud->showSF(false);
967  sfIdx = -1;
968  }
969 
970  for (unsigned j = 0; j < 3; ++j) {
971  cloudViewer::ScalarField*& sf = c2cParams.splitDistances[j];
972  if (sf) {
973  sf->release();
974  sf = nullptr;
975  }
976  }
977 
978  updateDisplay(sfIdx >= 0, false);
979 
980  return result >= 0;
981 }
982 
984  if (!m_compCloud) return;
985 
987  if (!sf) return;
988 
989  ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
990  hDlg->setWindowTitle(QString("Histogram [%1]").arg(m_compCloud->getName()));
991  {
992  ccHistogramWindow* histogram = hDlg->window();
993  histogram->setTitle(QString("Approximate distances (%1 values)")
994  .arg(m_compCloud->size()));
995  histogram->fromSF(sf, 8, false);
996  histogram->setAxisLabels("Approximate distances", "Count");
997  }
998  hDlg->resize(400, 300);
999  hDlg->show();
1000 }
1001 
1003  if (m_compCloud) {
1004  // m_compCloud->setCurrentDisplayedScalarField(-1);
1005  // m_compCloud->showSF(false);
1006 
1007  // remove the approximate dist. SF
1008  int tmpSfIdx = m_compCloud->getScalarFieldIndexByName(
1010  if (tmpSfIdx >= 0) {
1011  m_compCloud->deleteScalarField(tmpSfIdx);
1012  tmpSfIdx = -1;
1013  }
1014 
1015  // now, if we have a temp distance scalar field (the 'real' distances
1016  // computed by the user) we should rename it properly
1019  if (sfIdx >= 0) {
1020  if (m_sfName.isEmpty()) // hum,hum
1021  {
1022  CVLog::Warning("Something went wrong!");
1025  m_compCloud->showSF(false);
1026  } else {
1027  // we delete any existing scalar field with the exact same name
1028  int _sfIdx = m_compCloud->getScalarFieldIndexByName(
1029  qPrintable(m_sfName));
1030  if (_sfIdx >= 0) {
1031  m_compCloud->deleteScalarField(_sfIdx);
1032  // we update sfIdx because indexes are all messed up after
1033  // deletion
1036  }
1037 
1038  m_compCloud->renameScalarField(sfIdx, qPrintable(m_sfName));
1040  m_compCloud->showSF(sfIdx >= 0);
1041  }
1042  }
1043 
1044  // m_compCloud->setCurrentDisplayedScalarField(-1);
1045  // m_compCloud->showSF(false);
1046  }
1047 
1049 
1050  releaseOctrees();
1051 
1052  accept();
1053 }
1054 
1056  if (m_compCloud) {
1058  m_compCloud->showSF(false);
1059 
1060  // remove the approximate dist. SF
1061  int tmpSfIdx = m_compCloud->getScalarFieldIndexByName(
1063  if (tmpSfIdx >= 0) {
1064  m_compCloud->deleteScalarField(tmpSfIdx);
1065  tmpSfIdx = -1;
1066  }
1067 
1070  if (sfIdx >= 0) {
1072  sfIdx = -1;
1073  }
1074 
1075  if (!m_oldSfName.isEmpty()) {
1076  int oldSfIdx = m_compCloud->getScalarFieldIndexByName(
1077  qPrintable(m_oldSfName));
1078  if (oldSfIdx) {
1080  m_compCloud->showSF(oldSfIdx >= 0);
1081  }
1082  }
1083  }
1084 
1086 
1087  releaseOctrees();
1088 
1089  reject();
1090 }
constexpr unsigned CV_LOCAL_MODEL_MIN_SIZE[]
Min number of points to compute local models (see CV_LOCAL_MODEL_TYPES)
Definition: CVConst.h:129
CV_LOCAL_MODEL_TYPES
Definition: CVConst.h:121
@ NO_MODEL
Definition: CVConst.h:122
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int count
core::Tensor result
Definition: VtkUtils.cpp:76
virtual void link()
Increase counter.
Definition: CVShareable.cpp:33
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static void UpdateUI()
Static shortcut to MainWindow::updateUI.
Bounding box structure.
Definition: ecvBBox.h:25
ccGenericPointCloud * m_refCloud
Reference entity equivalent cloud (if any)
void locaModelChanged(int)
QString m_sfName
last computed scalar field name
ccOctree::Shared m_refOctree
Reference entity's octree.
int determineBestOctreeLevel(double)
bool m_noDisplay
Whether a display is active (and should be refreshed) or not.
ccGenericMesh * m_refMesh
Reference entity equivalent mesh (if any)
CC_COMPARISON_TYPE
Comparison type.
ccHObject * m_refEnt
Reference entity.
bool m_refVisibility
Initial reference entity visibility.
ccOctree::Shared m_compOctree
Compared entity's octree.
ccComparisonDlg(ccHObject *compEntity, ccHObject *refEntity, CC_COMPARISON_TYPE cpType, QWidget *parent=nullptr, bool noDisplay=false)
Default constructor.
void updateDisplay(bool showSF, bool hideRef)
CC_COMPARISON_TYPE m_compType
Comparison type.
bool prepareEntitiesForComparison()
bool m_refOctreeIsPartial
Whether the reference entity octree is partial or not.
ccPointCloud * m_compCloud
Compared entity equivalent cloud.
bool m_compSFVisibility
Initial compared entity visibility.
bool m_compOctreeIsPartial
Whether the compared entity octree is partial or not.
int m_bestOctreeLevel
Best octree level (or 0 if none has been guessed already)
~ccComparisonDlg()
Default destructor.
ccHObject * m_compEnt
Compared entity.
QString m_oldSfName
Initial SF name enabled on the compared entity.
std::vector< PointCoordinateType > zBuff
Z-Buffer grid.
virtual bool isVisible() const
Returns whether entity is visible or not.
virtual void setVisible(bool state)
Sets entity visibility.
virtual bool sfShown() const
Returns whether active scalar field is visible.
virtual void showSF(bool state)
Sets active scalarfield visibility.
Ground-based Laser sensor.
Definition: ecvGBLSensor.h:26
bool computeDepthBuffer(cloudViewer::GenericCloud *cloud, int &errorCode, ccPointCloud *projectedCloud=nullptr)
static QString GetErrorString(int errorCode)
Returns the error string corresponding to an error code.
const ccDepthBuffer & getDepthBuffer() const
Returns the associated depth buffer.
Definition: ecvGBLSensor.h:225
Generic mesh interface.
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
static ccGenericMesh * ToGenericMesh(ccHObject *obj)
Converts current object to ccGenericMesh (if possible)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual ccBBox getOwnBB(bool withGLFeatures=false)
Returns the entity's own bounding-box.
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
void setRedrawFlagRecursive(bool redraw=false)
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
Encapsulating dialog for ccHistogramWindow.
ccHistogramWindow * window()
Returns encapsulated ccHistogramWindow.
Histogram widget.
void setTitle(const QString &str)
Sets title.
void fromSF(ccScalarField *sf, unsigned initialNumberOfClasses=0, bool numberOfClassesCanBeChanged=true, bool showNaNValuesInGrey=true)
Computes histogram from a scalar field.
void setAxisLabels(const QString &xLabel, const QString &yLabel)
Sets axis labels.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
virtual bool isEnabled() const
Returns whether the object is enabled or not.
Definition: ecvObject.h:97
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
Octree structure.
Definition: ecvOctree.h:27
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void enableVisibilityCheck(bool state)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
bool hasSensor() const
Returns whether the mesh as an associated sensor or not.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
int getCurrentDisplayedScalarFieldIndex() const
Returns the currently displayed scalar field index (or -1 if none)
void deleteScalarField(int index) override
Deletes a specific scalar field.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
A scalar field associated to display-related parameters.
T getDiagNorm() const
Returns diagonal length.
Definition: BoundingBox.h:172
unsigned CellCode
Type of the code of an octree cell.
Definition: DgmOctree.h:78
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
Definition: DgmOctree.h:67
std::vector< IndexAndCode > cellsContainer
Container of 'IndexAndCode' structures.
Definition: DgmOctree.h:351
static unsigned char GET_BIT_SHIFT(unsigned char level)
Returns the binary shift for a given level of subdivision.
Definition: DgmOctree.cpp:113
static int computeApproxCloud2CloudDistance(GenericIndexedCloudPersist *comparedCloud, GenericIndexedCloudPersist *referenceCloud, unsigned char octreeLevel, PointCoordinateType maxSearchDist=0, GenericProgressCallback *progressCb=nullptr, DgmOctree *compOctree=nullptr, DgmOctree *refOctree=nullptr)
Computes approximate distances between two point clouds.
static int computeCloud2MeshDistances(GenericIndexedCloudPersist *pointCloud, GenericIndexedMesh *mesh, Cloud2MeshDistancesComputationParams &params, GenericProgressCallback *progressCb=nullptr, DgmOctree *cloudOctree=nullptr)
Computes the distance between a point cloud and a mesh.
static int computeCloud2CloudDistances(GenericIndexedCloudPersist *comparedCloud, GenericIndexedCloudPersist *referenceCloud, Cloud2CloudDistancesComputationParams &params, GenericProgressCallback *progressCb=nullptr, DgmOctree *compOctree=nullptr, DgmOctree *refOctree=nullptr)
A generic mesh with index-based vertex access.
virtual unsigned size() const =0
Returns the number of triangles.
static double computeMeshArea(GenericMesh *mesh)
Computes the mesh area.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
unsigned size() const override
Definition: PointCloudTpl.h:38
bool renameScalarField(int index, const char *newName)
Renames a specific scalar field.
const char * getScalarFieldName(int index) const
Returns the name of a specific scalar field.
ScalarField * getCurrentInScalarField() const
Returns the scalar field currently associated to the cloud input.
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
ScalarType getMin() const
Returns the minimum value.
Definition: ScalarField.h:72
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void computeMeanAndVariance(ScalarType &mean, ScalarType *variance=nullptr) const
Definition: ScalarField.cpp:41
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
void setName(const char *name)
Sets scalar field name.
Definition: ScalarField.cpp:22
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Definition: ScalarField.cpp:81
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
static void SetRedrawRecursive(bool redraw=false)
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
Graphical progress indicator (thread-safe)
int max(int a, int b)
Definition: cutil_math.h:48
#define CC_CLOUD2MESH_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:16
#define CC_CLOUD2CLOUD_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:12
#define CC_CLOUD2MESH_SIGNED_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:17
#define CC_TEMP_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:14
#define CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:13
const unsigned char DEFAULT_OCTREE_LEVEL
@ MESH
Definition: CVTypes.h:105
@ GBL_SENSOR
Definition: CVTypes.h:117
@ POINT_CLOUD
Definition: CVTypes.h:104
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
cloudViewer::NormalizedProgress * nProgress
unsigned char octreeLevel
Cloud-to-cloud "Hausdorff" distance computation parameters.
ReferenceCloud * CPSet
Container of (references to) points to store the "Closest Point Set".
ScalarType radiusForLocalModel
Radius for nearest neighbours search (local model)
bool reuseExistingLocalModels
Whether to use an approximation for local model computation.
unsigned kNNForLocalModel
Number of neighbours for nearest neighbours search (local model)
ScalarType maxSearchDist
Maximum search distance (true distance won't be computed if greater)
ScalarField * splitDistances[3]
Split distances (one scalar field per dimension: X, Y and Z)