ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qCanupo.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 "qCanupo.h"
9 
10 // local
12 
13 #include "qCanupo2DViewDialog.h"
14 #include "qCanupoClassifDialog.h"
15 #include "qCanupoCommands.h"
17 #include "qCanupoTools.h"
18 #include "qCanupoTrainingDialog.h"
19 #include "trainer.h"
20 
21 // cloudViewer
22 #include <CloudSamplingTools.h>
23 #include <ReferenceCloud.h>
24 
25 // CV_DB_LIB
26 #include <ecvDisplayTools.h>
27 #include <ecvOctree.h>
28 #include <ecvOctreeProxy.h>
29 #include <ecvPointCloud.h>
30 #include <ecvPolyline.h>
31 #include <ecvProgressDialog.h>
32 #include <ecvScalarField.h>
33 #include <ecvSphere.h>
34 
35 // Qt
36 #include <QApplication>
37 #include <QMessageBox>
38 #include <QStringList>
39 #include <QtCore>
40 #include <QtGui>
41 
42 qCanupoPlugin::qCanupoPlugin(QObject* parent /*=0*/)
43  : QObject(parent),
44  ccStdPluginInterface(":/CC/plugin/qCanupoPlugin/info.json"),
45  m_classifyAction(0),
46  m_trainAction(0) {}
47 
49  const ccHObject::Container& selectedEntities) {
50  if (m_classifyAction) {
51  // classification: only one point cloud
52  m_classifyAction->setEnabled(
53  selectedEntities.size() == 1 &&
54  selectedEntities[0]->isA(CV_TYPES::POINT_CLOUD));
55  }
56 
57  if (m_trainAction) {
58  m_trainAction->setEnabled(m_app && m_app->dbRootObject() &&
60  0); // need some loaded entities to
61  // train the classifier!
62  }
63 
64  m_selectedEntities = selectedEntities;
65 }
66 
67 QList<QAction*> qCanupoPlugin::getActions() {
68  QList<QAction*> group;
69 
70  if (!m_trainAction) {
71  m_trainAction = new QAction(tr("Train classifier"), this);
72  m_trainAction->setToolTip(tr("Train classifier"));
73  m_trainAction->setIcon(QIcon(QString::fromUtf8(
74  ":/CC/plugin/qCanupoPlugin/images/iconCreate.png")));
75  connect(m_trainAction, SIGNAL(triggered()), this,
76  SLOT(doTrainAction()));
77  }
78  group.push_back(m_trainAction);
79 
80  if (!m_classifyAction) {
81  m_classifyAction = new QAction(tr("Classify"), this);
82  m_classifyAction->setToolTip(tr("Classify cloud"));
83  m_classifyAction->setIcon(QIcon(QString::fromUtf8(
84  ":/CC/plugin/qCanupoPlugin/images/iconClassify.png")));
85  connect(m_classifyAction, SIGNAL(triggered()), this,
86  SLOT(doClassifyAction()));
87  }
88  group.push_back(m_classifyAction);
89 
90  return group;
91 }
92 
94  if (!m_app) {
95  assert(false);
96  return;
97  }
98 
99  // disclaimer accepted?
101  return;
102  }
103 
104  if (m_selectedEntities.empty() ||
105  !m_selectedEntities.front()->isA(CV_TYPES::POINT_CLOUD)) {
106  m_app->dispToConsole(tr("Select one and only one point cloud!"),
108  return;
109  }
110 
111  ccPointCloud* cloud =
112  static_cast<ccPointCloud*>(m_selectedEntities.front());
113 
114  // display dialog
115  qCanupoClassifDialog cDlg(cloud, m_app);
116  if (!cDlg.exec()) {
117  // process cancelled by the user
118  return;
119  }
121 
122  // store parameters
124  {
125  params.confidenceThreshold = cDlg.getConfidenceTrehshold();
126  params.generateAdditionalSF =
127  cDlg.generateAdditionalSFsCheckBox->isChecked();
128  params.generateRoughnessSF =
129  cDlg.generateRoughnessSFsCheckBox->isChecked();
130  params.maxThreadCount = cDlg.getMaxThreadCount();
131  params.useActiveSFForConfidence = cDlg.useSF();
132  params.samplingDist = 0; // only set if we subsample the input cloud!
133  }
134  QString classifierFilename = cDlg.classifFileLineEdit->text();
135 
136  // how should we generate the core points?
139  PointCoordinateType samplingDist = static_cast<PointCoordinateType>(
140  cDlg.cpSubsamplingDoubleSpinBox->value());
141 
142  CorePointDescSet corePointsDescriptors; // core point descriptors
143  ccPointCloud* realCorePoints =
144  0; // the core point cloud (as a real point cloud, if available)
146  0; // the core points, potentially as references!
147 
148  switch (coreSource) {
151  realCorePoints = cDlg.getCorePointsCloud();
152  if (!realCorePoints) {
153  assert(false);
155  tr("Internal error: failed to access core pointss?!"),
157  return;
158  }
159  corePoints = realCorePoints;
160  } break;
161 
163  // progress dialog
164  ecvProgressDialog pDlg(true, m_app->getActiveWindow());
165 
166  assert(samplingDist > 0);
168  false);
169  cloudViewer::ReferenceCloud* refCloud =
171  cloud, samplingDist, modParams, 0, &pDlg);
172  if (!refCloud) {
174  tr("Failed to compute sub-sampled core points!"),
176  return;
177  }
178 
179  params.samplingDist = samplingDist;
180 
181  // try to convert the references into a real point cloud (not
182  // mandatory though!)
183  realCorePoints = cloud->partialClone(refCloud);
184  if (realCorePoints) {
185  realCorePoints->setName(
186  cloud->getName() +
187  tr(".core points (subsampled @ %1)").arg(samplingDist));
188  cloud->addChild(realCorePoints);
189  m_app->addToDB(realCorePoints);
190  corePoints = realCorePoints;
191  } else {
193  tr("Can't save subsampled cloud (not enough memory)!"));
194  }
195 
196  delete refCloud;
197  refCloud = 0;
198  } break;
199 
201  realCorePoints = new ccPointCloud(tr("MSC core points"));
202  QString filenmae = cDlg.getMscFilename();
203  QString error;
204  if (!corePointsDescriptors.loadFromMSC(filenmae, error,
205  realCorePoints)) {
206  // failed to read the input MSC file
207  if (realCorePoints) {
208  delete realCorePoints;
209  realCorePoints = 0;
210  }
213  return;
214  } else if (!error.isNull()) {
215  // then it's just a warning
216  m_app->dispToConsole(tr("[qCanupo] ") + error,
218  }
219 
220  assert(realCorePoints);
221  cloud->addChild(realCorePoints);
222  m_app->addToDB(realCorePoints);
223  corePoints = realCorePoints;
224  } break;
225 
226  default: {
227  assert(false);
229  tr("Internal error: no core point source specified?!"),
231  return;
232  }
233  }
234 
235  assert(corePoints);
236 
237  if (qCanupoProcess::Classify(classifierFilename, params, cloud, corePoints,
238  corePointsDescriptors, realCorePoints, m_app,
239  m_app->getActiveWindow())) {
240  // cloud->prepareDisplayForRefresh();
242  cloud->setRedraw(true);
243  m_app->refreshAll();
244  m_app->updateUI();
245  }
246 
247  // dispose of the 'virtual' core points (if any)
248  if (corePoints != realCorePoints) {
249  delete corePoints;
250  corePoints = 0;
251  }
252 }
253 
255  // disclaimer accepted?
256  if (!ShowTrainDisclaimer(m_app)) return;
257 
258  // if (m_selectedEntities.size() != 2
259  // || !m_selectedEntities[0]->isA(CV_TYPES::POINT_CLOUD)
260  // || !m_selectedEntities[1]->isA(CV_TYPES::POINT_CLOUD))
261  //{
262  // m_app->dispToConsole("Select two point
263  // clouds!",ecvMainAppInterface::ERR_CONSOLE_MESSAGE); return;
264  // }
265  //
266  // ccPointCloud* cloud1 = static_cast<ccPointCloud*>(m_selectedEntities[0]);
267  // ccPointCloud* cloud2 = static_cast<ccPointCloud*>(m_selectedEntities[1]);
268 
269  // display dialog
270  qCanupoTrainingDialog ctDlg(/*cloud1, cloud2, */ m_app);
271  if (!ctDlg.exec()) return;
273 
274  // get scales
275  std::vector<float> scales;
276  {
277  if (!ctDlg.getScales(scales)) {
278  m_app->dispToConsole(tr("Invalid scale parameters!"),
280  return;
281  }
282  // make sure values are in descending order!
283  std::sort(scales.begin(), scales.end(), std::greater<float>());
284  }
285 
286  ccPointCloud* originCloud = ctDlg.getOriginPointCloud();
287  ccPointCloud* cloud1 = ctDlg.getClass1Cloud();
288  ccPointCloud* cloud2 = ctDlg.getClass2Cloud();
289  if (!cloud1 || !cloud2) {
290  if (m_app)
292  tr("At least one cloud (class #1 or #2) was not defined!"),
294  return;
295  }
296 
297  assert(cloud1 != cloud2);
298  ccPointCloud* evaluationCloud = ctDlg.getEvaluationCloud();
299 
300  // Descriptor ID
301  unsigned descriptorID = ctDlg.getDescriptorID();
302  // check that the selected descriptor (computer) is valid
303  {
304  assert(descriptorID != 0);
306  ScaleParamsComputer::GetByID(descriptorID);
307  if (!computer) {
308  if (m_app)
310  tr("Internal error: unhandled descriptor ID (%1)!")
311  .arg(descriptorID),
313  return;
314  }
315  if (computer->needSF() &&
316  (cloud1->getCurrentDisplayedScalarField() == 0 ||
317  cloud2->getCurrentDisplayedScalarField() == 0 ||
318  (evaluationCloud &&
319  evaluationCloud->getCurrentDisplayedScalarField() == 0))) {
320  if (m_app)
322  tr("To compute this type of descriptor, all clouds "
323  "must have an active scalar field!"),
325  return;
326  }
327  }
328 
329  // sub-sampled clouds
332  cloudViewer::GenericIndexedCloudPersist* evaluationPoints = 0;
333 
334  // progress dialog
335  ecvProgressDialog pDlg(true, m_app->getActiveWindow());
336 
337  while (true) {
338  // sub-sample clouds (if necessary)
339  {
340  assert(ctDlg.maxPointsSpinBox->value() > 0);
341  unsigned maxCorePoints =
342  static_cast<unsigned>(ctDlg.maxPointsSpinBox->value());
343 
344  // if the user has specified a third cloud for behavior
345  // representation
346  if (evaluationCloud) {
347  if (evaluationCloud->size() > maxCorePoints)
348  evaluationPoints = cloudViewer::CloudSamplingTools::
349  subsampleCloudRandomly(evaluationCloud,
350  maxCorePoints, &pDlg);
351  else
352  evaluationPoints = evaluationCloud;
353 
354  if (!evaluationPoints) {
356  tr("Failed to compute sub-sampled version of "
357  "evaluation points!"),
359  break;
360  }
361  }
362 
363  // for class clouds, we take the smallest count (if inferior to the
364  // specified limit)
365  if (maxCorePoints > cloud1->size()) maxCorePoints = cloud1->size();
366  if (maxCorePoints > cloud2->size()) maxCorePoints = cloud2->size();
367 
368  if (cloud1->size() > maxCorePoints)
369  corePoints1 =
371  cloud1, maxCorePoints, &pDlg);
372  else
373  corePoints1 = cloud1;
374 
375  if (!corePoints1) {
376  m_app->dispToConsole(tr("Failed to compute sub-sampled version "
377  "of cloud #1!"),
379  break;
380  }
381 
382  if (cloud2->size() > maxCorePoints)
383  corePoints2 =
385  cloud2, maxCorePoints, &pDlg);
386  else
387  corePoints2 = cloud2;
388 
389  if (!corePoints2) {
390  m_app->dispToConsole(tr("Failed to compute sub-sampled version "
391  "of cloud #1!"),
393  break;
394  }
395  }
396 
397  // compute MSC data for cloud #1
398  CorePointDescSet descriptors1;
399  {
400  bool invalidDescriptors = false;
401  QString errorStr;
403  descriptors1,
404  //if the origin cloud was specified, then we'll use it as base cloud for descriptors
405  originCloud ? originCloud : cloud1,
406  scales,
408  errorStr,
409  descriptorID,
410  ctDlg.getMaxThreadCount(),
411  &pDlg/*,
412  octree*/))
413  {
415  tr("Failed to compute core points descriptors: %1")
416  .arg(errorStr),
418  break;
419  } else if (invalidDescriptors) {
421  tr("[qCanupo] Some descriptors couldn't be computed on "
422  "cloud#1 (min scale may be too small)!"),
424  }
425  }
426 
427  // compute MSC data for cloud #2
428  CorePointDescSet descriptors2;
429  {
430  bool invalidDescriptors = false;
431  QString errorStr;
433  descriptors2,
434  //if the origin cloud was specified, then we'll use it as base cloud for descriptors
435  originCloud ? originCloud : cloud2,
436  scales,
438  errorStr,
439  descriptorID,
440  ctDlg.getMaxThreadCount(),
441  &pDlg/*,
442  octree*/))
443  {
445  tr("Failed to compute core points descriptors: %1")
446  .arg(errorStr),
448  break;
449  } else if (invalidDescriptors) {
451  tr("[qCanupo] Some descriptors couldn't be computed on "
452  "cloud#2 (min scale may be too small)!"),
454  }
455  }
456 
457  // if the user has specified a third cloud for behavior representation
458  // we must compute its descriptors now
459  CorePointDescSet evaluationDescriptors;
460  if (evaluationPoints) {
461  // computes the 'descriptors'
462  bool invalidDescriptors = false;
463  QString errorStr;
465  evaluationPoints, evaluationDescriptors,
466  evaluationCloud, scales, invalidDescriptors, errorStr,
467  descriptorID, ctDlg.getMaxThreadCount(), &pDlg)) {
469  tr("Failed to compute core points descriptors: %1")
470  .arg(errorStr),
472  break;
473  }
474 
475  if (invalidDescriptors) {
477  tr("[qCanupo] Some descriptors couldn't be computed on "
478  "evaluation cloud (min scale may be too small)!"),
480  }
481  }
482 
483  // now for the Classifier training!
484  {
485  qCanupo2DViewDialog c2DDlg(
486  &descriptors1, &descriptors2, cloud1->getName(),
487  cloud2->getName(), ctDlg.cloud1ClassSpinBox->value(),
488  ctDlg.cloud2ClassSpinBox->value(),
489  evaluationPoints ? &evaluationDescriptors : 0, m_app);
490 
491  // we need the 3D view to be visible before updating the zoom!
492  c2DDlg.show();
493  c2DDlg.setWindowModality(Qt::ApplicationModal);
494  QApplication::processEvents();
495 
496  if (!c2DDlg.trainClassifier()) {
497  // a real error message should have already been issued
499  tr("[qCanupo] Classifier training failed..."),
501  break;
502  }
503 
504  c2DDlg.exec();
505  }
506 
507  // end of the story!
508  break;
509  }
510 
511  if (corePoints1 != cloud1) delete corePoints1;
512  if (corePoints2 != cloud2) delete corePoints2;
513  if (evaluationPoints != evaluationCloud) delete evaluationPoints;
514 }
515 
516 void qCanupoPlugin::registerCommands(ccCommandLineInterface* cmd) {
517  if (!cmd) {
518  assert(false);
519  return;
520  }
521  cmd->registerCommand(
523 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
cmdLineReadable * params[]
Set of (core) point descriptors.
bool loadFromMSC(QString filename, QString &error, ccPointCloud *corePoints=0)
Loads structure of descriptors from an ".msc" file (see Brodu's version)
Generic parameters 'computer' class (at a given scale)
static ScaleParamsComputer * GetByID(unsigned descID)
Vault: returns the computer corresponding to the given ID.
virtual bool needSF() const
Returns whether the computer requires a scalar field or not.
Command line interface.
virtual bool registerCommand(Command::Shared command)=0
Registers a new command.
virtual void setRedraw(bool state)
Sets entity redraw mode.
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.
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
ccPointCloud * partialClone(const cloudViewer::ReferenceCloud *selection, int *warnings=nullptr, bool withChildEntities=true) const
Creates a new point cloud object from a ReferenceCloud (selection)
Standard ECV plugin interface.
virtual void onNewSelection(const ccHObject::Container &selectedEntities)
ecvMainAppInterface * m_app
Main application interface.
Several point cloud resampling algorithms (octree-based, random, etc.)
static ReferenceCloud * subsampleCloudRandomly(GenericIndexedCloudPersist *cloud, unsigned newNumberOfPoints, GenericProgressCallback *progressCb=nullptr)
Subsamples a point cloud (process based on random selections)
static ReferenceCloud * resampleCloudSpatially(GenericIndexedCloudPersist *cloud, PointCoordinateType minDistance, const SFModulationParams &modParams, DgmOctree *octree=nullptr, GenericProgressCallback *progressCb=nullptr)
Resamples a point cloud (process based on inter point distance)
A generic 3D point cloud with index-based and presistent access to points.
unsigned size() const override
Definition: PointCloudTpl.h:38
A very simple point cloud (no point duplication)
static void SetRedrawRecursive(bool redraw=false)
virtual void updateUI()=0
virtual ccHObject * dbRootObject()=0
Returns DB root (as a ccHObject)
virtual QWidget * getActiveWindow()=0
virtual void refreshAll(bool only2D=false, bool forceRedraw=true)=0
Redraws all GL windows that have the 'refresh' flag on.
virtual void addToDB(ccHObject *obj, bool updateZoom=false, bool autoExpandDBTree=true, bool checkDimensions=false, bool autoRedraw=true)=0
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
Graphical progress indicator (thread-safe)
CANUPO plugin's 2D view dialog.
bool trainClassifier()
Trains the classifier (with the current number of scales!)
CANUPO plugin's classification dialog.
ccPointCloud * getCorePointsCloud()
Get core points cloud (if any)
QString getMscFilename() const
Returns MSC file name (if source == MSC_FILE)
void saveParamsToPersistentSettings()
Saves parameters to persistent settings.
CORE_CLOUD_SOURCES getCorePointsCloudSource() const
Returns the selected source for core points.
double getConfidenceTrehshold() const
Returns the confidence threshold.
int getMaxThreadCount() const
Returns the max number of threads to use.
CORE_CLOUD_SOURCES
"Sources" of core points
void doClassifyAction()
Definition: qCanupo.cpp:93
void doTrainAction()
Definition: qCanupo.cpp:254
ccHObject::Container m_selectedEntities
Currently selected entities.
Definition: qCanupo.h:48
QAction * m_classifyAction
Calssift action.
Definition: qCanupo.h:43
QAction * m_trainAction
Train action.
Definition: qCanupo.h:45
static bool Classify(QString classifierFilename, const ClassifyParams &params, ccPointCloud *cloud, cloudViewer::GenericIndexedCloudPersist *corePoints, CorePointDescSet &corePointsDescriptors, ccPointCloud *realCorePoints=nullptr, ecvMainAppInterface *app=nullptr, QWidget *parentWidget=nullptr, bool silent=false)
Classify a point cloud.
static bool ComputeCorePointsDescriptors(cloudViewer::GenericIndexedCloud *corePoints, CorePointDescSet &corePointsDescriptors, ccGenericPointCloud *sourceCloud, const std::vector< float > &sortedScales, bool &invalidDescriptors, QString &error, unsigned descriptorID=DESC_DIMENSIONALITY, int maxThreadCount=0, cloudViewer::GenericProgressCallback *progressCb=0, cloudViewer::DgmOctree *inputOctree=0, std::vector< ccScalarField * > *roughnessSFs=0)
Computes the 'descriptors' for various scales on core points only.
CANUPO plugin's training dialog.
ccPointCloud * getEvaluationCloud()
Get evaluation point cloud.
bool getScales(std::vector< float > &scales) const
Returns input scales.
ccPointCloud * getClass1Cloud()
Get class #1 point cloud.
void saveParamsToPersistentSettings()
Saves parameters to persistent settings.
unsigned getDescriptorID() const
Returns the selected descriptor ID.
ccPointCloud * getOriginPointCloud()
Get origin point cloud.
int getMaxThreadCount() const
Returns the max number of threads to use.
ccPointCloud * getClass2Cloud()
Get class #2 point cloud.
static void error(char *msg)
Definition: lsd.c:159
@ POINT_CLOUD
Definition: CVTypes.h:104
static bool ShowClassifyDisclaimer(ecvMainAppInterface *app)
static bool ShowTrainDisclaimer(ecvMainAppInterface *app)
bool invalidDescriptors
cloudViewer::GenericIndexedCloud * corePoints
ScaleParamsComputer * computer
QSharedPointer< Command > Shared
Shared type.
Parameters for the scalar-field based modulation of a parameter.