ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvEntityAction.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 <QColorDialog>
9 #include <QElapsedTimer>
10 #include <QInputDialog>
11 #include <QMessageBox>
12 #include <QPushButton>
13 
14 // CV_CORE_LIB
15 #include <NormalDistribution.h>
16 #include <ScalarFieldTools.h>
18 #include <WeibullDistribution.h>
19 
20 // CV_DB_LIB
21 #include <ecvColorScalesManager.h>
22 #include <ecvDisplayTools.h>
23 #include <ecvFacet.h>
24 #include <ecvGenericPrimitive.h>
25 #include <ecvGuiParameters.h>
26 #include <ecvOctreeProxy.h>
27 #include <ecvPointCloud.h>
29 #include <ecvPolyline.h>
30 #include <ecvSensor.h>
31 
32 // common
33 #include <ecvPickOneElementDlg.h>
34 
35 // Local
39 #include "ecvColorGradientDlg.h"
40 #include "ecvColorLevelsDlg.h"
41 #include "ecvCommon.h"
42 #include "ecvComputeOctreeDlg.h"
43 #include "ecvConsole.h"
44 #include "ecvEntityAction.h"
45 #include "ecvExportCoordToSFDlg.h"
46 #include "ecvHistogramWindow.h"
47 #include "ecvInterpolationDlg.h"
48 #include "ecvItemSelectionDlg.h"
49 #include "ecvLibAlgorithms.h"
51 #include "ecvOrderChoiceDlg.h"
52 #include "ecvProgressDialog.h"
56 #include "ecvStatisticalTestDlg.h"
57 #include "ecvUtils.h"
58 
59 // This is included only for temporarily removing an object from the tree.
60 // TODO figure out a cleaner way to do this without having to include all
61 // of MainWindow.h
62 #include "MainWindow.h"
63 
64 // SYSTEM
65 #include <algorithm>
66 
67 namespace ccEntityAction {
68 static QString GetFirstAvailableSFName(const ccPointCloud* cloud,
69  const QString& baseName) {
70  if (cloud == nullptr) {
71  Q_ASSERT(false);
72  return QString();
73  }
74 
75  QString name = baseName;
76  int tries = 0;
77 
78  while (cloud->getScalarFieldIndexByName(qPrintable(name)) >= 0 ||
79  tries > 99)
80  name = QString("%1 #%2").arg(baseName).arg(++tries);
81 
82  if (tries > 99) return QString();
83 
84  return name;
85 }
86 
88 // Colours
89 bool setColor(ccHObject::Container selectedEntities,
90  bool colorize,
91  QWidget* parent) {
92  QColor colour = QColorDialog::getColor(Qt::white, parent);
93 
94  if (!colour.isValid()) return false;
95 
96  while (!selectedEntities.empty()) {
97  ccHObject* ent = selectedEntities.back();
98  selectedEntities.pop_back();
99  if (ent->isA(CV_TYPES::HIERARCHY_OBJECT)) {
100  // automatically parse a group's children set
101  for (unsigned i = 0; i < ent->getChildrenNumber(); ++i)
102  selectedEntities.push_back(ent->getChild(i));
103  } else if (ent->isA(CV_TYPES::POINT_CLOUD) ||
104  ent->isA(CV_TYPES::MESH)) {
105  ccPointCloud* cloud = nullptr;
106  if (ent->isA(CV_TYPES::POINT_CLOUD)) {
107  cloud = static_cast<ccPointCloud*>(ent);
108  } else {
109  ccMesh* mesh = static_cast<ccMesh*>(ent);
110  ccGenericPointCloud* vertices = mesh->getAssociatedCloud();
111  if (!vertices || !vertices->isA(CV_TYPES::POINT_CLOUD) ||
112  (vertices->isLocked() && !mesh->isAncestorOf(vertices))) {
114  QString("[SetColor] Can't set color for mesh '%1' "
115  "(vertices are not accessible)")
116  .arg(ent->getName()));
117  continue;
118  }
119 
120  cloud = static_cast<ccPointCloud*>(vertices);
121  }
122 
123  if (colorize) {
124  cloud->colorize(static_cast<float>(colour.redF()),
125  static_cast<float>(colour.greenF()),
126  static_cast<float>(colour.blueF()));
127  } else {
128  cloud->setRGBColor(ecvColor::FromQColor(colour));
129  }
130  cloud->showColors(true);
131  cloud->showSF(false); // just in case
132 
133  if (ent != cloud) {
134  ent->showColors(true);
135  } else if (cloud->getParent() &&
136  cloud->getParent()->isKindOf(CV_TYPES::MESH)) {
137  cloud->getParent()->showColors(true);
138  cloud->getParent()->showSF(false); // just in case
139  }
140 
143  } else if (ent->isKindOf(CV_TYPES::PRIMITIVE)) {
145  ecvColor::Rgb col(static_cast<ColorCompType>(colour.red()),
146  static_cast<ColorCompType>(colour.green()),
147  static_cast<ColorCompType>(colour.blue()));
148  prim->setColor(col);
149  ent->showColors(true);
150  ent->showSF(false); // just in case
151  PROPERTY_PARAM params(ent, col);
153  } else if (ent->isA(CV_TYPES::POLY_LINE)) {
155  poly->setColor(ecvColor::FromQColor(colour));
156  ent->showColors(true);
157  ent->showSF(false); // just in case
158  if (!poly->isClosed()) {
160  poly->setRedrawFlagRecursive(true);
162 
163  } else {
164  PROPERTY_PARAM params(ent, poly->getColor());
166  }
167  } else if (ent->isA(CV_TYPES::FACET)) {
168  ccFacet* facet = ccHObjectCaster::ToFacet(ent);
169  facet->setColor(ecvColor::FromQColor(colour));
170  ent->showColors(true);
171  ent->showSF(false); // just in case
173  facet->setRedrawFlagRecursive(true);
175  } else {
177  QString("[SetColor] Can't change color of entity '%1'")
178  .arg(ent->getName()));
179  }
180  }
181 
182  return true;
183 }
184 
185 bool rgbToGreyScale(const ccHObject::Container& selectedEntities) {
186  for (ccHObject* ent : selectedEntities) {
187  bool lockedVertices = false;
188  ccGenericPointCloud* cloud =
189  ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
190  if (lockedVertices) {
192  ent->getName(), selectedEntities.size() == 1);
193  continue;
194  }
195 
196  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) {
197  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
198  if (pc->hasColors()) {
199  pc->convertRGBToGreyScale();
200  pc->showColors(true);
201  pc->showSF(false); // just in case
202  pc->setRedrawFlagRecursive(true);
203  }
204  }
205  }
206 
207  return true;
208 }
209 
210 bool setColorGradient(const ccHObject::Container& selectedEntities,
211  QWidget* parent) {
212  ccColorGradientDlg dlg(parent);
213  if (!dlg.exec()) return false;
214 
215  unsigned char dim = dlg.getDimension();
217 
218  ccColorScale::Shared colorScale(nullptr);
219  if (ramp == ccColorGradientDlg::Default) {
221  } else if (ramp == ccColorGradientDlg::TwoColors) {
222  colorScale = ccColorScale::Create("Temp scale");
223  QColor first, second;
224  dlg.getColors(first, second);
225  colorScale->insert(ccColorScaleElement(0.0, first), false);
226  colorScale->insert(ccColorScaleElement(1.0, second), true);
227  }
228 
229  Q_ASSERT(colorScale || ramp == ccColorGradientDlg::Banding);
230 
231  const double frequency = dlg.getBandingFrequency();
232 
233  for (ccHObject* ent : selectedEntities) {
234  bool lockedVertices = false;
235  ccGenericPointCloud* cloud =
236  ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
237  if (lockedVertices) {
239  ent->getName(), selectedEntities.size() == 1);
240  continue;
241  }
242 
243  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) // TODO
244  {
245  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
246 
247  bool success = false;
248  if (ramp == ccColorGradientDlg::Banding)
249  success = pc->setRGBColorByBanding(dim, frequency);
250  else
251  success = pc->setRGBColorByHeight(dim, colorScale);
252 
253  if (success) {
254  ent->showColors(true);
255  ent->showSF(false); // just in case
256  ent->setRedrawFlagRecursive(true);
257  }
258  }
259  }
260 
261  return true;
262 }
263 
264 bool changeColorLevels(const ccHObject::Container& selectedEntities,
265  QWidget* parent) {
266  if (selectedEntities.size() != 1) {
267  ecvConsole::Error("Select one and only one colored cloud or mesh!");
268  return false;
269  }
270 
271  bool lockedVertices;
272  ccPointCloud* pointCloud =
273  ccHObjectCaster::ToPointCloud(selectedEntities[0], &lockedVertices);
274  if (!pointCloud || lockedVertices) {
275  if (lockedVertices && pointCloud)
276  ecvUtils::DisplayLockedVerticesWarning(pointCloud->getName(), true);
277  return false;
278  }
279 
280  if (!pointCloud->hasColors()) {
281  ecvConsole::Error("Selected entity has no colors!");
282  return false;
283  }
284 
285  ccColorLevelsDlg dlg(parent, pointCloud);
286  dlg.exec();
287 
288  return true;
289 }
290 
292 bool interpolateColors(const ccHObject::Container& selectedEntities,
293  QWidget* parent) {
294  if (selectedEntities.size() != 2) {
295  ecvConsole::Error("Select 2 entities (clouds or meshes)!");
296  return false;
297  }
298 
299  ccHObject* ent1 = selectedEntities[0];
300  ccHObject* ent2 = selectedEntities[1];
301 
304 
305  if (!cloud1 || !cloud2) {
306  ecvConsole::Error("Select 2 entities (clouds or meshes)!");
307  return false;
308  }
309 
310  if (!cloud1->hasColors() && !cloud2->hasColors()) {
312  "None of the selected entities has per-point or per-vertex "
313  "colors!");
314  return false;
315  } else if (cloud1->hasColors() && cloud2->hasColors()) {
317  "Both entities have colors! Remove the colors on the entity "
318  "you wish to import the colors to!");
319  return false;
320  }
321 
322  ccGenericPointCloud* source = cloud1;
323  ccGenericPointCloud* dest = cloud2;
324 
325  if (cloud2->hasColors()) {
326  std::swap(source, dest);
327  std::swap(cloud1, cloud2);
328  std::swap(ent1, ent2);
329  }
330 
331  if (!dest->isA(CV_TYPES::POINT_CLOUD)) {
333  "Destination cloud (or vertices) must be a real point cloud!");
334  return false;
335  }
336 
337  ecvProgressDialog pDlg(true, parent);
338 
339  if (static_cast<ccPointCloud*>(dest)->interpolateColorsFrom(source,
340  &pDlg)) {
341  ent2->showColors(true);
342  ent2->showSF(false); // just in case
343  ent2->setRedrawFlagRecursive(true);
344  } else {
345  ecvConsole::Error("An error occurred! (see console)");
346  }
347 
348  return true;
349 }
350 
352 bool interpolateSFs(const ccHObject::Container& selectedEntities,
353  ecvMainAppInterface* app) {
354  if (selectedEntities.size() != 2) {
355  ecvConsole::Error("Select 2 entities (clouds or meshes)!");
356  return false;
357  }
358 
359  ccHObject* ent1 = selectedEntities[0];
360  ccHObject* ent2 = selectedEntities[1];
361 
364 
365  if (!cloud1 || !cloud2) {
366  ecvConsole::Error("Select 2 entities (clouds or meshes)!");
367  return false;
368  }
369 
370  if (!cloud1->hasScalarFields() && !cloud2->hasScalarFields()) {
372  "None of the selected entities has per-point or per-vertex "
373  "colors!");
374  return false;
375  } else if (cloud1->hasScalarFields() && cloud2->hasScalarFields()) {
376  // ask the user to chose which will be the 'source' cloud
377  ccOrderChoiceDlg ocDlg(cloud1, "Source", cloud2, "Destination", app);
378  if (!ocDlg.exec()) {
379  // process cancelled by the user
380  return false;
381  }
382  if (cloud1 != ocDlg.getFirstEntity()) {
383  std::swap(cloud1, cloud2);
384  }
385  } else if (cloud2->hasScalarFields()) {
386  std::swap(cloud1, cloud2);
387  }
388 
389  ccPointCloud* source = cloud1;
390  ccPointCloud* dest = cloud2;
391 
392  // show the list of scalar fields available on the source point cloud
393  std::vector<int> sfIndexes;
394  try {
395  unsigned sfCount = source->getNumberOfScalarFields();
396  if (sfCount == 1) {
397  sfIndexes.push_back(0);
398  } else if (sfCount > 1) {
399  ccItemSelectionDlg isDlg(true, app->getMainWindow(), "entity");
400  QStringList scalarFields;
401  {
402  for (unsigned i = 0; i < sfCount; ++i) {
403  scalarFields << source->getScalarFieldName(i);
404  }
405  }
406  isDlg.setItems(scalarFields, 0);
407  if (!isDlg.exec()) {
408  // cancelled by the user
409  return false;
410  }
411  isDlg.getSelectedIndexes(sfIndexes);
412  if (sfIndexes.empty()) {
413  ecvConsole::Error("No scalar field was selected");
414  return false;
415  }
416  } else {
417  assert(false);
418  }
419  } catch (const std::bad_alloc&) {
420  ecvConsole::Error("Not enough memory");
421  return false;
422  }
423 
424  // semi-persistent parameters
425  static ccPointCloudInterpolator::Parameters::Method s_interpMethod =
427  static ccPointCloudInterpolator::Parameters::Algo s_interpAlgo =
429  static int s_interpKNN = 6;
430 
431  ccInterpolationDlg iDlg(app->getMainWindow());
432  iDlg.setInterpolationMethod(s_interpMethod);
433  iDlg.setInterpolationAlgorithm(s_interpAlgo);
434  iDlg.knnSpinBox->setValue(s_interpKNN);
435  iDlg.radiusDoubleSpinBox->setValue(dest->getOwnBB().getDiagNormd() / 100);
436 
437  if (!iDlg.exec()) {
438  // process cancelled by the user
439  return false;
440  }
441 
442  // setup parameters
444  params.method = s_interpMethod = iDlg.getInterpolationMethod();
445  params.algo = s_interpAlgo = iDlg.getInterpolationAlgorithm();
446  params.knn = s_interpKNN = iDlg.knnSpinBox->value();
447  params.radius = iDlg.radiusDoubleSpinBox->value();
448  params.sigma = iDlg.kernelDoubleSpinBox->value();
449 
450  ecvProgressDialog pDlg(true, app->getMainWindow());
451  unsigned sfCountBefore = dest->getNumberOfScalarFields();
452 
454  dest, source, sfIndexes, params, &pDlg)) {
456  static_cast<int>(std::min(sfCountBefore + 1,
457  dest->getNumberOfScalarFields())) -
458  1);
459  dest->showSF(true);
460  dest->setRedrawFlagRecursive(true);
461  } else {
462  ecvConsole::Error("An error occurred! (see console)");
463  }
464 
465  return true;
466 }
467 
468 bool convertTextureToColor(const ccHObject::Container& selectedEntities,
469  QWidget* parent) {
470  for (ccHObject* ent : selectedEntities) {
471  if (ent->isA(
472  CV_TYPES::
473  MESH) /*|| ent->isKindOf(CV_TYPES::PRIMITIVE)*/) // TODO
474  {
475  ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
476  Q_ASSERT(mesh);
477 
478  if (!mesh->hasMaterials()) {
479  CVLog::Warning(QString("[convertTextureToColor] Mesh '%1' has "
480  "no material/texture!")
481  .arg(mesh->getName()));
482  continue;
483  } else {
484  if (mesh->hasColors() &&
485  QMessageBox::warning(parent, "Mesh already has colors",
486  QString("Mesh '%1' already has "
487  "colors! Overwrite them?")
488  .arg(mesh->getName()),
489  QMessageBox::Yes | QMessageBox::No,
490  QMessageBox::No) != QMessageBox::Yes) {
491  continue;
492  }
493 
494  // ColorCompType
495  // C[3]={MAX_COLOR_COMP,MAX_COLOR_COMP,MAX_COLOR_COMP};
496  // mesh->getColorFromMaterial(triIndex,*P,C,withRGB);
497  // cloud->addRGBColor(C);
498  if (mesh->convertMaterialsToVertexColors()) {
499  mesh->showColors(true);
500  mesh->showSF(false); // just in case
501  mesh->showMaterials(false);
502  } else {
503  CVLog::Warning(QString("[convertTextureToColor] Failed to "
504  "convert texture on mesh '%1'!")
505  .arg(mesh->getName()));
506  }
507  }
508  }
509  }
510 
511  return true;
512 }
513 
514 bool enhanceRGBWithIntensities(const ccHObject::Container& selectedEntities,
515  QWidget* parent) {
516  QString defaultSFName("Intensity");
517 
518  bool useCustomIntensityRange = false;
519  static double s_minI = 0.0, s_maxI = 1.0;
520  if (QMessageBox::question(parent, "Intensity range",
521  "Do you want to define the theoretical intensity "
522  "range (yes)\nor use the actual one (no)?",
523  QMessageBox::Yes,
524  QMessageBox::No) == QMessageBox::Yes) {
525  ccAskTwoDoubleValuesDlg atdvDlg("Min", "Max", -1000000.0, 1000000.0,
526  s_minI, s_maxI, 3,
527  "Theroetical intensity", parent);
528  if (!atdvDlg.exec()) {
529  // process cancelled by the user
530  return false;
531  }
532  s_minI = atdvDlg.doubleSpinBox1->value();
533  s_maxI = atdvDlg.doubleSpinBox2->value();
534  useCustomIntensityRange = true;
535  }
536 
537  for (ccHObject* ent : selectedEntities) {
538  bool lockedVertices = false;
539  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
540  if (!pc || lockedVertices) {
542  ent->getName(), selectedEntities.size() == 1);
543  continue;
544  }
545 
546  if (!pc->hasColors()) {
547  CVLog::Warning(QString("[enhanceRGBWithIntensities] Entity '%1' "
548  "has no RGB color!")
549  .arg(ent->getName()));
550  continue;
551  }
552  if (!pc->hasScalarFields()) {
553  CVLog::Warning(QString("[enhanceRGBWithIntensities] Entity '%1' "
554  "has no scalar field!")
555  .arg(ent->getName()));
556  continue;
557  }
558 
559  int sfIdx = -1;
560  if (pc->getNumberOfScalarFields() > 1) {
561  // does the previously selected SF works?
562  if (!defaultSFName.isEmpty()) {
563  // if it's valid, we'll keep this SF!
564  sfIdx = pc->getScalarFieldIndexByName(
565  qPrintable(defaultSFName));
566  }
567  if (sfIdx < 0) {
568  // let the user choose the right scalar field
569  ccPickOneElementDlg poeDlg("Intensity scalar field",
570  "Choose scalar field", parent);
571  for (unsigned i = 0; i < pc->getNumberOfScalarFields(); ++i) {
573  assert(sf);
574  QString sfName(sf->getName());
575  poeDlg.addElement(sfName);
576  if (sfIdx < 0 &&
577  sfName.contains("intensity", Qt::CaseInsensitive)) {
578  sfIdx = static_cast<int>(i);
579  }
580  }
581 
582  poeDlg.setDefaultIndex(std::max(0, sfIdx));
583  if (!poeDlg.exec()) {
584  // process cancelled by the user
585  return false;
586  }
587  sfIdx = poeDlg.getSelectedIndex();
588  defaultSFName = pc->getScalarField(sfIdx)->getName();
589  }
590  } else {
591  sfIdx = 0;
592  }
593  assert(sfIdx >= 0);
594 
595  if (pc->enhanceRGBWithIntensitySF(sfIdx, useCustomIntensityRange,
596  s_minI, s_maxI)) {
597  ent->showColors(true);
598  ent->showSF(false);
599  ent->setRedrawFlagRecursive(true);
600  } else {
601  CVLog::Warning(QString("[enhanceRGBWithIntensities] Failed to "
602  "apply the process on entity '%1'!")
603  .arg(ent->getName()));
604  }
605  }
606 
607  return true;
608 }
609 
610 bool rgbGaussianFilter(const ccHObject::Container& selectedEntities,
611  ccPointCloud::RgbFilterOptions filterParams,
612  QWidget* parent /*=nullptr*/) {
613  if (selectedEntities.empty()) {
614  return false;
615  }
616 
617  // select only the clouds (or vertices) with RGB colors
618  std::vector<std::pair<ccHObject*, ccPointCloud*>> selectedCloudsWithColors;
619  double spatialSigma = std::numeric_limits<double>::max();
620 
621  for (ccHObject* ent : selectedEntities) {
622  bool lockedVertices = false;
623  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
624  if (!pc || lockedVertices) {
626  ent->getName(), selectedEntities.size() == 1);
627  continue;
628  }
629 
630  // check if the cloud has color
631  if (pc->hasColors()) {
632  if ((filterParams.filterType ==
633  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) &&
634  !pc->hasDisplayedScalarField()) {
635  continue;
636  }
637 
638  selectedCloudsWithColors.push_back({ent, pc});
639 
640  double sigmaCloud = ccLibAlgorithms::GetDefaultCloudKernelSize(pc);
641 
642  // we keep the smallest value
643  if (sigmaCloud < spatialSigma) {
644  spatialSigma = sigmaCloud;
645  }
646  }
647  }
648 
649  if (filterParams.spatialSigma > 0) {
650  spatialSigma = filterParams.spatialSigma;
651  }
652 
653  if (selectedCloudsWithColors.empty()) {
654  if (filterParams.filterType ==
655  ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
657  "Select at least one cloud or mesh with RGB "
658  "colors and an active scalar field");
659  else
661  "Select at least one cloud or mesh with RGB colors");
662  return false;
663  }
664 
665  double sigmaSF = -1.0;
666  if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
668  selectedCloudsWithColors.front()
669  .second->getCurrentDisplayedScalarField();
670  if (sf) {
671  ScalarType sfRange = sf->getMax() - sf->getMin();
672  sigmaSF = sfRange / 4; // using 1/4 of total range
673  }
674  if (filterParams.sigmaSF > 0) {
675  sigmaSF = filterParams.sigmaSF;
676  }
677  }
678 
679  QScopedPointer<ecvProgressDialog> pDlg;
680  if (!filterParams.commandLine) {
681  bool ok = false;
682 
683  if (filterParams.filterType ==
684  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
686  "Spatial sigma", "Scalar sigma", "Color threshold", DBL_MIN,
687  1.0e9, spatialSigma, sigmaSF,
688  filterParams.burntOutColorThreshold, 8, nullptr, parent);
689 
690  dlg.setWindowTitle("RGB bilateral filter");
691 
692  dlg.doubleSpinBox1->setStatusTip("3*sigma = 99.7% attenuation");
693  dlg.doubleSpinBox2->setStatusTip(
694  "Scalar sigma controls how much the filter behaves as a "
695  "Gaussian Filter\nSigma at +inf uses the whole range of "
696  "scalars");
697  dlg.doubleSpinBox3->setStatusTip(
698  "For averaging, it will only use colors for which all "
699  "components are in the range[threshold:255 - threshold]");
700  if (!dlg.exec()) {
701  return false;
702  }
703 
704  // get values
705  spatialSigma = dlg.doubleSpinBox1->value();
706  sigmaSF = dlg.doubleSpinBox2->value();
707  filterParams.burntOutColorThreshold = dlg.doubleSpinBox3->value();
708  } else {
709  ccAskTwoDoubleValuesDlg dlg("Spatial sigma", "Color threshold",
710  DBL_MIN, 1.0e9, spatialSigma,
711  filterParams.burntOutColorThreshold, 8,
712  nullptr, parent);
713  dlg.setWindowTitle("RGB gaussian/mean/median filter");
714 
715  dlg.doubleSpinBox1->setStatusTip("3*sigma = 99.7% attenuation");
716  dlg.doubleSpinBox2->setStatusTip(
717  "For averaging, it will only use colors for which all "
718  "components are in the range [threshold:255-threshold]");
719  if (!dlg.exec()) {
720  return false;
721  }
722 
723  // get values
724  spatialSigma = dlg.doubleSpinBox1->value();
725  filterParams.burntOutColorThreshold = dlg.doubleSpinBox2->value();
726  }
727  }
728 
729  if (parent) {
730  pDlg.reset(new ecvProgressDialog(true, parent));
731  pDlg->setAutoClose(false);
732  }
733 
734  for (auto entAndPC : selectedCloudsWithColors) {
735  ccPointCloud* pc = entAndPC.second;
736  assert(pc);
737  int sfIdx = 0;
738  if ((filterParams.filterType ==
739  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) ||
740  filterParams.applyToSFduringRGB) {
741  // we set the displayed SF as "OUT" SF
742  int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
743  Q_ASSERT(outSfIdx >= 0);
744 
745  pc->setCurrentOutScalarField(outSfIdx);
746 
747  if (filterParams.applyToSFduringRGB) {
748  cloudViewer::ScalarField* outSF =
750  Q_ASSERT(outSF != nullptr);
751  QString sfName;
752  if (filterParams.filterType ==
753  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
754  sfName = QString("%1.bilsmooth(%2,%3)")
755  .arg(outSF->getName())
756  .arg(spatialSigma)
757  .arg(sigmaSF);
758  } else if (filterParams.filterType ==
759  ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN) {
760  sfName = QString("%1.smooth(%2)")
761  .arg(outSF->getName())
762  .arg(spatialSigma);
763  } else if (filterParams.filterType ==
764  ccPointCloud::RGB_FILTER_TYPES::MEAN) {
765  sfName = QString("%1.meansmooth(%2)")
766  .arg(outSF->getName())
767  .arg(spatialSigma);
768  } else {
769  sfName = QString("%1.medsmooth(%2)")
770  .arg(outSF->getName())
771  .arg(spatialSigma);
772  }
773 
774  sfIdx = pc->getScalarFieldIndexByName(qPrintable(sfName));
775  if (sfIdx < 0)
776  sfIdx = pc->addScalarField(
777  qPrintable(sfName)); // output SF has same type as
778  // input SF
779  if (sfIdx >= 0)
780  pc->setCurrentInScalarField(sfIdx);
781  else {
783  QString("Failed to create scalar field for cloud "
784  "'%1' (not enough memory?)")
785  .arg(pc->getName()));
786  return false;
787  }
788  }
789  }
790 
792  if (!octree) {
793  octree = pc->computeOctree(parent ? pDlg.data() : nullptr);
794  if (!octree) {
796  QString("Couldn't compute octree for cloud '%1'!")
797  .arg(pc->getName()));
798  continue;
799  }
800  }
801 
802  QElapsedTimer eTimer;
803  eTimer.start();
804  pc->applyFilterToRGB(static_cast<PointCoordinateType>(spatialSigma),
805  static_cast<PointCoordinateType>(sigmaSF),
806  filterParams, parent ? pDlg.data() : nullptr);
807  ecvConsole::Print("[RGBFilter] Timing: %3.2f s.",
808  static_cast<double>(eTimer.elapsed()) / 1000.0);
809 
810  if (filterParams.applyToSFduringRGB) {
811  // calc sf min/max for correct display.
813  pc->showSF(sfIdx >= 0);
815  if (sf) sf->computeMinAndMax();
816  }
817  // automatically hide any SF and show the colors instead
818  // entAndPC.first->prepareDisplayForRefresh_recursive();
819  entAndPC.first->showColors(true);
820  entAndPC.first->showSF(false);
821  }
822 
823  return true;
824 }
825 
827 // Scalar Fields
828 
829 bool sfGaussianFilter(const ccHObject::Container& selectedEntities,
830  ccPointCloud::RgbFilterOptions filterParams,
831  QWidget* parent /*=nullptr*/) {
832  if (selectedEntities.empty()) return false;
833 
834  double spatialSigma = filterParams.spatialSigma == -1
836  selectedEntities)
837  : filterParams.spatialSigma;
838  if (spatialSigma < 0.0) {
839  ecvConsole::Error("No eligible point cloud in selection!");
840  return false;
841  }
842 
843  // estimate a good value for scalar field sigma, based on the first cloud
844  // and its displayed scalar field
845  double scalarFieldSigma = -1.0;
846  if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
847  ccPointCloud* testPC =
848  ccHObjectCaster::ToPointCloud(selectedEntities.front());
849  cloudViewer::ScalarField* testSF =
851  if (!testSF) {
852  ecvConsole::Error("No active scalar field");
853  return false;
854  }
855  if (filterParams.sigmaSF == -1) {
856  ScalarType range = testSF->getMax() - testSF->getMin();
857  scalarFieldSigma = range / 4; // using 1/4 of total range
858  } else {
859  scalarFieldSigma = filterParams.sigmaSF;
860  }
861  }
862 
863  if (!filterParams.commandLine) {
864  if (filterParams.filterType ==
865  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
866  ccAskTwoDoubleValuesDlg dlg("Spatial sigma", "Scalar sigma",
867  DBL_MIN, 1.0e9, spatialSigma,
868  scalarFieldSigma, 8, nullptr, parent);
869  dlg.doubleSpinBox1->setStatusTip("3*sigma = 98% attenuation");
870  dlg.doubleSpinBox2->setStatusTip(
871  "Scalar field's sigma controls how much the filter behaves "
872  "as a "
873  "Gaussian Filter\n sigma at +inf uses the whole range of "
874  "scalars");
875  if (!dlg.exec()) return false;
876 
877  spatialSigma = dlg.doubleSpinBox1->value();
878  scalarFieldSigma = dlg.doubleSpinBox2->value();
879  } else {
880  bool ok = false;
881  spatialSigma = QInputDialog::getDouble(parent, "Gaussian filter",
882  "sigma:", spatialSigma,
883  DBL_MIN, 1.0e9, 8, &ok);
884  if (!ok) return false;
885  }
886  }
887 
888  ecvProgressDialog pDlg(true, parent);
889  pDlg.setAutoClose(false);
890 
891  for (ccHObject* ent : selectedEntities) {
892  bool lockedVertices = false;
893  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
894  if (!pc || lockedVertices) {
896  ent->getName(), selectedEntities.size() == 1);
897  continue;
898  }
899 
900  // la methode est activee sur le champ scalaire affiche
902  if (sf != nullptr) {
903  // on met en lecture (OUT) le champ scalaire actuellement affiche
904  int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
905  Q_ASSERT(outSfIdx >= 0);
906 
907  pc->setCurrentOutScalarField(outSfIdx);
909  Q_ASSERT(sf != nullptr);
910 
911  QString sfName;
912  if (filterParams.filterType ==
913  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
914  sfName = QString("%1.bilsmooth(%2,%3)")
915  .arg(outSF->getName())
916  .arg(spatialSigma)
917  .arg(scalarFieldSigma);
918  } else {
919  sfName = QString("%1.smooth(%2)")
920  .arg(outSF->getName())
921  .arg(spatialSigma);
922  }
923  int sfIdx = pc->getScalarFieldIndexByName(qPrintable(sfName));
924  if (sfIdx < 0)
925  sfIdx = pc->addScalarField(qPrintable(
926  sfName)); // output SF has same type as input SF
927  if (sfIdx >= 0)
928  pc->setCurrentInScalarField(sfIdx);
929  else {
930  ecvConsole::Error(QString("Failed to create scalar field for "
931  "cloud '%1' (not enough memory?)")
932  .arg(pc->getName()));
933  continue;
934  }
935 
937  if (!octree) {
938  octree = pc->computeOctree(&pDlg);
939  if (!octree) {
941  QString("Couldn't compute octree for cloud '%1'!")
942  .arg(pc->getName()));
943  continue;
944  }
945  }
946 
947  if (octree) {
948  QString methodTitle =
949  (filterParams.filterType ==
950  ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
951  ? QObject::tr("Bilateral filter")
952  : QObject::tr("Gaussian filter");
953  pDlg.setMethodTitle(methodTitle);
954  pDlg.setInfo(QObject::tr("Processing cloud '%1'")
955  .arg(pc->getName()));
956  pDlg.start();
957  QCoreApplication::processEvents();
958 
959  QElapsedTimer eTimer;
960  eTimer.start();
961 
962  int result;
963  if (filterParams.filterType ==
964  ccPointCloud::RGB_FILTER_TYPES::BILATERAL) {
967  static_cast<PointCoordinateType>(
968  spatialSigma),
969  pc, scalarFieldSigma, &pDlg, octree.data());
970  } else {
973  static_cast<PointCoordinateType>(
974  spatialSigma),
975  pc, -1, &pDlg, octree.data());
976  }
977 
979  "[%s] Timing: %3.2f s.", qPrintable(methodTitle),
980  static_cast<double>(eTimer.elapsed()) / 1000.0);
981 
982  if (result >= 0) {
984  pc->showSF(sfIdx >= 0);
985  sf = pc->getCurrentInScalarField();
986  if (sf) sf->computeMinAndMax();
987  } else {
988  QString errorMsg =
989  (filterParams.filterType ==
990  ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
991  ? QString("Failed to apply Bilateral "
992  "filter to cloud '%1'")
993  : QString("Failed to apply Gaussian filter "
994  "to cloud '%1'");
995  ecvConsole::Error(errorMsg.arg(pc->getName()));
996  }
997  } else {
998  ecvConsole::Error(QString("Failed to compute entity [%1] "
999  "octree! (not enough memory?)")
1000  .arg(pc->getName()));
1001  }
1002  } else {
1004  QString("Entity [%1] has no active scalar field!")
1005  .arg(pc->getName()));
1006  }
1007  }
1008 
1009  return true;
1010 }
1011 
1012 // Legacy wrapper function for backward compatibility
1013 bool sfBilateralFilter(const ccHObject::Container& selectedEntities,
1014  QWidget* parent) {
1015  ccPointCloud::RgbFilterOptions filterParams;
1016  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::BILATERAL;
1017  return sfGaussianFilter(selectedEntities, filterParams, parent);
1018 }
1019 
1020 bool sfConvertToRGB(const ccHObject::Container& selectedEntities,
1021  QWidget* parent) {
1022  // we first ask the user if the SF colors should be mixed with existing
1023  // colors
1024  bool mixWithExistingColors = false;
1025 
1026  QMessageBox::StandardButton answer = QMessageBox::warning(
1027  parent, "Scalar Field to RGB", "Mix with existing colors (if any)?",
1028  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1029  QMessageBox::Yes);
1030  if (answer == QMessageBox::Yes)
1031  mixWithExistingColors = true;
1032  else if (answer == QMessageBox::Cancel)
1033  return false;
1034 
1035  for (ccHObject* ent : selectedEntities) {
1036  ccGenericPointCloud* cloud = nullptr;
1037 
1038  bool lockedVertices = false;
1039  cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
1040  if (lockedVertices) {
1042  ent->getName(), selectedEntities.size() == 1);
1043  continue;
1044  }
1045  if (cloud != nullptr) // TODO
1046  {
1047  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1048  // if there is no displayed SF --> nothing to do!
1049  if (pc->getCurrentDisplayedScalarField()) {
1051  mixWithExistingColors)) {
1052  ent->showColors(true);
1053  ent->showSF(false); // just in case
1054  ent->setRedrawFlagRecursive(true);
1055  }
1056  }
1057  }
1058  }
1059 
1060  return true;
1061 }
1062 
1063 bool sfConvertToRandomRGB(const ccHObject::Container& selectedEntities,
1064  QWidget* parent) {
1065  static int s_randomColorsNumber = 256;
1066 
1067  bool ok = false;
1068  s_randomColorsNumber =
1069  QInputDialog::getInt(parent, "Random colors",
1070  "Number of random colors (will be regularly "
1071  "sampled over the SF interval):",
1072  s_randomColorsNumber, 2, INT_MAX, 16, &ok);
1073  if (!ok) return false;
1074  Q_ASSERT(s_randomColorsNumber > 1);
1075 
1076  ColorsTableType* randomColors = new ColorsTableType;
1077  if (!randomColors->reserveSafe(
1078  static_cast<unsigned>(s_randomColorsNumber))) {
1079  ecvConsole::Error("Not enough memory!");
1080  return false;
1081  }
1082 
1083  // generate random colors
1084  for (int i = 0; i < s_randomColorsNumber; ++i) {
1086  randomColors->addElement(col);
1087  }
1088 
1089  // apply random colors
1090  for (ccHObject* ent : selectedEntities) {
1091  ccGenericPointCloud* cloud = nullptr;
1092 
1093  bool lockedVertices = false;
1094  cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
1095  if (lockedVertices) {
1097  ent->getName(), selectedEntities.size() == 1);
1098  continue;
1099  }
1100  if (cloud != nullptr) // TODO
1101  {
1102  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1104  // if there is no displayed SF --> nothing to do!
1105  if (sf && sf->currentSize() >= pc->size()) {
1106  if (!pc->resizeTheRGBTable(false)) {
1107  ecvConsole::Error("Not enough memory!");
1108  break;
1109  } else {
1110  ScalarType minSF = sf->getMin();
1111  ScalarType maxSF = sf->getMax();
1112 
1113  ScalarType step =
1114  (maxSF - minSF) / (s_randomColorsNumber - 1);
1115  if (step == 0) step = static_cast<ScalarType>(1.0);
1116 
1117  for (unsigned i = 0; i < pc->size(); ++i) {
1118  ScalarType val = sf->getValue(i);
1119  unsigned colIndex =
1120  static_cast<unsigned>((val - minSF) / step);
1121  if (colIndex == s_randomColorsNumber) --colIndex;
1122 
1123  pc->setPointColor(i, randomColors->getValue(colIndex));
1124  }
1125 
1126  pc->showColors(true);
1127  pc->showSF(false); // just in case
1128  }
1129  }
1130 
1131  cloud->setRedrawFlagRecursive(true);
1132  }
1133  }
1134 
1135  return true;
1136 }
1137 
1138 bool sfRename(const ccHObject::Container& selectedEntities, QWidget* parent) {
1139  for (ccHObject* ent : selectedEntities) {
1141  if (cloud != nullptr) // TODO
1142  {
1143  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1145  // if there is no displayed SF --> nothing to do!
1146  if (sf == nullptr) {
1148  QString("Cloud %1 has no displayed scalar field!")
1149  .arg(pc->getName()));
1150  } else {
1151  const char* sfName = sf->getName();
1152  bool ok = false;
1153  QString newName = QInputDialog::getText(
1154  parent, "SF name", "name:", QLineEdit::Normal,
1155  QString(sfName ? sfName : "unknown"), &ok);
1156  if (ok) sf->setName(qPrintable(newName));
1157  }
1158  }
1159  }
1160 
1161  return true;
1162 }
1163 
1164 bool sfAddIdField(const ccHObject::Container& selectedEntities) {
1165  for (ccHObject* ent : selectedEntities) {
1167  if (cloud != nullptr) // TODO
1168  {
1169  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1170 
1172  if (sfIdx < 0) sfIdx = pc->addScalarField(CC_DEFAULT_ID_SF_NAME);
1173  if (sfIdx < 0) {
1174  CVLog::Warning("Not enough memory!");
1175  return false;
1176  }
1177 
1178  cloudViewer::ScalarField* sf = pc->getScalarField(sfIdx);
1179  Q_ASSERT(sf->currentSize() == pc->size());
1180 
1181  for (unsigned j = 0; j < cloud->size(); j++) {
1182  ScalarType idValue = static_cast<ScalarType>(j);
1183  sf->setValue(j, idValue);
1184  }
1185 
1186  sf->computeMinAndMax();
1187  pc->setCurrentDisplayedScalarField(sfIdx);
1188  pc->showSF(true);
1189  }
1190  }
1191 
1192  return true;
1193 }
1194 
1195 bool importToSF(const ccHObject::Container& selectedEntities,
1196  const std::vector<std::vector<ScalarType>>& scalarsVector,
1197  const std::string& name) {
1198  if (scalarsVector.size() != selectedEntities.size()) {
1199  return false;
1200  }
1201 
1202  size_t scalarIndex = 0;
1203  for (ccHObject* ent : selectedEntities) {
1204  const std::vector<ScalarType>& scalars = scalarsVector[scalarIndex++];
1205 
1207  if (cloud != nullptr) // TODO
1208  {
1209  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1210  if (scalars.size() != pc->size()) {
1211  CVLog::Warning(QString("input scalars size does not match "
1212  "entity[%1] points size, ignore it!")
1213  .arg(pc->getName()));
1214  continue;
1215  }
1216 
1217  int sfIdx = pc->getScalarFieldIndexByName(name.c_str());
1218  if (sfIdx < 0) sfIdx = pc->addScalarField(name.c_str());
1219  if (sfIdx < 0) {
1220  CVLog::Warning("Not enough memory!");
1221  return false;
1222  }
1223 
1224  cloudViewer::ScalarField* sf = pc->getScalarField(sfIdx);
1225  Q_ASSERT(sf->currentSize() == pc->size());
1226 
1227  for (unsigned j = 0; j < cloud->size(); j++) {
1228  sf->setValue(j, scalars[j]);
1229  }
1230 
1231  sf->computeMinAndMax();
1232  pc->setCurrentDisplayedScalarField(sfIdx);
1233  pc->showSF(true);
1234  }
1235  }
1236 
1237  return true;
1238 }
1239 
1241  QWidget* parent) {
1242  bool ok = false;
1243  double out = QInputDialog::getDouble(
1244  parent, QObject::tr("SF --> coordinate"),
1245  QObject::tr("Enter the coordinate equivalent to NaN values:"),
1246  minSFValue, -1.0e9, 1.0e9, 6, &ok);
1247 
1248  if (ok) {
1249  return static_cast<PointCoordinateType>(out);
1250  } else {
1251  CVLog::Warning(QObject::tr(
1252  "[SetSFAsCoord] By default the coordinate equivalent to NaN "
1253  "values will be the minimum SF value"));
1254  return minSFValue;
1255  }
1256 }
1257 
1258 bool sfSetAsCoord(const ccHObject::Container& selectedEntities,
1259  QWidget* parent) {
1260  ccExportCoordToSFDlg ectsDlg(parent);
1261  ectsDlg.warningLabel->setVisible(false);
1262  ectsDlg.setWindowTitle("Export SF to coordinate(s)");
1263 
1264  if (!ectsDlg.exec()) return false;
1265 
1266  bool importDim[3]{ectsDlg.exportX(), ectsDlg.exportY(), ectsDlg.exportZ()};
1267  if (!importDim[0] && !importDim[1] && !importDim[2]) // nothing to do?!
1268  {
1269  return false;
1270  }
1271 
1272  ScalarType defaultValueForNaN = NAN_VALUE;
1273  // for each selected cloud (or vertices set)
1274  for (ccHObject* ent : selectedEntities) {
1276  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) {
1277  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1278 
1280 
1281  if (sf != nullptr) {
1282  if (std::isnan(defaultValueForNaN) &&
1283  (sf->countValidValues() < sf->size())) {
1284  // we have some invalid values, let's ask the user what they
1285  // should be replaced with
1286  defaultValueForNaN =
1287  GetDefaultValueForNaN(sf->getMin(), parent);
1288  break;
1289  }
1290 
1291  pc->setCoordFromSF(importDim, sf, defaultValueForNaN);
1292  pc->invalidateBoundingBox();
1293  }
1294  }
1295 
1296  if (ent->isKindOf(CV_TYPES::MESH)) {
1297  static_cast<ccGenericMesh*>(ent)->refreshBB();
1298  }
1299  }
1300 
1301  return true;
1302 }
1303 
1304 bool exportCoordToSF(const ccHObject::Container& selectedEntities,
1305  QWidget* parent) {
1306  ccExportCoordToSFDlg ectsDlg(parent);
1307 
1308  if (!ectsDlg.exec()) {
1309  return false;
1310  }
1311 
1312  bool exportDims[3] = {ectsDlg.exportX(), ectsDlg.exportY(),
1313  ectsDlg.exportZ()};
1314 
1315  if (!exportDims[0] && !exportDims[1] && !exportDims[2]) // nothing to do?!
1316  {
1317  return false;
1318  }
1319 
1320  // for each selected cloud (or vertices set)
1321  for (ccHObject* entity : selectedEntities) {
1323  if (pc == nullptr) {
1324  // TODO do something with error?
1325  continue;
1326  }
1327 
1328  if (!pc->exportCoordToSF(exportDims)) {
1329  CVLog::Error("The process failed!");
1330  return true; // true because we want the UI to be updated anyway
1331  }
1332 
1333  if (entity != pc) {
1334  entity->showSF(true); // for meshes
1335  }
1336  }
1337 
1338  return true;
1339 }
1340 
1341 bool exportNormalToSF(const ccHObject::Container& selectedEntities,
1342  QWidget* parent,
1343  bool* exportDimensions /*=nullptr*/) {
1344  bool exportDims[3] = {false, false, false};
1345  if (exportDimensions) {
1346  exportDims[0] = exportDimensions[0];
1347  exportDims[1] = exportDimensions[1];
1348  exportDims[2] = exportDimensions[2];
1349  } else {
1350  // ask the user
1351  ccExportCoordToSFDlg ectsDlg(parent);
1352  ectsDlg.setWindowTitle(QObject::tr("Export normals to SF(s)"));
1353 
1354  if (!ectsDlg.exec()) {
1355  return false;
1356  }
1357 
1358  exportDims[0] = ectsDlg.exportX();
1359  exportDims[1] = ectsDlg.exportY();
1360  exportDims[2] = ectsDlg.exportZ();
1361  }
1362 
1363  if (!exportDims[0] && !exportDims[1] && !exportDims[2]) // nothing to do?!
1364  {
1365  return false;
1366  }
1367 
1368  // for each selected cloud (or vertices set)
1369  for (ccHObject* entity : selectedEntities) {
1371  if (pc == nullptr) {
1372  // TODO do something with error?
1373  continue;
1374  }
1375 
1376  if (!pc->hasNormals()) {
1378  QString("Cloud '%1' has no normals").arg(pc->getName()));
1379  continue;
1380  }
1381 
1382  if (!pc->exportNormalToSF(exportDims)) {
1383  CVLog::Error("The process failed!");
1384  return true; // true because we want the UI to be updated anyway
1385  }
1386 
1387  if (entity != pc) {
1388  entity->showSF(true); // for meshes
1389  }
1390  entity->setRedrawFlagRecursive(true);
1391  }
1392 
1393  return true;
1394 }
1395 
1396 bool sfArithmetic(const ccHObject::Container& selectedEntities,
1397  QWidget* parent) {
1398  Q_ASSERT(!selectedEntities.empty());
1399 
1400  ccHObject* entity = selectedEntities[0];
1401  bool lockedVertices;
1402  ccPointCloud* cloud =
1403  ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
1404  if (lockedVertices) {
1406  return false;
1407  }
1408  if (cloud == nullptr) {
1409  return false;
1410  }
1411 
1412  ccScalarFieldArithmeticsDlg sfaDlg(cloud, parent);
1413 
1414  if (!sfaDlg.exec()) {
1415  return false;
1416  }
1417 
1418  if (!sfaDlg.apply(cloud)) {
1419  ecvConsole::Error("An error occurred (see Console for more details)");
1420  }
1421 
1422  cloud->showSF(true);
1423  return true;
1424 }
1425 
1426 bool sfFromColor(const ccHObject::Container& selectedEntities,
1427  QWidget* parent) {
1428  // candidates
1429  std::unordered_set<ccPointCloud*> clouds;
1430 
1431  for (ccHObject* ent : selectedEntities) {
1433  if (cloud && ent->hasColors()) // only for clouds (or vertices)
1434  clouds.insert(cloud);
1435  }
1436 
1437  if (clouds.empty()) return false;
1438 
1439  ccScalarFieldFromColorDlg dialog(parent);
1440  if (!dialog.exec()) return false;
1441 
1442  const bool exportR = dialog.getRStatus();
1443  const bool exportG = dialog.getGStatus();
1444  const bool exportB = dialog.getBStatus();
1445  const bool exportC = dialog.getCompositeStatus();
1446 
1447  for (const auto cloud : clouds) {
1448  std::vector<ccScalarField*> fields(4);
1449  fields[0] = (exportR ? new ccScalarField(qPrintable(
1450  GetFirstAvailableSFName(cloud, "R")))
1451  : nullptr);
1452  fields[1] = (exportG ? new ccScalarField(qPrintable(
1453  GetFirstAvailableSFName(cloud, "G")))
1454  : nullptr);
1455  fields[2] = (exportB ? new ccScalarField(qPrintable(
1456  GetFirstAvailableSFName(cloud, "B")))
1457  : nullptr);
1458  fields[3] =
1459  (exportC ? new ccScalarField(qPrintable(
1460  GetFirstAvailableSFName(cloud, "Composite")))
1461  : nullptr);
1462 
1463  // try to instantiate memory for each field
1464  unsigned count = cloud->size();
1465  for (ccScalarField*& sf : fields) {
1466  if (sf && !sf->reserveSafe(count)) {
1467  CVLog::Warning(QString("[sfFromColor] Not enough memory to "
1468  "instantiate SF '%1' on cloud '%2'")
1469  .arg(sf->getName(), cloud->getName()));
1470  sf->release();
1471  sf = nullptr;
1472  }
1473  }
1474 
1475  // export points
1476  for (unsigned j = 0; j < cloud->size(); ++j) {
1477  const ecvColor::Rgb& rgb = cloud->getPointColor(j);
1478 
1479  if (fields[0]) fields[0]->addElement(rgb.r);
1480  if (fields[1]) fields[1]->addElement(rgb.g);
1481  if (fields[2]) fields[2]->addElement(rgb.b);
1482  if (fields[3])
1483  fields[3]->addElement(
1484  static_cast<ScalarType>(rgb.r + rgb.g + rgb.b) / 3);
1485  }
1486 
1487  QString fieldsStr;
1488 
1489  for (ccScalarField*& sf : fields) {
1490  if (sf == nullptr) continue;
1491 
1492  sf->computeMinAndMax();
1493 
1494  int sfIdx = cloud->getScalarFieldIndexByName(sf->getName());
1495  if (sfIdx >= 0) cloud->deleteScalarField(sfIdx);
1496  sfIdx = cloud->addScalarField(sf);
1497  Q_ASSERT(sfIdx >= 0);
1498 
1499  if (sfIdx >= 0) {
1500  cloud->setCurrentDisplayedScalarField(sfIdx);
1501  cloud->showSF(true);
1502  // cloud->prepareDisplayForRefresh();
1503 
1504  // mesh vertices?
1505  if (cloud->getParent() &&
1506  cloud->getParent()->isKindOf(CV_TYPES::MESH)) {
1507  cloud->getParent()->showSF(true);
1508  // cloud->getParent()->prepareDisplayForRefresh();
1509  }
1510 
1511  if (!fieldsStr.isEmpty()) fieldsStr.append(", ");
1512  fieldsStr.append(sf->getName());
1513  } else {
1515  QString("[sfFromColor] Failed to add scalar field '%1' "
1516  "to cloud '%2'?!")
1517  .arg(sf->getName(), cloud->getName()));
1518  sf->release();
1519  sf = nullptr;
1520  }
1521  }
1522 
1523  if (!fieldsStr.isEmpty())
1524  CVLog::Print(QString("[sfFromColor] New scalar fields (%1) added "
1525  "to '%2'")
1526  .arg(fieldsStr, cloud->getName()));
1527  }
1528 
1529  return true;
1530 }
1531 
1532 bool processMeshSF(const ccHObject::Container& selectedEntities,
1534  QWidget* parent) {
1535  for (ccHObject* ent : selectedEntities) {
1536  if (ent->isKindOf(CV_TYPES::MESH) ||
1537  ent->isKindOf(CV_TYPES::PRIMITIVE)) // TODO
1538  {
1539  ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
1540  if (mesh == nullptr) continue;
1541 
1542  ccGenericPointCloud* cloud = mesh->getAssociatedCloud();
1543  if (cloud == nullptr) continue;
1544 
1545  if (cloud->isA(CV_TYPES::POINT_CLOUD)) // TODO
1546  {
1547  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
1548 
1549  // on active le champ scalaire actuellement affiche
1550  int sfIdx = pc->getCurrentDisplayedScalarFieldIndex();
1551  if (sfIdx >= 0) {
1552  pc->setCurrentScalarField(sfIdx);
1553  mesh->processScalarField(process);
1555  } else {
1556  ecvConsole::Warning(QString("Mesh [%1] vertices have no "
1557  "activated scalar field!")
1558  .arg(mesh->getName()));
1559  }
1560  }
1561  }
1562  }
1563 
1564  return true;
1565 }
1566 
1568 // Normals
1569 
1570 bool computeNormals(const ccHObject::Container& selectedEntities,
1571  QWidget* parent) {
1572  if (selectedEntities.empty()) {
1573  ecvConsole::Error("Select at least one point cloud");
1574  return false;
1575  }
1576 
1577  // look for clouds and meshes
1578  std::vector<ccPointCloud*> clouds;
1579  bool withScanGrid = false;
1580  bool withSensor = false;
1581  std::vector<ccMesh*> meshes;
1582  PointCoordinateType defaultRadius = 0;
1583 
1584  try {
1585  for (const auto entity : selectedEntities) {
1586  if (entity->isA(CV_TYPES::POINT_CLOUD)) {
1587  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
1588  clouds.push_back(cloud);
1589 
1590  if (cloud->gridCount() > 0) {
1591  withScanGrid = true;
1592  }
1593  for (unsigned i = 0; i < cloud->getChildrenNumber(); ++i) {
1594  if (cloud->hasSensor()) {
1595  withSensor = true;
1596  }
1597  }
1598 
1599  if (defaultRadius == 0) {
1600  // default radius
1601  defaultRadius = ccNormalVectors::GuessNaiveRadius(cloud);
1602  }
1603  } else if (entity->isKindOf(CV_TYPES::MESH)) {
1604  if (entity->isA(CV_TYPES::MESH)) {
1605  ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
1606  meshes.push_back(mesh);
1607  } else {
1609  QString("Can't compute normals on sub-meshes! "
1610  "Select the parent mesh instead"));
1611  return false;
1612  }
1613  }
1614  }
1615  } catch (const std::bad_alloc&) {
1616  ecvConsole::Error("Not enough memory!");
1617  return false;
1618  }
1619 
1620  // compute normals for each selected cloud
1621  if (!clouds.empty()) {
1622  static CV_LOCAL_MODEL_TYPES s_lastModelType = LS;
1623  static ccNormalVectors::Orientation s_lastNormalOrientation =
1625  static int s_lastMSTNeighborCount = 6;
1626  static double s_lastMinGridAngle_deg = 1.0;
1627 
1628  ccNormalComputationDlg ncDlg(withScanGrid, withSensor, parent);
1629  ncDlg.setLocalModel(s_lastModelType);
1630  ncDlg.setRadius(defaultRadius);
1631  ncDlg.setPreferredOrientation(s_lastNormalOrientation);
1632  ncDlg.setMSTNeighborCount(s_lastMSTNeighborCount);
1633  ncDlg.setMinGridAngle_deg(s_lastMinGridAngle_deg);
1634  if (clouds.size() == 1) {
1635  ncDlg.setCloud(clouds.front());
1636  }
1637 
1638  if (!ncDlg.exec()) return false;
1639 
1640  // normals computation
1641  CV_LOCAL_MODEL_TYPES model = s_lastModelType = ncDlg.getLocalModel();
1642  bool useGridStructure =
1643  withScanGrid && ncDlg.useScanGridsForComputation();
1644  defaultRadius = ncDlg.getRadius();
1645  double minGridAngle_deg = s_lastMinGridAngle_deg =
1646  ncDlg.getMinGridAngle_deg();
1647 
1648  // normals orientation
1649  bool orientNormals = ncDlg.orientNormals();
1650  bool orientNormalsWithGrids =
1651  withScanGrid && ncDlg.useScanGridsForOrientation();
1652  bool orientNormalsWithSensors =
1653  withSensor && ncDlg.useSensorsForOrientation();
1654  ccNormalVectors::Orientation preferredOrientation =
1655  s_lastNormalOrientation = ncDlg.getPreferredOrientation();
1656  bool orientNormalsMST = ncDlg.useMSTOrientation();
1657  int mstNeighbors = s_lastMSTNeighborCount = ncDlg.getMSTNeighborCount();
1658 
1659  ecvProgressDialog pDlg(true, parent);
1660  pDlg.setAutoClose(false);
1661 
1662  size_t errors = 0;
1663 
1664  for (auto cloud : clouds) {
1665  Q_ASSERT(cloud != nullptr);
1666 
1667  bool result = false;
1668  bool normalsAlreadyOriented = false;
1669 
1670  if (useGridStructure && cloud->gridCount()) {
1671 #if 0
1672  ccPointCloud* newCloud = new ccPointCloud("temp");
1673  newCloud->reserve(cloud->size());
1674  for (size_t gi=0; gi<cloud->gridCount(); ++gi)
1675  {
1676  const ccPointCloud::Grid::Shared& scanGrid = cloud->grid(gi);
1677  if (scanGrid && scanGrid->indexes.empty())
1678  {
1679  //empty grid, we skip it
1680  continue;
1681  }
1682  ccGLMatrixd toSensor = scanGrid->sensorPosition.inverse();
1683 
1684  const int* _indexGrid = scanGrid->indexes.data();
1685  for (int j = 0; j < static_cast<int>(scanGrid->h); ++j)
1686  {
1687  for (int i = 0; i < static_cast<int>(scanGrid->w); ++i, ++_indexGrid)
1688  {
1689  if (*_indexGrid >= 0)
1690  {
1691  unsigned pointIndex = static_cast<unsigned>(*_indexGrid);
1692  const CCVector3* P = cloud->getPoint(pointIndex);
1693  CCVector3 Q = toSensor * (*P);
1694  newCloud->addPoint(Q);
1695  }
1696  }
1697  }
1698 
1699  addToDB(newCloud);
1700  }
1701 #endif
1702 
1703  // compute normals with the associated scan grid(s)
1704  normalsAlreadyOriented = true;
1705  result =
1706  cloud->computeNormalsWithGrids(minGridAngle_deg, &pDlg);
1707  } else {
1708  // compute normals with the octree
1709  normalsAlreadyOriented =
1710  orientNormals &&
1711  (preferredOrientation != ccNormalVectors::UNDEFINED);
1712  result = cloud->computeNormalsWithOctree(
1713  model,
1714  orientNormals ? preferredOrientation
1716  defaultRadius, &pDlg);
1717  }
1718 
1719  // do we need to orient the normals? (this may have been already
1720  // done if 'orientNormalsForThisCloud' is true)
1721  if (result && orientNormals && !normalsAlreadyOriented) {
1722  if (cloud->gridCount() && orientNormalsWithGrids) {
1723  // we can still use the grid structure(s) to orient the
1724  // normals!
1725  result = cloud->orientNormalsWithGrids();
1726  } else if (cloud->hasSensor() && orientNormalsWithSensors) {
1727  result = false;
1728 
1729  // RJ: TODO: the issue here is that a cloud can have
1730  // multiple sensors. As the association to sensor is not
1731  // explicit in CC, given a cloud some points can belong to
1732  // one sensor and some others can belongs to others sensors.
1733  // so it's why here grid orientation has precedence over
1734  // sensor orientation because in this case association is
1735  // more explicit. Here we take the first valid viewpoint for
1736  // now even if it's not a good one...
1737  for (unsigned i = 0; i < cloud->getChildrenNumber(); ++i) {
1738  ccHObject* child = cloud->getChild(i);
1739  if (child && child->isKindOf(CV_TYPES::SENSOR)) {
1740  ccSensor* sensor = ccHObjectCaster::ToSensor(child);
1741  CCVector3 sensorPosition;
1742  if (sensor->getActiveAbsoluteCenter(
1743  sensorPosition)) {
1744  result = cloud->orientNormalsTowardViewPoint(
1745  sensorPosition, &pDlg);
1746  break;
1747  }
1748  }
1749  }
1750  } else if (orientNormalsMST) {
1751  // use Minimum Spanning Tree to resolve normals direction
1752  result = cloud->orientNormalsWithMST(mstNeighbors, &pDlg);
1753  }
1754  }
1755 
1756  if (!result) {
1757  ++errors;
1758  }
1759 
1760  // cloud->prepareDisplayForRefresh();
1761  }
1762 
1763  if (errors != 0) {
1764  if (errors < clouds.size())
1766  "Failed to compute or orient the normals on some "
1767  "clouds! (see console)");
1768  else
1770  "Failed to compute or orient the normals! (see "
1771  "console)");
1772  }
1773  }
1774 
1775  // compute normals for each selected mesh
1776  if (!meshes.empty()) {
1777  QMessageBox question(QMessageBox::Question, "Mesh normals",
1778  "Compute per-vertex normals (smooth) or "
1779  "per-triangle (faceted)?",
1780  QMessageBox::NoButton, parent);
1781 
1782  QPushButton* perVertexButton =
1783  question.addButton("Per-vertex", QMessageBox::YesRole);
1784  QPushButton* perTriangleButton =
1785  question.addButton("Per-triangle", QMessageBox::NoRole);
1786 
1787  question.exec();
1788 
1789  bool computePerVertexNormals =
1790  (question.clickedButton() == perVertexButton);
1791 
1792  for (auto mesh : meshes) {
1793  Q_ASSERT(mesh != nullptr);
1794 
1795  // we remove temporarily the mesh as its normals may be removed (and
1796  // they can be a child object)
1797  MainWindow* instance = dynamic_cast<MainWindow*>(parent);
1798  MainWindow::ccHObjectContext objContext;
1799  if (instance)
1800  objContext = instance->removeObjectTemporarilyFromDBTree(mesh);
1801  mesh->clearTriNormals();
1802  mesh->showNormals(false);
1803  bool result = mesh->computeNormals(computePerVertexNormals);
1804  if (instance) instance->putObjectBackIntoDBTree(mesh, objContext);
1805 
1806  if (!result) {
1808  QString("Failed to compute normals on mesh '%1'")
1809  .arg(mesh->getName()));
1810  continue;
1811  }
1812  }
1813  }
1814 
1815  return true;
1816 }
1817 
1818 bool invertNormals(const ccHObject::Container& selectedEntities) {
1819  for (ccHObject* ent : selectedEntities) {
1820  // is it a mesh?
1821  ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
1822  if (mesh && mesh->hasNormals()) {
1823  mesh->invertNormals();
1824  mesh->showNormals(true);
1825  continue;
1826  }
1827 
1828  // is it a cloud?
1829  bool lockedVertices;
1830  ccPointCloud* cloud =
1831  ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
1832  if (cloud && cloud->hasNormals()) {
1833  if (lockedVertices) {
1835  ent->getName(), selectedEntities.size() == 1);
1836  continue;
1837  }
1838 
1839  cloud->invertNormals();
1840  cloud->showNormals(true);
1841  }
1842  }
1843 
1844  return true;
1845 }
1846 
1847 bool orientNormalsFM(const ccHObject::Container& selectedEntities,
1848  QWidget* parent) {
1849  if (selectedEntities.empty()) {
1850  ecvConsole::Error("Select at least one point cloud");
1851  return false;
1852  }
1853 
1854  bool ok = false;
1855  const int s_defaultLevel = 6;
1856  int value = QInputDialog::getInt(
1857  parent, "Orient normals (FM)", "Octree level", s_defaultLevel, 1,
1859  if (!ok) return false;
1860 
1861  Q_ASSERT(value >= 0 && value <= 255);
1862 
1863  unsigned char level = static_cast<unsigned char>(value);
1864 
1865  ecvProgressDialog pDlg(false, parent);
1866  pDlg.setAutoClose(false);
1867 
1868  size_t errors = 0;
1869  for (ccHObject* entity : selectedEntities) {
1870  if (!entity->isA(CV_TYPES::POINT_CLOUD)) continue;
1871 
1872  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
1873 
1874  if (!cloud->hasNormals()) {
1875  ecvConsole::Warning(QString("Cloud '%1' has no normals!")
1876  .arg(cloud->getName()));
1877  continue;
1878  }
1879 
1880  // orient normals with Fast Marching
1881  if (cloud->orientNormalsWithFM(level, &pDlg)) {
1882  // cloud->prepareDisplayForRefresh();
1883  } else {
1884  ++errors;
1885  }
1886  }
1887 
1888  if (errors) {
1889  ecvConsole::Error(QString("Process failed (check console)"));
1890  } else {
1892  "Normals have been oriented: you may still have to globally "
1893  "invert the cloud normals however (Edit > Normals > Invert).");
1894  }
1895 
1896  return true;
1897 }
1898 
1899 bool orientNormalsMST(const ccHObject::Container& selectedEntities,
1900  QWidget* parent) {
1901  if (selectedEntities.empty()) {
1902  ecvConsole::Error("Select at least one point cloud");
1903  return false;
1904  }
1905 
1906  bool ok = false;
1907  static unsigned s_defaultKNN = 6;
1908  unsigned kNN = static_cast<unsigned>(
1909  QInputDialog::getInt(parent, "Neighborhood size", "Neighbors",
1910  s_defaultKNN, 1, 1000, 1, &ok));
1911  if (!ok) return false;
1912 
1913  s_defaultKNN = kNN;
1914 
1915  ecvProgressDialog pDlg(true, parent);
1916  pDlg.setAutoClose(false);
1917 
1918  size_t errors = 0;
1919  for (ccHObject* entity : selectedEntities) {
1920  if (!entity->isA(CV_TYPES::POINT_CLOUD)) continue;
1921 
1922  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
1923 
1924  if (!cloud->hasNormals()) {
1925  ecvConsole::Warning(QString("Cloud '%1' has no normals!")
1926  .arg(cloud->getName()));
1927  continue;
1928  }
1929 
1930  // use Minimum Spanning Tree to resolve normals direction
1931  if (cloud->orientNormalsWithMST(kNN, &pDlg)) {
1932  // cloud->prepareDisplayForRefresh();
1933  } else {
1934  ecvConsole::Warning(QString("Process failed on cloud '%1'")
1935  .arg(cloud->getName()));
1936  ++errors;
1937  }
1938  }
1939 
1940  if (errors) {
1941  ecvConsole::Error(QString("Process failed (check console)"));
1942  } else {
1944  "Normals have been oriented: you may still have to globally "
1945  "invert the cloud normals however (Edit > Normals > Invert).");
1946  }
1947 
1948  return true;
1949 }
1950 
1951 bool convertNormalsTo(const ccHObject::Container& selectedEntities,
1952  NORMAL_CONVERSION_DEST dest) {
1953  unsigned errorCount = 0;
1954 
1955  size_t selNum = selectedEntities.size();
1956  for (size_t i = 0; i < selNum; ++i) {
1957  ccHObject* ent = selectedEntities[i];
1958  bool lockedVertices = false;
1959  ccGenericPointCloud* cloud =
1960  ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
1961  if (lockedVertices) {
1962  ecvUtils::DisplayLockedVerticesWarning(ent->getName(), selNum == 1);
1963  continue;
1964  }
1965 
1966  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) // TODO
1967  {
1968  ccPointCloud* ccCloud = static_cast<ccPointCloud*>(cloud);
1969  if (ccCloud->hasNormals()) {
1970  bool success = true;
1971  switch (dest) {
1973  success = ccCloud->convertNormalToRGB();
1974  if (success) {
1975  ccCloud->showSF(false);
1976  ccCloud->showNormals(false);
1977  ccCloud->showColors(true);
1978  }
1979  } break;
1980 
1982  // get/create 'dip' scalar field
1983  int dipSFIndex = ccCloud->getScalarFieldIndexByName(
1985  if (dipSFIndex < 0)
1986  dipSFIndex = ccCloud->addScalarField(
1988  if (dipSFIndex < 0) {
1990  "[ccEntityAction::convertNormalsTo] Not "
1991  "enough memory!");
1992  success = false;
1993  break;
1994  }
1995 
1996  // get/create 'dip direction' scalar field
1997  int dipDirSFIndex = ccCloud->getScalarFieldIndexByName(
1999  if (dipDirSFIndex < 0)
2000  dipDirSFIndex = ccCloud->addScalarField(
2002  if (dipDirSFIndex < 0) {
2003  ccCloud->deleteScalarField(dipSFIndex);
2005  "[ccEntityAction::convertNormalsTo] Not "
2006  "enough memory!");
2007  success = false;
2008  break;
2009  }
2010 
2011  ccScalarField* dipSF = static_cast<ccScalarField*>(
2012  ccCloud->getScalarField(dipSFIndex));
2013  ccScalarField* dipDirSF = static_cast<ccScalarField*>(
2014  ccCloud->getScalarField(dipDirSFIndex));
2015  Q_ASSERT(dipSF && dipDirSF);
2016 
2017  success = ccCloud->convertNormalToDipDirSFs(dipSF,
2018  dipDirSF);
2019 
2020  if (success) {
2021  // apply default 360 degrees color scale!
2022  ccColorScale::Shared dipScale =
2025  ccColorScale::Shared dipDirScale =
2028  DIP_DIR_REPEAT);
2029  dipSF->setColorScale(dipScale);
2030  dipDirSF->setColorScale(dipDirScale);
2032  dipDirSFIndex); // dip dir. seems more
2033  // interesting by default
2034  ccCloud->showSF(true);
2035  } else {
2036  ccCloud->deleteScalarField(dipSFIndex);
2037  ccCloud->deleteScalarField(dipDirSFIndex);
2038  }
2039  } break;
2040 
2041  default:
2042  Q_ASSERT(false);
2044  "[ccEntityAction::convertNormalsTo] Internal "
2045  "error: unhandled destination!");
2046  success = false;
2047  i = selNum; // no need to process the selected entities
2048  // anymore!
2049  break;
2050  }
2051 
2052  if (success) {
2053  // ccCloud->prepareDisplayForRefresh_recursive();
2054  } else {
2055  ++errorCount;
2056  }
2057  }
2058  }
2059  }
2060 
2061  // errors should have been sent to console as warnings
2062  if (errorCount) {
2063  ecvConsole::Error("Error(s) occurred! (see console)");
2064  }
2065 
2066  return true;
2067 }
2068 
2070 // Octree
2071 
2072 bool computeOctree(const ccHObject::Container& selectedEntities,
2073  QWidget* parent) {
2074  ccBBox bbox;
2075  std::unordered_set<ccGenericPointCloud*> clouds;
2076  PointCoordinateType maxBoxSize = -1;
2078  for (ccHObject* ent : selectedEntities) {
2079  // specific test for locked vertices
2080  bool lockedVertices = false;
2081  ccGenericPointCloud* cloud =
2082  ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
2083 
2084  if (cloud == nullptr) {
2085  continue;
2086  }
2087 
2088  if (lockedVertices) {
2090  ent->getName(), selectedEntities.size() == 1);
2091  continue;
2092  }
2093  clouds.insert(cloud);
2094 
2095  // we look for the biggest box so as to define the "minimum cell size"
2096  const ccBBox thisBBox = cloud->getOwnBB();
2097  if (thisBBox.isValid()) {
2098  CCVector3 dd = thisBBox.maxCorner() - thisBBox.minCorner();
2099  PointCoordinateType maxd = std::max(dd.x, std::max(dd.y, dd.z));
2100  if (maxBoxSize < 0.0 || maxd > maxBoxSize) maxBoxSize = maxd;
2101  }
2102  bbox += thisBBox;
2103  }
2104 
2105  if (clouds.empty() || maxBoxSize < 0.0) {
2107  "[doActionComputeOctree] No eligible entities in selection!");
2108  return false;
2109  }
2110 
2111  // min(cellSize) = max(dim)/2^N with N = max subidivision level
2112  const double minCellSize =
2113  static_cast<double>(maxBoxSize) / (1 << ccOctree::MAX_OCTREE_LEVEL);
2114 
2115  ccComputeOctreeDlg coDlg(bbox, minCellSize, parent);
2116  if (!coDlg.exec()) return false;
2117 
2118  ecvProgressDialog pDlg(true, parent);
2119  pDlg.setAutoClose(false);
2120 
2121  // if we must use a custom bounding box, we update 'bbox'
2123  bbox = coDlg.getCustomBBox();
2124 
2125  for (const auto cloud : clouds) {
2126  // we temporarily detach entity, as it may undergo
2127  //"severe" modifications (octree deletion, etc.) --> see
2128  // ccPointCloud::computeOctree
2129  MainWindow* instance = dynamic_cast<MainWindow*>(parent);
2130  MainWindow::ccHObjectContext objContext;
2131  if (instance)
2132  objContext = instance->removeObjectTemporarilyFromDBTree(cloud);
2133 
2134  // computation
2135  QElapsedTimer eTimer;
2136  eTimer.start();
2137  ccOctree::Shared octree(nullptr);
2138  switch (coDlg.getMode()) {
2140  octree = cloud->computeOctree(&pDlg);
2141  break;
2144  // for a cell-size based custom box, we must update it for each
2145  // cloud!
2146  if (coDlg.getMode() == ccComputeOctreeDlg::MIN_CELL_SIZE) {
2147  double cellSize = coDlg.getMinCellSize();
2148  PointCoordinateType halfBoxWidth =
2149  static_cast<PointCoordinateType>(
2150  cellSize *
2151  (1 << ccOctree::MAX_OCTREE_LEVEL) / 2.0);
2152  CCVector3 C = cloud->getOwnBB().getCenter();
2153  bbox = ccBBox(C - CCVector3(halfBoxWidth, halfBoxWidth,
2154  halfBoxWidth),
2155  C + CCVector3(halfBoxWidth, halfBoxWidth,
2156  halfBoxWidth));
2157  }
2158  cloud->deleteOctree();
2159  octree = ccOctree::Shared(new ccOctree(cloud));
2160  if (octree->build(bbox.minCorner(), bbox.maxCorner(), nullptr,
2161  nullptr, &pDlg) > 0) {
2162  ccOctreeProxy* proxy = new ccOctreeProxy(octree);
2163  // proxy->setDisplay(cloud->getDisplay());
2164  cloud->addChild(proxy);
2165  } else {
2166  octree.clear();
2167  }
2168  } break;
2169  default:
2170  Q_ASSERT(false);
2171  return false;
2172  }
2173  qint64 elapsedTime_ms = eTimer.elapsed();
2174 
2175  // put object back in tree
2176  if (instance) instance->putObjectBackIntoDBTree(cloud, objContext);
2177 
2178  if (octree) {
2179  ecvConsole::Print("[doActionComputeOctree] Timing: %2.3f s",
2180  static_cast<double>(elapsedTime_ms) / 1000.0);
2181  cloud->setEnabled(true); // for mesh vertices!
2182  cloud->setRedraw(true);
2183  ccOctreeProxy* proxy = cloud->getOctreeProxy();
2184  assert(proxy);
2185  proxy->setVisible(true);
2186  // proxy->prepareDisplayForRefresh();
2187  } else {
2189  QString("Octree computation on cloud '%1' failed!")
2190  .arg(cloud->getName()));
2191  }
2192  }
2193 
2194  return true;
2195 }
2196 
2198 // Properties
2199 
2200 bool clearProperty(ccHObject::Container selectedEntities,
2201  CLEAR_PROPERTY property,
2202  QWidget* parent) {
2203  for (ccHObject* ent : selectedEntities) {
2204  // specific case: clear normals on a mesh
2205  if (property == CLEAR_PROPERTY::NORMALS &&
2206  (ent->isA(
2207  CV_TYPES::
2208  MESH) /*|| ent->isKindOf(CV_TYPES::PRIMITIVE)*/)) // TODO
2209  {
2210  ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
2211  if (!mesh) {
2212  assert(false);
2213  continue;
2214  }
2215  if (mesh->hasTriNormals()) {
2216  mesh->showNormals(false);
2217 
2218  MainWindow* instance = dynamic_cast<MainWindow*>(parent);
2219  MainWindow::ccHObjectContext objContext;
2220  if (instance)
2221  objContext =
2222  instance->removeObjectTemporarilyFromDBTree(mesh);
2223  mesh->clearTriNormals();
2224  if (instance)
2225  instance->putObjectBackIntoDBTree(mesh, objContext);
2226 
2227  ent->setRedraw(true);
2228  continue;
2229  } else if (mesh->hasNormals()) // per-vertex normals?
2230  {
2231  if (mesh->getParent() && (mesh->getParent()->isA(CV_TYPES::MESH) /*|| mesh->getParent()->isKindOf(CV_TYPES::PRIMITIVE)*/) // TODO
2233  ->getAssociatedCloud() ==
2234  mesh->getAssociatedCloud()) {
2236  "[doActionClearNormals] Can't remove per-vertex "
2237  "normals on a sub mesh!");
2238  } else // mesh is alone, we can freely remove normals
2239  {
2240  if (mesh->getAssociatedCloud() &&
2241  mesh->getAssociatedCloud()->isA(
2243  mesh->showNormals(false);
2244  static_cast<ccPointCloud*>(mesh->getAssociatedCloud())
2245  ->unallocateNorms();
2246  mesh->setRedraw(true);
2247  continue;
2248  }
2249  }
2250  }
2251  }
2252 
2253  bool lockedVertices;
2254  ccGenericPointCloud* cloud =
2255  ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
2256  if (lockedVertices) {
2258  ent->getName(), selectedEntities.size() == 1);
2259  continue;
2260  }
2261 
2262  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) // TODO
2263  {
2264  auto pointCloud = static_cast<ccPointCloud*>(cloud);
2265 
2266  switch (property) {
2268  if (cloud->hasColors()) {
2269  pointCloud->unallocateColors();
2270  ent->setRedrawFlagRecursive(true);
2271  }
2272  break;
2273 
2275  if (cloud->hasNormals()) {
2276  pointCloud->unallocateNorms();
2277  ent->setRedrawFlagRecursive(true);
2278  }
2279  break;
2280 
2282  if (cloud->hasDisplayedScalarField()) {
2283  pointCloud->deleteScalarField(
2284  pointCloud
2285  ->getCurrentDisplayedScalarFieldIndex());
2286  ent->setRedrawFlagRecursive(true);
2287  }
2288  break;
2289 
2291  if (cloud->hasScalarFields()) {
2292  pointCloud->deleteAllScalarFields();
2293  ent->setRedrawFlagRecursive(true);
2294  }
2295  break;
2296  }
2297  }
2298  }
2299 
2300  return true;
2301 }
2302 
2303 bool toggleProperty(const ccHObject::Container& selectedEntities,
2304  TOGGLE_PROPERTY property) {
2305  ccHObject baseEntities;
2306  ConvertToGroup(selectedEntities, baseEntities, ccHObject::DP_NONE);
2307 
2308  for (unsigned i = 0; i < baseEntities.getChildrenNumber(); ++i) {
2309  ccHObject* child = baseEntities.getChild(i);
2310  switch (property) {
2312  child->toggleActivation /*_recursive*/ ();
2313  break;
2315  child->toggleVisibility_recursive();
2316  break;
2318  child->toggleColors_recursive();
2319  break;
2321  child->toggleNormals_recursive();
2322  break;
2324  child->toggleSF_recursive();
2325  break;
2326  case TOGGLE_PROPERTY::NAME:
2327  child->toggleShowName_recursive();
2328  break;
2329  default:
2330  Q_ASSERT(false);
2331  return false;
2332  }
2333  }
2334 
2335  return true;
2336 }
2337 
2339 // Stats
2340 
2341 bool statisticalTest(const ccHObject::Container& selectedEntities,
2342  QWidget* parent) {
2343  ccPickOneElementDlg poeDlg("Distribution", "Choose distribution", parent);
2344  poeDlg.addElement("Gauss");
2345  poeDlg.addElement("Weibull");
2346  poeDlg.setDefaultIndex(0);
2347  if (!poeDlg.exec()) {
2348  return false;
2349  }
2350 
2351  int distribIndex = poeDlg.getSelectedIndex();
2352 
2353  ccStatisticalTestDlg* sDlg = nullptr;
2354  switch (distribIndex) {
2355  case 0: // Gauss
2356  sDlg = new ccStatisticalTestDlg("mu", "sigma", QString(),
2357  "Local Statistical Test (Gauss)",
2358  parent);
2359  break;
2360  case 1: // Weibull
2361  sDlg = new ccStatisticalTestDlg("a", "b", "shift",
2362  "Local Statistical Test (Weibull)",
2363  parent);
2364  break;
2365  default:
2366  ecvConsole::Error("Invalid distribution!");
2367  return false;
2368  }
2369 
2370  if (!sDlg->exec()) {
2371  sDlg->deleteLater();
2372  return false;
2373  }
2374 
2375  // build up corresponding distribution
2376  cloudViewer::GenericDistribution* distrib = nullptr;
2377  {
2378  ScalarType a = static_cast<ScalarType>(sDlg->getParam1());
2379  ScalarType b = static_cast<ScalarType>(sDlg->getParam2());
2380  ScalarType c = static_cast<ScalarType>(sDlg->getParam3());
2381 
2382  switch (distribIndex) {
2383  case 0: // Gauss
2384  {
2387  N->setParameters(
2388  a, b * b); // warning: we input sigma2 here (not sigma)
2389  distrib = static_cast<cloudViewer::GenericDistribution*>(N);
2390  break;
2391  }
2392  case 1: // Weibull
2395  W->setParameters(a, b, c);
2396  distrib = static_cast<cloudViewer::GenericDistribution*>(W);
2397  break;
2398  }
2399  }
2400 
2401  const double pChi2 = sDlg->getProba();
2402  const int nn = sDlg->getNeighborsNumber();
2403 
2404  ecvProgressDialog pDlg(true, parent);
2405  pDlg.setAutoClose(false);
2406 
2407  for (ccHObject* ent : selectedEntities) {
2409  if (pc == nullptr) {
2410  // TODO handle error?
2411  continue;
2412  }
2413 
2414  // we apply method on currently displayed SF
2416  if (inSF == nullptr) {
2417  // TODO handle error?
2418  continue;
2419  }
2420 
2421  Q_ASSERT(inSF->capacity() != 0);
2422 
2423  // force SF as 'OUT' field (in case of)
2424  const int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
2425  pc->setCurrentOutScalarField(outSfIdx);
2426 
2427  // force Chi2 Distances field as 'IN' field (create it by the way if
2428  // necessary)
2429  int chi2SfIdx = pc->getScalarFieldIndexByName(
2431 
2432  if (chi2SfIdx < 0)
2434 
2435  if (chi2SfIdx < 0) {
2437  "Couldn't allocate a new scalar field for computing chi2 "
2438  "distances! Try to free some memory ...");
2439  break;
2440  }
2441  pc->setCurrentInScalarField(chi2SfIdx);
2442 
2443  // compute octree if necessary
2444  ccOctree::Shared theOctree = pc->getOctree();
2445  if (!theOctree) {
2446  theOctree = pc->computeOctree(&pDlg);
2447  if (!theOctree) {
2449  QString("Couldn't compute octree for cloud '%1'!")
2450  .arg(pc->getName()));
2451  break;
2452  }
2453  }
2454 
2455  QElapsedTimer eTimer;
2456  eTimer.start();
2457 
2458  double chi2dist = cloudViewer::StatisticalTestingTools::
2459  testCloudWithStatisticalModel(distrib, pc, nn, pChi2, &pDlg,
2460  theOctree.data());
2461 
2462  ecvConsole::Print("[Chi2 Test] Timing: %3.2f ms.",
2463  eTimer.elapsed() / 1000.0);
2464  ecvConsole::Print("[Chi2 Test] %s test result = %f", distrib->getName(),
2465  chi2dist);
2466 
2467  // we set the theoretical Chi2 distance limit as the minimum displayed
2468  // SF value so that all points below are grayed
2469  {
2470  ccScalarField* chi2SF =
2471  static_cast<ccScalarField*>(pc->getCurrentInScalarField());
2472  Q_ASSERT(chi2SF);
2473  chi2SF->computeMinAndMax();
2474  chi2dist *= chi2dist;
2475  chi2SF->setMinDisplayed(static_cast<ScalarType>(chi2dist));
2476  chi2SF->setSymmetricalScale(false);
2477  chi2SF->setSaturationStart(static_cast<ScalarType>(chi2dist));
2478  // chi2SF->setSaturationStop(chi2dist);
2479 
2480  pc->setCurrentDisplayedScalarField(chi2SfIdx);
2481  pc->showSF(true);
2482  }
2483  }
2484 
2485  delete distrib;
2486  distrib = nullptr;
2487 
2488  sDlg->deleteLater();
2489 
2490  return true;
2491 }
2492 
2493 bool computeStatParams(const ccHObject::Container& selectedEntities,
2494  QWidget* parent) {
2495  ccPickOneElementDlg pDlg("Distribution", "Distribution Fitting", parent);
2496  pDlg.addElement("Gauss");
2497  pDlg.addElement("Weibull");
2498  pDlg.setDefaultIndex(0);
2499  if (!pDlg.exec()) return false;
2500 
2501  cloudViewer::GenericDistribution* distrib = nullptr;
2502  {
2503  switch (pDlg.getSelectedIndex()) {
2504  case 0: // GAUSS
2505  distrib = new cloudViewer::NormalDistribution();
2506  break;
2507  case 1: // WEIBULL
2508  distrib = new cloudViewer::WeibullDistribution();
2509  break;
2510  default:
2511  Q_ASSERT(false);
2512  return false;
2513  }
2514  }
2515  Q_ASSERT(distrib != nullptr);
2516 
2517  for (ccHObject* ent : selectedEntities) {
2519  if (pc == nullptr) {
2520  // TODO report error?
2521  continue;
2522  }
2523 
2524  // we apply method on currently displayed SF
2526  if (sf == nullptr) {
2527  // TODO report error?
2528  continue;
2529  }
2530 
2531  // compute the number of valid values
2532  size_t sfValidCount = sf->countValidValues();
2533  if (sfValidCount == 0) {
2535  QObject::tr(
2536  "Scalar field '%1' of cloud %2 has no valid values")
2537  .arg(QString::fromStdString(sf->getName()))
2538  .arg(pc->getName()));
2539  continue;
2540  }
2541 
2542  Q_ASSERT(!sf->empty());
2543 
2544  if (sf && distrib->computeParameters(*sf)) {
2545  QString description;
2546  const unsigned precision =
2548  switch (pDlg.getSelectedIndex()) {
2549  case 0: // GAUSS
2550  {
2552  static_cast<cloudViewer::NormalDistribution*>(
2553  distrib);
2554  description =
2555  QString("mean = %1 / std.dev. = %2")
2556  .arg(normal->getMu(), 0, 'f', precision)
2557  .arg(sqrt(normal->getSigma2()), 0, 'f',
2558  precision);
2559  } break;
2560 
2561  case 1: // WEIBULL
2562  {
2564  static_cast<cloudViewer::WeibullDistribution*>(
2565  distrib);
2566  ScalarType a, b;
2567  weibull->getParameters(a, b);
2568  description = QString("a = %1 / b = %2 / shift = %3")
2569  .arg(a, 0, 'f', precision)
2570  .arg(b, 0, 'f', precision)
2571  .arg(weibull->getValueShift(), 0, 'f',
2572  precision);
2573  CVLog::Print(QString("[Distribution fitting] Additional "
2574  "Weibull distrib. parameters: mode = "
2575  "%1 / skewness = %2")
2576  .arg(weibull->computeMode())
2577  .arg(weibull->computeSkewness()));
2578  } break;
2579 
2580  default: {
2581  Q_ASSERT(false);
2582  return false;
2583  }
2584  }
2585  description.prepend(QString("%1: ").arg(distrib->getName()));
2587  QString("[Distribution fitting] %1").arg(description));
2588 
2589  const unsigned numberOfClasses = static_cast<unsigned>(
2590  ceil(sqrt(static_cast<double>(pc->size()))));
2591  std::vector<unsigned> histo;
2592  std::vector<double> npis;
2593  try {
2594  histo.resize(numberOfClasses, 0);
2595  npis.resize(numberOfClasses, 0.0);
2596  } catch (const std::bad_alloc&) {
2598  "[Distribution fitting] Not enough memory!");
2599  continue;
2600  }
2601 
2602  // compute the Chi2 distance
2603  {
2604  unsigned finalNumberOfClasses = 0;
2605  const double chi2dist = cloudViewer::StatisticalTestingTools::
2607  distrib, pc, 0, finalNumberOfClasses, false,
2608  nullptr, nullptr, histo.data(), npis.data());
2609 
2610  if (chi2dist >= 0.0) {
2612  "[Distribution fitting] %s: Chi2 Distance = %f",
2613  distrib->getName(), chi2dist);
2614  } else {
2616  "[Distribution fitting] Failed to compute Chi2 "
2617  "distance?!");
2618  continue;
2619  }
2620  }
2621 
2622  // compute RMS
2623  {
2624  unsigned n = pc->size();
2625  double squareSum = 0;
2626  unsigned counter = 0;
2627  for (unsigned i = 0; i < n; ++i) {
2628  ScalarType v = pc->getPointScalarValue(i);
2630  squareSum += static_cast<double>(v) * v;
2631  ++counter;
2632  }
2633  }
2634 
2635  if (counter != 0) {
2636  double rms = sqrt(squareSum / counter);
2638  QString("Scalar field RMS = %1").arg(rms));
2639  }
2640  }
2641 
2642  // show histogram
2643  ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(parent);
2644  hDlg->setWindowTitle("[Distribution fitting]");
2645 
2646  ccHistogramWindow* histogram = hDlg->window();
2647  histogram->fromBinArray(histo, sf->getMin(), sf->getMax());
2648  histo.clear();
2649  histogram->setCurveValues(npis);
2650  npis.clear();
2651  histogram->setTitle(description);
2652  histogram->setColorScheme(
2654  histogram->setColorScale(sf->getColorScale());
2655  histogram->setAxisLabels(sf->getName(), "Count");
2656  histogram->refresh();
2657 
2658  hDlg->show();
2659  } else {
2660  ecvConsole::Warning(QString("[Entity: %1]-[SF: %2] Couldn't "
2661  "compute distribution parameters!")
2662  .arg(pc->getName(), sf->getName()));
2663  }
2664  }
2665 
2666  delete distrib;
2667  distrib = nullptr;
2668 
2669  return true;
2670 }
2671 
2673 // segmentation
2674 
2675 bool DBScanCluster(const ccHObject::Container& selectedEntities,
2676  QWidget* parent) {
2677  if (selectedEntities.empty()) return false;
2678 
2679  ccPointCloud* pc_test = ccHObjectCaster::ToPointCloud(selectedEntities[0]);
2680  double eps = pc_test->ComputeResolution() * 10;
2681  int minPoints = 100;
2682  ecvAskDoubleIntegerValuesDlg dlg("density parameter eps", "minimum points",
2683  DBL_MIN, 1.0e9, 1, 1000000, eps, minPoints,
2684  8, "DBScan Cluster", parent);
2685 
2686  dlg.doubleSpinBox->setStatusTip(
2687  "Density parameter that is used to find neighbouring points.");
2688  dlg.integerSpinBox->setStatusTip(
2689  "Minimum number of points to form a cluster");
2690  if (!dlg.exec()) return false;
2691 
2692  // get values
2693  eps = dlg.doubleSpinBox->value();
2694  minPoints = dlg.integerSpinBox->value();
2695 
2696  ccHObject::Container entities;
2697  std::vector<std::vector<int>> clusters;
2698  for (ccHObject* ent : selectedEntities) {
2699  bool lockedVertices = false;
2700  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
2701  if (!pc || lockedVertices) {
2703  ent->getName(), selectedEntities.size() == 1);
2704  continue;
2705  }
2706  entities.push_back(pc);
2707  clusters.emplace_back(pc->ClusterDBSCAN(eps, minPoints));
2708  vector<int>::iterator itMax =
2709  std::max_element(clusters[clusters.size() - 1].begin(),
2710  clusters[clusters.size() - 1].end());
2711  int clusterNumber = *itMax + 1;
2712  CVLog::Print(QString("%1 has %2 clusters.")
2713  .arg(pc->getName())
2714  .arg(clusterNumber));
2715  }
2716 
2717  std::vector<std::vector<ScalarType>> scalarsVector;
2718  ccEntityAction::ConvertToScalarType<int>(clusters, scalarsVector);
2719  if (!ccEntityAction::importToSF(entities, scalarsVector,
2720  "DBSCANClusters")) {
2721  CVLog::Error("[ecvEntityAction::DBScanCluster] import sf failed!");
2722  return false;
2723  } else {
2724  CVLog::Print(
2725  "Clusters information has been imported to scalar field of "
2726  "each cloud.");
2727  }
2728 
2729  return true;
2730 }
2731 
2732 bool RansacSegmentation(const ccHObject::Container& selectedEntities,
2733  ccHObject::Container& outEntities,
2734  QWidget* parent) {
2735  if (selectedEntities.empty()) return false;
2736 
2737  ecvRansacSegmentationDlg dlg(parent);
2738  if (!dlg.exec()) return false;
2739 
2740  // get values
2741  double distanceThreshold = dlg.distanceThresholdSpinbox->value();
2742  int ransacN = dlg.ransacNSpinBox->value();
2743  int iterations = dlg.iterationsSpinBox->value();
2744  bool extractInliers = dlg.inliersCheckBox->isChecked();
2745  bool extractOutliers = dlg.outliersCheckBox->isChecked();
2746 
2747  outEntities.clear();
2748  for (ccHObject* ent : selectedEntities) {
2749  bool lockedVertices = false;
2750  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
2751  if (!pc || lockedVertices) {
2753  ent->getName(), selectedEntities.size() == 1);
2754  continue;
2755  }
2756 
2757  std::vector<size_t> inliers;
2758  Eigen::Vector4d planeModel;
2759  std::tie(planeModel, inliers) =
2760  pc->SegmentPlane(distanceThreshold, ransacN, iterations);
2761 
2762  CVLog::Print(QString("[%1] Plane model: %2x + %3y + %4z + %5 = 0")
2763  .arg(ent->getName())
2764  .arg(planeModel(0))
2765  .arg(planeModel(1))
2766  .arg(planeModel(2))
2767  .arg(planeModel(3)));
2768  if (extractInliers) {
2769  ccPointCloud* cloud = ccPointCloud::From(pc, inliers, false);
2770  if (pc->getParent()) {
2771  pc->getParent()->addChild(cloud);
2772  pc->setEnabled(false);
2773  }
2774 
2775  cloud->setName(QString("%1-plane").arg(pc->getName()));
2776  outEntities.push_back(cloud);
2777  }
2778 
2779  if (extractOutliers) {
2780  ccPointCloud* cloud = ccPointCloud::From(pc, inliers, true);
2781  if (pc->getParent()) {
2782  pc->getParent()->addChild(cloud);
2783  }
2784  cloud->setName(QString("%1-non-plane").arg(pc->getName()));
2785  outEntities.push_back(cloud);
2786  }
2787  }
2788 
2789  return !outEntities.empty();
2790 }
2791 
2793 // convex hull
2794 bool ConvexHull(const ccHObject::Container& selectedEntities,
2795  ccHObject::Container& outEntities,
2796  QWidget* parent) {
2797  if (selectedEntities.empty()) return false;
2798 
2799  outEntities.clear();
2800  for (ccHObject* ent : selectedEntities) {
2801  bool lockedVertices = false;
2802  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
2803  if (!pc || lockedVertices) {
2805  ent->getName(), selectedEntities.size() == 1);
2806  continue;
2807  }
2808 
2809  std::shared_ptr<ccMesh> mesh;
2810  std::vector<size_t> pt_map;
2811  std::tie(mesh, pt_map) = pc->ComputeConvexHull();
2812  if (!mesh) {
2814  QString("[ccEntityAction::ConvexHull] "
2815  "computing convex hull failed from cloud [%1]! ")
2816  .arg(pc->getName()));
2817  continue;
2818  }
2819 
2820  ccMesh* outMesh = new ccMesh();
2821  outMesh->CreateInternalCloud();
2822  *outMesh = *mesh;
2823  if (pc->getParent()) {
2824  pc->getParent()->addChild(outMesh);
2825  }
2826  outMesh->setName("ConvexHull");
2827  outEntities.push_back(outMesh);
2828  }
2829 
2830  return !outEntities.empty();
2831 }
2832 
2834 // sampling
2835 
2836 bool VoxelSampling(const ccHObject::Container& selectedEntities,
2837  ccHObject::Container& outEntities,
2838  QWidget* parent) {
2839  if (selectedEntities.empty()) return false;
2840 
2841  outEntities.clear();
2842  ccPointCloud* pc_test = ccHObjectCaster::ToPointCloud(selectedEntities[0]);
2843  double voxelSize = pc_test->ComputeResolution();
2844 
2845  bool ok = false;
2846  voxelSize = QInputDialog::getDouble(parent, "voxel down sampling",
2847  "Voxel Size:", voxelSize, DBL_MIN,
2848  1.0e9, 8, &ok);
2849 
2850  for (ccHObject* ent : selectedEntities) {
2851  bool lockedVertices = false;
2852  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
2853  if (!pc || lockedVertices) {
2855  ent->getName(), selectedEntities.size() == 1);
2856  continue;
2857  }
2858 
2859  ccPointCloud* out = new ccPointCloud();
2860  *out = *pc->VoxelDownSample(voxelSize);
2861  outEntities.push_back(out);
2862  }
2863 
2864  return true;
2865 }
2866 
2867 } // namespace ccEntityAction
CV_LOCAL_MODEL_TYPES
Definition: CVConst.h:121
@ LS
Definition: CVConst.h:123
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
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
double normal[3]
std::vector< PCLPointField > fields
std::string name
int count
cmdLineReadable * params[]
core::Tensor result
Definition: VtkUtils.cpp:76
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
Array of RGB colors for each point.
ccHObjectContext removeObjectTemporarilyFromDBTree(ccHObject *obj) override
Removes object temporarily from DB tree.
void putObjectBackIntoDBTree(ccHObject *obj, const ccHObjectContext &context) override
Adds back object to DB tree.
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type & getValue(size_t index)
Definition: ecvArray.h:100
bool reserveSafe(size_t count)
Reserves memory (no exception thrown)
Definition: ecvArray.h:56
void addElement(const Type &value)
Definition: ecvArray.h:105
Generic dialog to query 3 (double) values.
Dialog to input 2 values with custom labels.
Bounding box structure.
Definition: ecvBBox.h:25
Dialog to define a color gradient (default, with 2 colors, banding, etc.)
double getBandingFrequency() const
Returns the frequency of the gradient ('Banding' mode)
GradientType
Gradient types.
void getColors(QColor &first, QColor &second) const
Returns the two colors of the gradient ('TwoColors' mode)
unsigned char getDimension() const
Returns the ramp dimension.
GradientType getType() const
Returns selected gradient type.
Dialog to change the color levels.
Color scale element: one value + one color.
Definition: ecvColorScale.h:22
static ccColorScale::Shared Create(const QString &name)
Creates a new color scale (with auto-generated unique id)
QSharedPointer< ccColorScale > Shared
Shared pointer type.
Definition: ecvColorScale.h:74
Color scales manager/container.
static ccColorScale::Shared GetDefaultScale(DEFAULT_SCALES scale=BGYR)
Returns a pre-defined color scale (static shortcut)
Dialog for octree computation.
ComputationMode getMode() const
Returns octree computation mode.
double getMinCellSize() const
Returns cell size at max level.
ccBBox getCustomBBox() const
Returns custom bbox.
virtual bool hasDisplayedScalarField() const
Returns whether an active scalar field is available or not.
virtual bool hasColors() const
Returns whether colors are enabled or not.
virtual void setVisible(bool state)
Sets entity visibility.
virtual bool hasNormals() const
Returns whether normals are enabled or not.
virtual void setRedraw(bool state)
Sets entity redraw mode.
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual bool hasScalarFields() const
Returns whether one or more scalar fields are instantiated.
Dialog to choose which dimension(s) (X, Y or Z) should be exported as SF(s)
bool exportX() const
Returns whether X dimension should be exported.
bool exportZ() const
Returns whether Z dimension should be exported.
bool exportY() const
Returns whether Y dimension should be exported.
Facet.
Definition: ecvFacet.h:25
void setColor(const ecvColor::Rgb &rgb)
Sets the facet unique color.
T * data()
Returns a pointer to internal data.
ccGLMatrixTpl< T > inverse() const
Returns inverse transformation.
Double version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:56
Generic mesh interface.
void showNormals(bool state) override
Sets normals visibility.
virtual void showMaterials(bool state)
Sets whether textures should be displayed or not.
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.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
Generic primitive interface.
virtual void setColor(const ecvColor::Rgb &col)
Sets primitive color (shortcut)
static ccMesh * ToMesh(ccHObject *obj)
Converts current object to ccMesh (if possible)
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
static ccGenericPrimitive * ToPrimitive(ccHObject *obj)
Converts current object to ccGenericPrimitive (if possible)
static ccPolyline * ToPolyline(ccHObject *obj)
Converts current object to ccPolyline (if possible)
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
static ccSensor * ToSensor(ccHObject *obj)
Converts current object to ccSensor (if possible)
static ccFacet * ToFacet(ccHObject *obj)
Converts current object to ccFacet (if possible)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
bool isAncestorOf(const ccHObject *anObject) const
Returns true if the current object is an ancestor of the specified one.
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
virtual void toggleSF_recursive()
Definition: ecvHObject.h:556
void setRedrawFlagRecursive(bool redraw=false)
virtual void toggleVisibility_recursive()
Definition: ecvHObject.h:545
virtual void toggleColors_recursive()
Definition: ecvHObject.h:547
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
virtual void toggleNormals_recursive()
Definition: ecvHObject.h:553
virtual void toggleShowName_recursive()
Definition: ecvHObject.h:559
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
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 setCurveValues(const std::vector< double > &curveValues)
Sets overlay curve values.
void setColorScale(ccColorScale::Shared scale)
Sets gradient color scale.
void refresh()
Updates the display.
void setColorScheme(HISTOGRAM_COLOR_SCHEME scheme)
Sets how the gradient bars should be colored.
void fromBinArray(const std::vector< unsigned > &histoValues, double minVal, double maxVal)
void setAxisLabels(const QString &xLabel, const QString &yLabel)
Sets axis labels.
Dialog for generic interpolation algorithms.
void setInterpolationMethod(ccPointCloudInterpolator::Parameters::Method method)
ccPointCloudInterpolator::Parameters::Method getInterpolationMethod() const
ccPointCloudInterpolator::Parameters::Algo getInterpolationAlgorithm() const
void setInterpolationAlgorithm(ccPointCloudInterpolator::Parameters::Algo algo)
Dialog to select one or multiple items.
void getSelectedIndexes(std::vector< int > &indexes) const
Returns selected indexes (multi-selection mode)
void setItems(const QStringList &items, int defaultSelectedIndex=0)
Sets the list of items.
Triangular mesh.
Definition: ecvMesh.h:35
bool hasMaterials() const override
bool processScalarField(MESH_SCALAR_FIELD_PROCESS process)
bool convertMaterialsToVertexColors()
Converts materials to vertex colors.
bool hasColors() const override
Returns whether colors are enabled or not.
MESH_SCALAR_FIELD_PROCESS
Mesh scalar field processes.
Definition: ecvMesh.h:554
void clearTriNormals()
Removes per-triangle normals.
Definition: ecvMesh.h:353
ccGenericPointCloud * getAssociatedCloud() const override
Returns the vertices cloud.
Definition: ecvMesh.h:143
bool hasNormals() const override
Returns whether normals are enabled or not.
void invertNormals()
Inverts normals (if any)
bool hasTriNormals() const override
Returns whether the mesh has per-triangle normals.
Dialog for normal computation.
bool useScanGridsForComputation() const
Returns whether scan grids should be used for computation.
int getMSTNeighborCount() const
Returns the number of neighbors for Minimum Spanning Tree (MST)
bool orientNormals() const
Returns whether normals should be oriented or not.
void setMSTNeighborCount(int n)
Sets the number of neighbors for Minimum Spanning Tree (MST)
ccNormalVectors::Orientation getPreferredOrientation() const
Returns the preferred orientation (if any)
double getMinGridAngle_deg() const
Returns the min angle for grid triangles.
bool useScanGridsForOrientation() const
Returns whether scan grids should be used for normals orientation.
void setMinGridAngle_deg(double value)
Sets the min angle for grid triangles.
CV_LOCAL_MODEL_TYPES getLocalModel() const
Returns the local model chosen for normal computation.
void setCloud(ccPointCloud *cloud)
Sets the currently selected cloud (required for 'auto' feature)
bool useSensorsForOrientation() const
Returns whether scan grids should be used for normals computation.
void setRadius(PointCoordinateType radius)
Sets default value for local neighbourhood radius.
void setPreferredOrientation(ccNormalVectors::Orientation orientation)
Sets the preferred orientation.
void setLocalModel(CV_LOCAL_MODEL_TYPES model)
Sets the local model chosen for normal computation.
PointCoordinateType getRadius() const
Returns local neighbourhood radius.
static PointCoordinateType GuessNaiveRadius(ccGenericPointCloud *cloud)
Orientation
'Default' orientations
@ UNDEFINED
Undefined (no orientation is required)
virtual bool isLocked() const
Returns whether the object is locked or not.
Definition: ecvObject.h:112
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
virtual void toggleActivation()
Toggles the "enabled" property.
Definition: ecvObject.h:107
Octree structure proxy.
Octree structure.
Definition: ecvOctree.h:27
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
Dialog to assign roles to two entities (e.g. compared/reference)
ccHObject * getFirstEntity()
Returns the first entity (new order)
Minimal dialog to pick one element in a list (combox box)
void addElement(const QString &elementName)
Add an element to the combo box.
int getSelectedIndex()
Returns the combo box current index (after completion)
void setDefaultIndex(int index)
Sets the combo box default index.
static bool InterpolateScalarFieldsFrom(ccPointCloud *destCloud, ccPointCloud *srccloud, const std::vector< int > &sfIndexes, const Parameters &params, cloudViewer::GenericProgressCallback *progressCb=0, unsigned char octreeLevel=0)
Interpolate scalar fields from another cloud.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool applyFilterToRGB(PointCoordinateType sigma, PointCoordinateType sigmaSF, RgbFilterOptions filterParams, cloudViewer::GenericProgressCallback *progressCb=nullptr)
Applies a spatial Gaussian filter on RGB colors.
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
bool setRGBColorByBanding(unsigned char dim, double freq)
Assigns color to points by 'banding'.
bool hasSensor() const
Returns whether the mesh as an associated sensor or not.
bool convertNormalToRGB()
Converts normals to RGB colors.
std::tuple< Eigen::Vector4d, std::vector< size_t > > SegmentPlane(const double distance_threshold=0.01, const int ransac_n=3, const int num_iterations=100) const
Segment ccPointCloud plane using the RANSAC algorithm.
bool convertRGBToGreyScale()
Converts RGB to grey scale colors.
double ComputeResolution() const
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
bool enhanceRGBWithIntensitySF(int sfIdx, bool useCustomIntensityRange=false, double minI=0.0, double maxI=1.0)
bool resizeTheRGBTable(bool fillWithWhite=false)
Resizes the RGB colors array.
void invalidateBoundingBox() override
Invalidates bounding box.
bool orientNormalsWithFM(unsigned char level, ecvProgressDialog *pDlg=nullptr)
Orient normals with Fast Marching.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool exportCoordToSF(bool exportDims[3])
Exports the specified coordinate dimension(s) to scalar field(s)
std::vector< int > ClusterDBSCAN(double eps, size_t min_points, bool print_progress=false) const
Cluster ccPointCloud using the DBSCAN algorithm Ester et al., "A Density-Based Algorithm for Discover...
bool orientNormalsWithMST(unsigned kNN=6, ecvProgressDialog *pDlg=nullptr)
Orient the normals with a Minimum Spanning Tree.
bool colorize(float r, float g, float b)
Multiplies all color components of all points by coefficients.
bool hasDisplayedScalarField() const override
Returns whether an active scalar field is available or not.
bool convertNormalToDipDirSFs(ccScalarField *dipSF, ccScalarField *dipDirSF)
Converts normals to two scalar fields: 'dip' and 'dip direction'.
bool exportNormalToSF(bool exportDims[3])
Exports the specified normal dimension(s) to scalar field(s)
bool setCoordFromSF(bool importDims[3], cloudViewer::ScalarField *sf, PointCoordinateType defaultValueForNaN)
Sets coordinate(s) from a scalar field.
int getCurrentDisplayedScalarFieldIndex() const
Returns the currently displayed scalar field index (or -1 if none)
bool hasColors() const override
Returns whether colors are enabled or not.
static ccPointCloud * From(const cloudViewer::GenericIndexedCloud *cloud, const ccGenericPointCloud *sourceCloud=nullptr)
Creates a new point cloud object from a GenericIndexedCloud.
bool setRGBColorByHeight(unsigned char heightDim, ccColorScale::Shared colorScale)
Assigns color to points proportionnaly to their 'height'.
void deleteScalarField(int index) override
Deletes a specific scalar field.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
std::shared_ptr< ccPointCloud > VoxelDownSample(double voxel_size)
Function to downsample input ccPointCloud into output ccPointCloud with a voxel.
bool setRGBColor(ColorCompType r, ColorCompType g, ColorCompType b)
Set a unique color for the whole cloud (shortcut)
bool hasScalarFields() const override
Returns whether one or more scalar fields are instantiated.
std::tuple< std::shared_ptr< ccMesh >, std::vector< size_t > > ComputeConvexHull() const
Function that computes the convex hull of the point cloud using qhull.
bool setRGBColorWithCurrentScalarField(bool mixWithExistingColor=false)
Sets RGB colors with current scalar field (values & parameters)
void invertNormals()
Inverts normals (if any)
size_t gridCount() const
Returns the number of associated grids.
Colored polyline.
Definition: ecvPolyline.h:24
const ecvColor::Rgb & getColor() const
Returns the polyline color.
Definition: ecvPolyline.h:99
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
bool apply(ccPointCloud *cloud)
Applies operation on a given cloud.
bool getCompositeStatus()
Returns if to export Composite channel as SF.
bool getBStatus()
Returns if to export B channel as SF.
bool getRStatus()
Returns if to export R channel as SF.
bool getGStatus()
Returns if to export G channel as SF.
A scalar field associated to display-related parameters.
const ccColorScale::Shared & getColorScale() const
Returns associated color scale.
void setMinDisplayed(ScalarType val)
Sets the minimum displayed value.
void setColorScale(ccColorScale::Shared scale)
Sets associated color scale.
void setSymmetricalScale(bool state)
Sets whether the color scale should be symmetrical or not.
void computeMinAndMax() override
Determines the min and max values.
void setSaturationStart(ScalarType val)
Sets the value at which to start color gradient.
Generic sensor interface.
Definition: ecvSensor.h:27
bool getActiveAbsoluteCenter(CCVector3 &vec) const
Gets currently active absolute position.
Dialog for the Local Statistical Test tool.
int getNeighborsNumber() const
Returns the number of neighbors.
double getParam1() const
Returns 1st parameter value.
double getParam2() const
Returns 2nd parameter value.
double getParam3() const
Returns 3rd parameter value.
double getProba() const
Returns the associated probability.
double getDiagNormd() const
Returns diagonal length (double precision)
Definition: BoundingBox.h:175
const Vector3Tpl< T > & maxCorner() const
Returns max corner (const)
Definition: BoundingBox.h:156
const Vector3Tpl< T > & minCorner() const
Returns min corner (const)
Definition: BoundingBox.h:154
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
Definition: DgmOctree.h:67
virtual void clear()
Clears the octree.
Definition: DgmOctree.cpp:183
int build(GenericProgressCallback *progressCb=nullptr)
Builds the structure.
Definition: DgmOctree.cpp:196
virtual unsigned size() const =0
Returns the number of points.
A generic class to handle a probability distribution.
virtual bool computeParameters(const ScalarContainer &values)=0
Computes the distribution parameters from a set of values.
virtual const char * getName() const =0
Returns distribution name.
The Normal/Gaussian statistical distribution.
bool setParameters(ScalarType _mu, ScalarType _sigma2)
Sets the distribution parameters.
ScalarField * getCurrentOutScalarField() const
Returns the scalar field currently associated to the cloud output.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
void setCurrentOutScalarField(int index)
Sets the OUTPUT scalar field.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
const char * getScalarFieldName(int index) const
Returns the name of a specific scalar field.
ScalarType getPointScalarValue(unsigned pointIndex) const override
void setCurrentInScalarField(int index)
Sets the INPUT scalar field.
ScalarField * getCurrentInScalarField() const
Returns the scalar field currently associated to the cloud input.
bool isClosed() const
Returns whether the polyline is closed or not.
Definition: Polyline.h:26
static bool applyScalarFieldGaussianFilter(PointCoordinateType sigma, GenericIndexedCloudPersist *theCloud, PointCoordinateType sigmaSF, GenericProgressCallback *progressCb=nullptr, DgmOctree *theOctree=nullptr)
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
std::size_t countValidValues() const
Returns the number of valid values in this scalar field.
Definition: ScalarField.cpp:29
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
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
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
unsigned currentSize() const
Definition: ScalarField.h:100
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
static double testCloudWithStatisticalModel(const GenericDistribution *distrib, GenericIndexedCloudPersist *theCloud, unsigned numberOfNeighbours, double pTrust, GenericProgressCallback *progressCb=nullptr, DgmOctree *inputOctree=nullptr)
static double computeAdaptativeChi2Dist(const GenericDistribution *distrib, const GenericCloud *cloud, unsigned numberOfClasses, unsigned &finalNumberOfClasses, bool noClassCompression=false, ScalarType *histoMin=nullptr, ScalarType *histoMax=nullptr, unsigned *histoValues=nullptr, double *npis=nullptr)
Computes the Chi2 distance on a sample of scalar values.
The Weibull statistical parametric distribution.
ScalarType getValueShift() const
Returns the distribution value shift.
bool setParameters(ScalarType a, ScalarType b, ScalarType valueShift=0)
Sets the distribution parameters.
bool getParameters(ScalarType &a, ScalarType &b) const
Returns the distribution parameters.
double computeSkewness() const
Returns the distribution 'skewness'.
double computeMode() const
Returns the distribution 'mode'.
Dialog to input 2 values with custom labels.
static Rgb Random(bool lightOnly=true)
Generates a random color.
RGB color structure.
Definition: ecvColorTypes.h:49
static void ChangeEntityProperties(PROPERTY_PARAM &propertyParam, bool autoUpdate=true)
static void SetRedrawRecursive(bool redraw=false)
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
static const ParamStruct & Parameters()
Returns the stored values of each parameter.
Main application interface (for plugins)
virtual QMainWindow * getMainWindow()=0
Returns main window.
Graphical progress indicator (thread-safe)
virtual void start() override
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
#define CC_DEFAULT_ID_SF_NAME
Definition: ecvCommon.h:41
#define CC_DEFAULT_DIP_SF_NAME
Definition: ecvCommon.h:38
#define CC_CHI2_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:22
#define CC_DEFAULT_DIP_DIR_SF_NAME
Definition: ecvCommon.h:39
void ConvertToGroup(const ccHObject::Container &origin, ccHObject &dest, int dependencyFlags=ccHObject::DP_NONE)
Puts all entities inside a container in a group.
Definition: ecvHObject.h:742
cloudViewer object type flags
Definition: CVTypes.h:100
@ SENSOR
Definition: CVTypes.h:116
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
@ PRIMITIVE
Definition: CVTypes.h:119
@ MESH
Definition: CVTypes.h:105
@ POINT_CLOUD
Definition: CVTypes.h:104
@ POLY_LINE
Definition: CVTypes.h:112
@ FACET
Definition: CVTypes.h:109
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
bool VoxelSampling(const ccHObject::Container &selectedEntities, ccHObject::Container &outEntities, QWidget *parent)
bool exportNormalToSF(const ccHObject::Container &selectedEntities, QWidget *parent, bool *exportDimensions)
bool invertNormals(const ccHObject::Container &selectedEntities)
bool sfSetAsCoord(const ccHObject::Container &selectedEntities, QWidget *parent)
bool statisticalTest(const ccHObject::Container &selectedEntities, QWidget *parent)
static QString GetFirstAvailableSFName(const ccPointCloud *cloud, const QString &baseName)
bool sfRename(const ccHObject::Container &selectedEntities, QWidget *parent)
bool convertTextureToColor(const ccHObject::Container &selectedEntities, QWidget *parent)
bool computeOctree(const ccHObject::Container &selectedEntities, QWidget *parent)
bool orientNormalsFM(const ccHObject::Container &selectedEntities, QWidget *parent)
bool sfFromColor(const ccHObject::Container &selectedEntities, QWidget *parent)
bool changeColorLevels(const ccHObject::Container &selectedEntities, QWidget *parent)
bool computeStatParams(const ccHObject::Container &selectedEntities, QWidget *parent)
bool orientNormalsMST(const ccHObject::Container &selectedEntities, QWidget *parent)
bool interpolateColors(const ccHObject::Container &selectedEntities, QWidget *parent)
Interpolate colors from on entity and transfer them to another one.
bool sfBilateralFilter(const ccHObject::Container &selectedEntities, QWidget *parent)
bool rgbToGreyScale(const ccHObject::Container &selectedEntities)
bool ConvexHull(const ccHObject::Container &selectedEntities, ccHObject::Container &outEntities, QWidget *parent)
bool clearProperty(ccHObject::Container selectedEntities, CLEAR_PROPERTY property, QWidget *parent)
bool sfGaussianFilter(const ccHObject::Container &selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget *parent)
bool sfAddIdField(const ccHObject::Container &selectedEntities)
bool toggleProperty(const ccHObject::Container &selectedEntities, TOGGLE_PROPERTY property)
bool enhanceRGBWithIntensities(const ccHObject::Container &selectedEntities, QWidget *parent)
bool setColorGradient(const ccHObject::Container &selectedEntities, QWidget *parent)
bool exportCoordToSF(const ccHObject::Container &selectedEntities, QWidget *parent)
bool setColor(ccHObject::Container selectedEntities, bool colorize, QWidget *parent)
bool interpolateSFs(const ccHObject::Container &selectedEntities, ecvMainAppInterface *app)
Interpolate scalar fields from on entity and transfer them to another one.
bool RansacSegmentation(const ccHObject::Container &selectedEntities, ccHObject::Container &outEntities, QWidget *parent)
bool convertNormalsTo(const ccHObject::Container &selectedEntities, NORMAL_CONVERSION_DEST dest)
Converts a cloud's normals.
bool importToSF(const ccHObject::Container &selectedEntities, const std::vector< std::vector< ScalarType >> &scalarsVector, const std::string &name)
bool sfArithmetic(const ccHObject::Container &selectedEntities, QWidget *parent)
bool DBScanCluster(const ccHObject::Container &selectedEntities, QWidget *parent)
static PointCoordinateType GetDefaultValueForNaN(PointCoordinateType minSFValue, QWidget *parent)
NORMAL_CONVERSION_DEST
Normals conversion destinations.
bool processMeshSF(const ccHObject::Container &selectedEntities, ccMesh::MESH_SCALAR_FIELD_PROCESS process, QWidget *parent)
bool sfConvertToRandomRGB(const ccHObject::Container &selectedEntities, QWidget *parent)
bool computeNormals(const ccHObject::Container &selectedEntities, QWidget *parent)
bool sfConvertToRGB(const ccHObject::Container &selectedEntities, QWidget *parent)
bool rgbGaussianFilter(const ccHObject::Container &selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget *parent)
PointCoordinateType GetDefaultCloudKernelSize(ccGenericPointCloud *cloud, unsigned knn)
Returns a default first guess for algorithms kernel size (one cloud)
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
constexpr Rgb white(MAX, MAX, MAX)
Rgb FromQColor(QColor qColor)
Conversion from QColor.
void DisplayLockedVerticesWarning(const QString &meshName, bool displayAsError)
Display a warning or error for locked verts.
Definition: ecvUtils.cpp:13
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::DgmOctree * octree
Generic interpolation parameters.
QSharedPointer< Grid > Shared
Shared type.
unsigned displayedNumPrecision
Displayed numbers precision.
Backup "context" for an object.