ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qRANSAC_SD_Commands.h
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 #pragma once
9 
10 // ACloudViewer
11 #include <ecvGenericMesh.h>
12 #include <ecvMesh.h>
13 
15 
16 // Local
17 #include "qRANSAC_SD.h"
18 
19 // QT
20 #include <QDir>
21 
22 constexpr char COMMAND_RANSAC[] = "RANSAC";
23 constexpr char EPSILON_ABSOLUTE[] = "EPSILON_ABSOLUTE";
24 constexpr char EPSILON_PERCENTAGE_OF_SCALE[] = "EPSILON_PERCENTAGE_OF_SCALE";
26  "BITMAP_EPSILON_PERCENTAGE_OF_SCALE";
27 constexpr char BITMAP_EPSILON_ABSOLUTE[] = "BITMAP_EPSILON_ABSOLUTE";
28 constexpr char SUPPORT_POINTS[] = "SUPPORT_POINTS";
29 constexpr char MAX_NORMAL_DEV[] = "MAX_NORMAL_DEV";
30 constexpr char PROBABILITY[] = "PROBABILITY";
31 constexpr char ENABLE_PRIMITIVE[] = "ENABLE_PRIMITIVE";
32 constexpr char OUT_CLOUD_DIR[] = "OUT_CLOUD_DIR";
33 constexpr char OUT_MESH_DIR[] = "OUT_MESH_DIR";
34 constexpr char OUT_PAIR_DIR[] = "OUT_PAIR_DIR";
35 constexpr char OUT_GROUP_DIR[] = "OUT_GROUP_DIR";
36 constexpr char OUT_RANDOM_COLOR[] = "OUT_RANDOM_COLOR";
37 constexpr char OUTPUT_INDIVIDUAL_PRIMITIVES[] = "OUTPUT_INDIVIDUAL_PRIMITIVES";
38 constexpr char OUTPUT_INDIVIDUAL_SUBCLOUDS[] = "OUTPUT_INDIVIDUAL_SUBCLOUDS";
40  "OUTPUT_INDIVIDUAL_PAIRED_CLOUD_PRIMITIVE";
41 constexpr char OUTPUT_GROUPED[] = "OUTPUT_GROUPED";
42 
43 constexpr char PRIM_PLANE[] = "PLANE";
44 constexpr char PRIM_SPHERE[] = "SPHERE";
45 constexpr char PRIM_CYLINDER[] = "CYLINDER";
46 constexpr char PRIM_CONE[] = "CONE";
47 constexpr char PRIM_TORUS[] = "TORUS";
48 
51  : ccCommandLineInterface::Command(QObject::tr("RANSAC"),
52  COMMAND_RANSAC) {}
53 
54  virtual bool process(ccCommandLineInterface& cmd) override {
55  if (cmd.clouds().empty()) {
56  return cmd.error(QObject::tr("No point cloud to attempt RANSAC on "
57  "(be sure to open one with \"-O "
58  "[cloud filename]\" before \"-%2\")")
59  .arg(COMMAND_RANSAC));
60  }
61  qRansacSD::RansacParams params;
62  QStringList paramNames =
63  QStringList()
71  QStringList primitiveNames = QStringList() << PRIM_PLANE << PRIM_SPHERE
73  << PRIM_TORUS;
74  QString outputCloudsDir;
75  QString outputMeshesDir;
76  QString outputPairDir;
77  QString outputGroupDir;
78  bool outputIndividualClouds = false;
79  bool outputIndividualPrimitives = false;
80  bool outputIndividualPairs = false;
81  bool outputGrouped = false;
82 
83  float epsilonABS = -1.0f;
84  float epsilonPercentage = -1.0f;
85  float bitmapEpsilonABS = -1.0f;
86  float bitmapEpsilonPercentage = -1.0f;
87  params.epsilon = -1.0f;
88  params.bitmapEpsilon = -1.0f;
89  params.randomColor = false;
90 
91  for (unsigned char k = 0; k < 5; ++k) {
92  params.primEnabled[k] = false;
93  }
94 
95  if (!cmd.arguments().empty()) {
96  QString param = cmd.arguments().takeFirst().toUpper();
97  while (paramNames.contains(param)) {
98  cmd.print(QObject::tr("\t%1 : %2").arg(COMMAND_RANSAC, param));
99  if (param == EPSILON_ABSOLUTE) {
100  if (cmd.arguments().empty()) {
101  return cmd.error(
102  QObject::tr("Missing parameter: number after "
103  "\"-%1 %2\"")
105  }
106  bool ok;
107  float val = cmd.arguments().takeFirst().toFloat(&ok);
108  if (!ok) {
109  return cmd.error("Invalid number for epsilon!");
110  }
111  cmd.print(QObject::tr("\tEpsilon : %1").arg(val));
112  epsilonABS = val;
113  } else if (param == EPSILON_PERCENTAGE_OF_SCALE) {
114  if (cmd.arguments().empty()) {
115  return cmd.error(
116  QObject::tr("Missing parameter: number after "
117  "\"-%1 %2\"")
118  .arg(COMMAND_RANSAC,
120  }
121  bool ok;
122  float val = cmd.arguments().takeFirst().toFloat(&ok);
123  if (!ok || (val <= 0.0f || val >= 1.0f)) {
124  return cmd.error(
125  "Invalid number for epsilon percentage must be "
126  "a float greater than 0.0 and less than 1.0!");
127  }
128  cmd.print(QObject::tr("\tEpsilon : %1").arg(val));
129  epsilonPercentage = val;
130  } else if (param == BITMAP_EPSILON_ABSOLUTE) {
131  if (cmd.arguments().empty()) {
132  return cmd.error(QObject::tr("Missing parameter: "
133  "number after \"-%1 %2\"")
134  .arg(COMMAND_RANSAC,
136  }
137  bool ok;
138  float val = cmd.arguments().takeFirst().toFloat(&ok);
139  if (!ok) {
140  return cmd.error("Invalid number for Bitmap epsilon!");
141  }
142  cmd.print(QObject::tr("\tBitmap Epsilon : %1").arg(val));
143  bitmapEpsilonABS = val;
144  } else if (param == BITMAP_EPSILON_PERCENTAGE_OF_SCALE) {
145  if (cmd.arguments().empty()) {
146  return cmd.error(
147  QObject::tr("Missing parameter: number after "
148  "\"-%1 %2\"")
149  .arg(COMMAND_RANSAC,
151  }
152  bool ok;
153  float val = cmd.arguments().takeFirst().toFloat(&ok);
154  if (!ok || (val <= 0.0f || val >= 1.0f)) {
155  return cmd.error(
156  "Invalid number for Bitmap epsilon must be a "
157  "float greater than 0.0 and less than 1.0!!");
158  }
159  cmd.print(QObject::tr("\tBitmap Epsilon : %1").arg(val));
160  bitmapEpsilonPercentage = val;
161  } else if (param == SUPPORT_POINTS) {
162  if (cmd.arguments().empty()) {
163  return cmd.error(
164  QObject::tr("Missing parameter: number after "
165  "\"-%1 %2\"")
167  }
168  bool ok;
169  unsigned count = cmd.arguments().takeFirst().toUInt(&ok);
170  if (!ok) {
171  return cmd.error(
172  "Invalid number of for support points!");
173  }
174  cmd.print(QObject::tr("\tsupport points: %1").arg(count));
175  params.supportPoints = count;
176  } else if (param == MAX_NORMAL_DEV) {
177  if (cmd.arguments().empty()) {
178  return cmd.error(
179  QObject::tr("Missing parameter: number after "
180  "\"-%1 %2\"")
182  }
183  bool ok;
184  float val = cmd.arguments().takeFirst().toFloat(&ok);
185  if (!ok) {
186  return cmd.error(
187  "Invalid number for Max Normal Deviation!");
188  }
189  cmd.print(QObject::tr("\tMax Normal Deviation : %1")
190  .arg(val));
191  params.maxNormalDev_deg = val;
192  } else if (param == PROBABILITY) {
193  if (cmd.arguments().empty()) {
194  return cmd.error(
195  QObject::tr("Missing parameter: number after "
196  "\"-%1 %2\"")
197  .arg(COMMAND_RANSAC, PROBABILITY));
198  }
199  bool ok;
200  float val = cmd.arguments().takeFirst().toFloat(&ok);
201  if (!ok) {
202  return cmd.error("Invalid number for Probability!");
203  }
204  cmd.print(QObject::tr("\tProbability : %1").arg(val));
205  params.probability = val;
206  } else if (param == OUT_RANDOM_COLOR) {
207  params.randomColor = true;
208  } else if (param == OUT_CLOUD_DIR) {
209  if (!makePathIfPossible(cmd, param, &outputCloudsDir,
210  &outputIndividualClouds)) {
211  return false;
212  }
213  } else if (param == OUT_MESH_DIR) {
214  if (!makePathIfPossible(cmd, param, &outputMeshesDir,
215  &outputIndividualPrimitives)) {
216  return false;
217  }
218  } else if (param == OUT_GROUP_DIR) {
219  if (!makePathIfPossible(cmd, param, &outputGroupDir,
220  &outputGrouped)) {
221  return false;
222  }
223  } else if (param == OUT_PAIR_DIR) {
224  if (!makePathIfPossible(cmd, param, &outputPairDir,
225  &outputIndividualPairs)) {
226  return false;
227  }
228  } else if (param == OUTPUT_INDIVIDUAL_SUBCLOUDS) {
229  outputIndividualClouds = true;
230  } else if (param == OUTPUT_INDIVIDUAL_PRIMITIVES) {
231  outputIndividualPrimitives = true;
232  } else if (param == OUTPUT_INDIVIDUAL_PAIRED_CLOUD_PRIMITIVE) {
233  outputIndividualPairs = true;
234  } else if (param == OUTPUT_GROUPED) {
235  outputGrouped = true;
236  } else if (param == ENABLE_PRIMITIVE) {
237  if (cmd.arguments().empty()) {
238  return cmd.error(
239  QObject::tr("Missing parameter: primitive type "
240  "after \"-%1 %2\"")
242  }
243  QString prim = cmd.arguments().takeFirst().toUpper();
244  unsigned primCount = 0;
245  while (primitiveNames.contains(prim)) {
246  if (prim == PRIM_PLANE) {
247  params.primEnabled[qRansacSD::RPT_PLANE] = true;
248  primCount++;
249  } else if (prim == PRIM_SPHERE) {
250  params.primEnabled[qRansacSD::RPT_SPHERE] = true;
251  primCount++;
252  } else if (prim == PRIM_CYLINDER) {
253  params.primEnabled[qRansacSD::RPT_CYLINDER] = true;
254  primCount++;
255  } else if (prim == PRIM_CONE) {
256  params.primEnabled[qRansacSD::RPT_CONE] = true;
257  primCount++;
258  } else if (prim == PRIM_TORUS) {
259  params.primEnabled[qRansacSD::RPT_TORUS] = true;
260  primCount++;
261  }
262  if (cmd.arguments().empty()) {
263  break;
264  }
265  prim = cmd.arguments().takeFirst().toUpper();
266  }
267  if (primCount > 0) {
268  if (!primitiveNames.contains(prim)) {
269  cmd.arguments().push_front(prim);
270  }
271  } else {
272  return cmd.error(
273  QObject::tr("No valid parameter: primitive "
274  "type after \"-%1 %2\"")
276  }
277  }
278  if (cmd.arguments().empty()) {
279  break;
280  }
281  param = cmd.arguments().takeFirst().toUpper();
282  }
283  if (!paramNames.contains(param)) {
284  cmd.arguments().push_front(param);
285  }
286  }
287  unsigned char primCount = 0;
288  for (unsigned char k = 0; k < 5; ++k) {
289  primCount += static_cast<unsigned>(params.primEnabled[k]);
290  }
291  if (primCount == 0) {
292  cmd.print(QObject::tr("\tDefault Shape Search == %1")
293  .arg(PRIM_PLANE));
294  params.primEnabled[qRansacSD::RPT_PLANE] = true;
295  }
296  if (!outputIndividualClouds && !outputIndividualPrimitives &&
297  !outputGrouped && !outputIndividualPairs) {
298  cmd.print(
299  QObject::tr("\tDefault output == %1").arg(OUTPUT_GROUPED));
300  outputGrouped = true;
301  }
302 
303  for (CLCloudDesc clCloud : cmd.clouds()) {
304  CCVector3 bbMin, bbMax;
305  clCloud.pc->getBoundingBox(bbMin, bbMax);
306  CCVector3 diff = bbMax - bbMin;
307  float scale = std::max(std::max(diff[0], diff[1]), diff[2]);
308  if (epsilonPercentage > 0.0f) {
309  params.epsilon = (epsilonPercentage * scale);
310  }
311  if (bitmapEpsilonPercentage > 0.0f) {
312  params.bitmapEpsilon = (bitmapEpsilonPercentage * scale);
313  }
314  if (epsilonABS > 0.0f) {
315  params.epsilon = epsilonABS;
316  }
317  if (bitmapEpsilonABS > 0.0f) {
318  params.bitmapEpsilon = bitmapEpsilonABS;
319  }
320  if (params.epsilon < 0.0f) {
321  params.epsilon = (0.005f * scale);
322  }
323  if (params.bitmapEpsilon < 0.0f) {
324  params.bitmapEpsilon = (0.01f * scale);
325  }
326 
327  ccHObject* group = qRansacSD::executeRANSAC(clCloud.pc, params,
328  cmd.silentMode());
329 
330  if (group) {
331  if (outputGrouped) {
332  CLGroupDesc clGroup(group,
333  clCloud.basename + "_" +
334  clCloud.pc->getName() +
335  "_RANSAC_DETECTED_SHAPES",
336  outputGroupDir != "" ? outputGroupDir
337  : clCloud.path);
338  QString errorStr = cmd.exportEntity(
339  clGroup, QString(), nullptr,
341  ForceHierarchy);
342  if (!errorStr.isEmpty()) {
343  cmd.warning(errorStr);
344  }
345  }
346  if (outputIndividualPrimitives || outputIndividualClouds ||
347  outputIndividualPairs) {
348  ccHObject::Container meshGroup;
349  unsigned meshCount = group->filterChildren(meshGroup, true,
351  unsigned planeCount = 1;
352  unsigned sphereCount = 1;
353  unsigned cylinderCount = 1;
354  unsigned coneCount = 1;
355  unsigned torusCount = 1;
356 
357  for (auto meshObj : meshGroup) {
358  auto mesh = ccHObjectCaster::ToMesh(meshObj);
359  if (mesh) {
360  QString suffix;
361  if (meshObj->isA(CV_TYPES::PLANE)) {
362  suffix = QString("_PLANE_%1")
363  .arg(planeCount, 4, 10,
364  QChar('0'));
365  planeCount++;
366  } else if (meshObj->isA(CV_TYPES::SPHERE)) {
367  suffix = QString("_SPHERE_%1")
368  .arg(sphereCount, 4, 10,
369  QChar('0'));
370  sphereCount++;
371  } else if (meshObj->isA(CV_TYPES::CYLINDER)) {
372  suffix = QString("_CYLINDER_%1")
373  .arg(cylinderCount, 4, 10,
374  QChar('0'));
375  cylinderCount++;
376  } else if (meshObj->isA(CV_TYPES::CONE)) {
377  suffix = QString("_CONE_%1")
378  .arg(coneCount, 4, 10,
379  QChar('0'));
380  coneCount++;
381  } else if (meshObj->isA(CV_TYPES::TORUS)) {
382  suffix = QString("_TORUS_%1")
383  .arg(torusCount, 4, 10,
384  QChar('0'));
385  torusCount++;
386  }
388  mesh->getParent());
389  if (outputIndividualPairs) {
390  CLGroupDesc clPair(
391  cld,
392  clCloud.basename + "_" +
393  clCloud.pc->getName() + suffix +
394  QString("_pair"),
395  outputPairDir != "" ? outputPairDir
396  : clCloud.path);
397  QString errorStr = cmd.exportEntity(
398  clPair, QString(), nullptr,
400  ForceHierarchy);
401  if (!errorStr.isEmpty()) {
402  cmd.warning(errorStr);
403  }
404  }
405  if (cld) {
406  cld->detachChild(mesh);
407  }
408  CLCloudDesc clCloudp(
409  cld,
410  clCloud.basename + "_" +
411  clCloud.pc->getName() + suffix +
412  QString("_cloud"),
413  outputCloudsDir != "" ? outputCloudsDir
414  : clCloud.path);
415  cmd.clouds().push_back(clCloudp);
416  CLMeshDesc clMesh(
417  mesh,
418  clCloud.basename + "_" +
419  clCloud.pc->getName() + suffix,
420  outputMeshesDir != "" ? outputMeshesDir
421  : clCloud.path);
422  cmd.meshes().push_back(clMesh);
423  if (outputIndividualClouds) {
424  QString errorStr = cmd.exportEntity(clCloudp);
425  if (!errorStr.isEmpty()) {
426  cmd.warning(errorStr);
427  }
428  }
429  if (outputIndividualPrimitives) {
430  QString errorStr = cmd.exportEntity(clMesh);
431  if (!errorStr.isEmpty()) {
432  cmd.warning(errorStr);
433  }
434  }
435  }
436  }
437  }
438  }
439  if (cmd.autoSaveMode()) {
440  QString errorStr =
441  cmd.exportEntity(clCloud); // The original cloud may
442  // have had normals added
443  if (!errorStr.isEmpty()) {
444  cmd.warning(errorStr);
445  }
446  }
447  }
448 
449  return true;
450  }
451 
453  const QString& param,
454  QString* outputPath,
455  bool* performOutput) {
456  if (cmd.arguments().empty()) {
457  return cmd.error(
458  QObject::tr(
459  "\nMissing parameter: Directory after \"-%1 %2\"")
460  .arg(COMMAND_RANSAC, param));
461  }
462  QString arg = cmd.arguments().takeFirst();
463  QDir dir(arg);
464  bool pathExists = dir.exists();
465  if (!pathExists) {
466  cmd.print(
467  QObject::tr("\n%1 Does not exist\tcreating path").arg(arg));
468  pathExists = dir.mkpath(arg);
469  }
470  if (pathExists) {
471  *outputPath = dir.cleanPath(arg);
472  cmd.print(QObject::tr("\t%1 : %2").arg(param, *outputPath));
473  *performOutput = true;
474  } else {
475  cmd.print(QObject::tr("\n%1 path could not be created, skipping %2")
476  .arg(arg, param));
477  }
478  return true;
479  }
480 };
int count
cmdLineReadable * params[]
Command line interface.
virtual QStringList & arguments()=0
Returns the list of arguments.
virtual void warning(const QString &message) const =0
virtual void print(const QString &message) const =0
virtual bool error(const QString &message) const =0
bool silentMode() const
Returns the silent mode.
virtual QString exportEntity(CLEntityDesc &entityDesc, const QString &suffix=QString(), QString *outputFilename=nullptr, ccCommandLineInterface::ExportOptions options=ExportOption::NoOptions)=0
Exports a cloud or a mesh.
virtual std::vector< CLMeshDesc > & meshes()
Currently opened meshes and their filename.
virtual std::vector< CLCloudDesc > & clouds()
Currently opened point clouds and their filename.
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.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
unsigned filterChildren(Container &filteredChildren, bool recursive=false, CV_CLASS_ENUM filter=CV_TYPES::OBJECT, bool strict=false) const
Collects the children corresponding to a certain pattern.
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
int max(int a, int b)
Definition: cutil_math.h:48
@ MESH
Definition: CVTypes.h:105
@ CONE
Definition: CVTypes.h:123
@ TORUS
Definition: CVTypes.h:122
@ SPHERE
Definition: CVTypes.h:121
@ PLANE
Definition: CVTypes.h:120
@ CYLINDER
Definition: CVTypes.h:126
constexpr char BITMAP_EPSILON_PERCENTAGE_OF_SCALE[]
constexpr char OUT_RANDOM_COLOR[]
constexpr char OUT_GROUP_DIR[]
constexpr char PRIM_CYLINDER[]
constexpr char PRIM_PLANE[]
constexpr char PRIM_TORUS[]
constexpr char COMMAND_RANSAC[]
constexpr char OUTPUT_INDIVIDUAL_PRIMITIVES[]
constexpr char OUT_PAIR_DIR[]
constexpr char ENABLE_PRIMITIVE[]
constexpr char OUTPUT_GROUPED[]
constexpr char EPSILON_PERCENTAGE_OF_SCALE[]
constexpr char PRIM_SPHERE[]
constexpr char PRIM_CONE[]
constexpr char EPSILON_ABSOLUTE[]
constexpr char SUPPORT_POINTS[]
constexpr char OUT_CLOUD_DIR[]
constexpr char OUTPUT_INDIVIDUAL_PAIRED_CLOUD_PRIMITIVE[]
constexpr char PROBABILITY[]
constexpr char BITMAP_EPSILON_ABSOLUTE[]
constexpr char OUTPUT_INDIVIDUAL_SUBCLOUDS[]
constexpr char OUT_MESH_DIR[]
constexpr char MAX_NORMAL_DEV[]
Loaded cloud description.
Loaded group description.
Loaded mesh description.
virtual bool process(ccCommandLineInterface &cmd) override
Main process.
bool makePathIfPossible(ccCommandLineInterface &cmd, const QString &param, QString *outputPath, bool *performOutput)
Command(const QString &name, const QString &keyword)
Default constructor.