ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qSRA.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 "qSRA.h"
9 
10 // Local
12 #include "profileImportDlg.h"
13 #include "profileLoader.h"
14 
15 // Qt
16 #include <QFile>
17 #include <QFileInfo>
18 #include <QMainWindow>
19 #include <QMessageBox>
20 #include <QSettings>
21 #include <QtGui>
22 
23 // CV_DB_LIB
24 #include <ecvCone.h>
25 #include <ecvDisplayTools.h>
26 #include <ecvFileUtils.h>
27 #include <ecvHObjectCaster.h>
28 #include <ecvMesh.h>
29 #include <ecvPointCloud.h>
30 #include <ecvPolyline.h>
31 #include <ecvProgressDialog.h>
32 #include <ecvScalarField.h>
33 
34 // System
35 #include <string.h>
36 
37 #include <algorithm>
38 #include <vector>
39 
40 qSRA::qSRA(QObject* parent /*=0*/)
41  : QObject(parent),
42  ccStdPluginInterface(":/CC/plugin/qSRA/info.json"),
43  m_doLoadProfile(0),
44  m_doCompareCloudToProfile(0),
45  m_doProjectCloudDists(0) {}
46 
47 QList<QAction*> qSRA::getActions() {
48  // actions
49  if (!m_doLoadProfile) {
50  m_doLoadProfile = new QAction(tr("Load profile"), this);
51  m_doLoadProfile->setToolTip(
52  tr("Loads the 2D profile of a Surface of Revolution (from a "
53  "dedicated ASCII file)"));
54  m_doLoadProfile->setIcon(QIcon(QString::fromUtf8(
55  ":/CC/plugin/qSRA/images/loadProfileIcon.png")));
56  // connect signal
57  connect(m_doLoadProfile, &QAction::triggered, this, &qSRA::loadProfile);
58  }
59 
62  new QAction(tr("Cloud-SurfRev radial distance"), this);
63  m_doCompareCloudToProfile->setToolTip(tr(
64  "Computes the radial distances between a cloud and a Surface "
65  "of Revolution (polyline/profile, cone or cylinder)"));
66  m_doCompareCloudToProfile->setIcon(QIcon(QString::fromUtf8(
67  ":/CC/plugin/qSRA/images/distToProfileIcon.png")));
68  // connect signal
69  connect(m_doCompareCloudToProfile, &QAction::triggered, this,
71  }
72 
73  if (!m_doProjectCloudDists) {
74  m_doProjectCloudDists = new QAction(tr("2D distance map"), this);
75  m_doProjectCloudDists->setToolTip(
76  tr("Creates the 2D deviation map (radial distances) from a "
77  "Surface or Revolution (unroll)"));
78  m_doProjectCloudDists->setIcon(QIcon(QString::fromUtf8(
79  ":/CC/plugin/qSRA/images/createMapIcon.png")));
80  // connect signal
81  connect(m_doProjectCloudDists, &QAction::triggered, this,
83  }
84 
85  return QList<QAction*>{
89  };
90 }
91 
92 void qSRA::onNewSelection(const ccHObject::Container& selectedEntities) {
93  if (m_doLoadProfile) {
94  // always active
95  }
96 
97  bool validSelection = false;
98  if (selectedEntities.size() == 2) {
99  // we expect a cloud...
100  int cloudIndex = selectedEntities[0]->isA(CV_TYPES::POINT_CLOUD) ? 0
101  : selectedEntities[1]->isA(CV_TYPES::POINT_CLOUD) ? 1
102  : -1;
103  if (cloudIndex != -1) {
104  //... and either a polyline or a cone/cylinder
105  validSelection = (selectedEntities[1 - cloudIndex]->isA(
107  selectedEntities[1 - cloudIndex]->isKindOf(
108  CV_TYPES::CONE));
109  }
110  }
111 
113  m_doCompareCloudToProfile->setEnabled(validSelection);
114  }
115 
116  if (m_doProjectCloudDists) {
117  m_doProjectCloudDists->setEnabled(validSelection);
118  }
119 }
120 
121 // return (and create if necessary) the plugin default destination container
122 const QString QSRA_DEFAULT_CONTAINER_NAME("Profile(s)");
124  if (!app || !app->dbRootObject()) {
125  assert(false);
126  return 0;
127  }
128 
129  // we look in qCC database for a group with the right name (i.e. if it has
130  // already been created)
131  ccHObject::Container groups;
132  app->dbRootObject()->filterChildren(groups, true,
134  for (size_t j = 0; j < groups.size(); ++j) {
135  if (groups[j]->getName() == QSRA_DEFAULT_CONTAINER_NAME)
136  return groups[j];
137  }
138 
139  // otherwise we create it
140  ccHObject* defaultContainer = new ccHObject(QSRA_DEFAULT_CONTAINER_NAME);
141  app->addToDB(defaultContainer);
142 
143  return defaultContainer;
144 }
145 
146 void qSRA::loadProfile() const {
147  if (!m_app) {
148  assert(false);
149  return;
150  }
151 
152  // persistent settings (default import path)
153  QSettings settings;
154  settings.beginGroup("qSRA");
155  QString path = settings.value("importPath", ecvFileUtils::defaultDocPath())
156  .toString();
157 
159  piDlg.setDefaultFilename(path);
160 
161  if (!piDlg.exec()) return;
162 
163  QString filename = piDlg.getFilename();
164  if (filename.isEmpty()) return;
165 
166  // save current import path to persistent settings
167  settings.setValue("importPath", QFileInfo(filename).absolutePath());
168 
169  // get the user defined global axis
170  int axisDim = piDlg.getAxisDimension();
171  assert(axisDim >= 0 && axisDim <= 2);
172 
173  // load profile as a (2D) polyline
174  CCVector3 origin(0, 0, 0);
175  ccPolyline* polyline = ProfileLoader::Load(filename, origin, m_app);
176  if (!polyline) {
177  if (m_app)
178  m_app->dispToConsole(tr("Failed to load file '%1'!").arg(filename),
180  return;
181  }
182 
183  // DGM: the following works only because the axis is colinear with X, Y or
184  // Z!
185  PointCoordinateType heightShift = 0;
186  if (piDlg.absoluteHeightValues()) {
187  heightShift = -origin.u[axisDim];
188  }
189 
190  // apply a visual transformation to see the polyline in the right place
191  {
192  ccGLMatrix trans;
193  CCVector3 T = origin;
194  T.u[axisDim] += heightShift;
195  trans.setTranslation(T);
196  float* mat = trans.data();
197  switch (axisDim) {
198  case 0: // X
199  // invert X and Y
200  mat[0] = 0;
201  mat[1] = 1;
202  mat[4] = 1;
203  mat[5] = 0;
204  break;
205  // case 1: //Y
206  case 2: // Z
207  // invert Z and Y
208  mat[5] = 0;
209  mat[6] = 1;
210  mat[9] = 1;
211  mat[10] = 0;
212  break;
213  default:
214  // nothing to do
215  break;
216  }
217  polyline->set2DMode(false);
218  polyline->setGLTransformation(trans);
219  }
220 
221  // set meta-data
225 
226  // default destination container
227  ccHObject* defaultContainer = GetDefaultContainer(m_app);
228  if (defaultContainer) {
229  defaultContainer->addChild(polyline);
230  }
231 
232  m_app->addToDB(polyline, true, false, true);
233 
235  tr("[qSRA] File '%1' successfully loaded").arg(filename),
237 }
238 
239 // helper
241  if (!cone) {
242  assert(false);
243  return 0;
244  }
245 
246  // we deduce the profile orientation and position from the cone 4x4
247  // transformation
248  ccGLMatrix& coneTrans = cone->getTransformation();
249 
250  CCVector3 axis = coneTrans.getColumnAsVec3D(2);
251  CCVector3 origin = coneTrans.getTranslationAsVec3D();
253  // we'll use the 'largest' axis dimension as 'revolution dimension'
254  int revolDim = 0;
255  for (int i = 1; i < 3; ++i)
256  if (fabs(axis.u[i]) > fabs(axis.u[revolDim])) revolDim = i;
257 
258  // the profile has only one segment
259  ccPointCloud* vertices = new ccPointCloud("vertices");
260  {
261  if (!vertices->reserve(2)) {
262  delete vertices;
263  CVLog::Error("Not enough memory");
264  return 0;
265  }
266 
267  vertices->addPoint(CCVector3(cone->getBottomRadius(), -height / 2, 0));
268  vertices->addPoint(CCVector3(cone->getTopRadius(), height / 2, 0));
269  }
270 
271  ccPolyline* polyline = new ccPolyline(vertices);
272  {
273  polyline->addChild(vertices);
274  if (!polyline->reserve(2)) {
275  delete polyline;
276  CVLog::Error("Not enough memory");
277  return 0;
278  }
279  polyline->addPointIndex(0, 2);
280  polyline->setClosed(false);
281  }
282 
283  // apply a visual transformation to see the polyline in the right place
284  {
285  CCVector3 y(0, 1, 0);
286  CCVector3 Z(0, 0, 0);
287  Z.u[revolDim] = PC_ONE;
288  ccGLMatrix axisTrans = ccGLMatrix::FromToRotation(y, Z);
289  assert(cloudViewer::LessThanEpsilon(((axisTrans * y) - Z).norm()));
290  ccGLMatrix polyMat = coneTrans * axisTrans;
291  polyline->setGLTransformation(polyMat);
292  }
293 
294  // set meta-data
298  0 /*height / 2*/);
300 
301  return polyline;
302 }
303 
305  if (!m_app) {
306  assert(false);
307  return;
308  }
309 
310  const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
311  if (selectedEntities.size() != 2) {
312  assert(false);
313  return;
314  }
315 
316  // retrieve input cloud and polyline
317  ccPointCloud* cloud = 0;
318  ccPolyline* polyline = 0;
319  bool tempPolyline = false;
320  {
321  for (unsigned i = 0; i < 2; ++i) {
322  if (selectedEntities[i]->isA(CV_TYPES::POINT_CLOUD)) {
323  cloud = static_cast<ccPointCloud*>(selectedEntities[i]);
324  } else if (selectedEntities[i]->isA(CV_TYPES::POLY_LINE)) {
325  polyline = static_cast<ccPolyline*>(selectedEntities[i]);
326  } else if (!polyline &&
327  selectedEntities[i]->isKindOf(CV_TYPES::CONE)) {
328  // special case: we can deduce the polyline from the
329  // cone/cylinder parameters
330  ccCone* cone = static_cast<ccCone*>(selectedEntities[i]);
331  polyline = GetConeProfile(cone);
332  if (!polyline) {
333  // the conversion failed?!
334  return;
335  }
336  tempPolyline = true;
337 
338 #ifdef _DEBUG
339  // test: apply a visual transformation to see the polyline in
340  // the right place
341  {
342  polyline->set2DMode(false);
343  polyline->setWidth(2);
344  polyline->setColor(ecvColor::green);
345  polyline->showColors(true);
346  cone->setVisible(false);
347  cone->addChild(polyline);
348  m_app->addToDB(polyline);
349  tempPolyline = false;
350  }
351 #endif
352  }
353  }
354  }
355 
356  if (cloud && polyline) {
357  if (doComputeRadialDists(cloud, polyline)) {
358  // automatically ask the user if he wants to generate a 2D map
359  if (QMessageBox::question(
360  m_app ? m_app->getMainWindow() : nullptr,
361  tr("Generate map"),
362  tr("Do you want to generate a 2D deviation map?"),
363  QMessageBox::Yes,
364  QMessageBox::No) == QMessageBox::Yes) {
365  doProjectCloudDistsInGrid(cloud, polyline);
366  }
367  }
368  } else {
369  if (m_app)
371  tr("Select exactly one cloud and one Surface of Revolution "
372  "(polyline/profile, cone or cylinder)"),
374  }
375 
376  if (polyline && tempPolyline) {
377  delete polyline;
378  polyline = 0;
379  }
380 }
381 
383  ccPolyline* polyline) const {
384  if (!cloud || !polyline) {
385  assert(false);
386  return false;
387  }
388 
389  if (DistanceMapGenerationTool::ComputeRadialDist(cloud, polyline, false,
390  m_app)) {
391  // cloud->prepareDisplayForRefresh();
392  if (m_app) {
393  m_app->updateUI();
395  cloud->setRedraw(true);
396  m_app->refreshAll();
397  }
398  return true;
399  } else {
400  if (m_app)
402  tr("An error occurred while computing radial distances!"),
404  return false;
405  }
406 }
407 
409  if (!m_app) {
410  assert(false);
411  return;
412  }
413 
414  const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
415  size_t selectCount = selectedEntities.size();
416  if (selectCount != 1 && selectCount != 2) {
417  assert(false);
418  return;
419  }
420 
421  // retrieve input cloud and polyline
422  ccPointCloud* cloud = 0;
423  ccPolyline* polyline = 0;
424  bool tempPolyline = false;
425  {
426  for (size_t i = 0; i < selectCount; ++i) {
427  if (selectedEntities[i]->isA(CV_TYPES::POINT_CLOUD)) {
428  cloud = static_cast<ccPointCloud*>(selectedEntities[i]);
429  } else if (selectedEntities[i]->isA(CV_TYPES::POLY_LINE)) {
430  polyline = static_cast<ccPolyline*>(selectedEntities[i]);
431  } else if (!polyline &&
432  selectedEntities[i]->isKindOf(CV_TYPES::CONE)) {
433  // special case: we can deduce the polyline from the
434  // cone/cylinder parameters
435  ccCone* cone = static_cast<ccCone*>(selectedEntities[i]);
436  polyline = GetConeProfile(cone);
437  if (!polyline) {
438  // the conversion failed?!
439  return;
440  }
441  tempPolyline = true;
442  }
443  }
444  }
445 
446  if (cloud && polyline) {
447  doProjectCloudDistsInGrid(cloud, polyline);
448  }
449 
450  if (polyline && tempPolyline) {
451  delete polyline;
452  polyline = 0;
453  }
454 }
455 
457  ccPolyline* polyline) const {
458  assert(cloud && m_app);
459  if (!cloud) return;
460 
461  // get the scalar field to map
462  ccScalarField* sf = 0;
463  {
465  if (sfIdx < 0) {
466  sf = cloud->getCurrentDisplayedScalarField();
467  if (sf) {
468  if (QMessageBox::question(
469  m_app ? m_app->getMainWindow() : nullptr,
470  tr("Distance field"),
471  tr("Cloud has no '%1' field. Do you want to use "
472  "the active scalar field instead?")
473  .arg(RADIAL_DIST_SF_NAME),
474  QMessageBox::Yes,
475  QMessageBox::No) == QMessageBox::No) {
476  // we can stop already
477  return;
478  }
479  } else {
480  CVLog::Error(tr("Cloud has no no '%1' field and no active "
481  "scalar field!")
482  .arg(RADIAL_DIST_SF_NAME));
483 
484  // additional indications
487  tr("You can compute the radial distances with the "
488  "'%1' method")
489  .arg(m_doCompareCloudToProfile->text()));
490  }
491  return;
492  }
493  } else {
494  sf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdx));
495  }
496  }
497  assert(sf);
498 
499  DistanceMapGenerationDlg dmgDlg(cloud, sf, polyline, m_app);
500 
501  dmgDlg.exec();
502 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
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
std::string filename
int height
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Dialog for generating a distance map (surface of revolution)
static void SetPoylineAxis(ccPolyline *polyline, const CCVector3 &axis)
Sets the revolution axis of a given polyline.
static void SetPoylineOrigin(ccPolyline *polyline, const CCVector3 &origin)
Sets the origin of a given polyline/profile.
static bool ComputeRadialDist(ccPointCloud *cloud, ccPolyline *profile, bool storeRadiiAsSF=false, ecvMainAppInterface *app=0)
Computes radial distance between cloud and a profile.
static void SetPoylineRevolDim(ccPolyline *polyline, int revolDim)
Sets the revolution dimension of a given polyline.
static void SetPolylineHeightShift(ccPolyline *polyline, PointCoordinateType heightShift)
Sets the profile 'height shift' (i.e. along the revolution axis)
Dialog for importing a 2D revolution profile (qSRA plugin)
QString getFilename() const
Returns input filename (on completion)
int getAxisDimension() const
bool absoluteHeightValues() const
void setDefaultFilename(QString filename)
Sets default filename.
static ccPolyline * Load(QString filename, CCVector3 &origin, ecvMainAppInterface *app=0)
Loads a 2D profile from a file.
Type u[3]
Definition: CVGeom.h:139
Cone (primitive)
Definition: ecvCone.h:16
PointCoordinateType getTopRadius() const
Returns top radius.
Definition: ecvCone.h:70
PointCoordinateType getHeight() const
Returns height.
Definition: ecvCone.h:52
PointCoordinateType getBottomRadius() const
Returns bottom radius.
Definition: ecvCone.h:60
virtual void setVisible(bool state)
Sets entity visibility.
virtual void setRedraw(bool state)
Sets entity redraw mode.
virtual void showColors(bool state)
Sets colors visibility.
virtual void setGLTransformation(const ccGLMatrix &trans)
Associates entity with a GL transformation (rotation + translation)
Vector3Tpl< T > getTranslationAsVec3D() const
Returns a copy of the translation as a CCVector3.
static ccGLMatrixTpl< float > FromToRotation(const Vector3Tpl< float > &from, const Vector3Tpl< float > &to)
Creates a transformation matrix that rotates a vector to another.
T * data()
Returns a pointer to internal data.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
Vector3Tpl< T > getColumnAsVec3D(unsigned index) const
Returns a copy of a given column as a CCVector3.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
virtual ccGLMatrix & getTransformation()
Returns the transformation that is currently applied to the vertices.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
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
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
Colored polyline.
Definition: ecvPolyline.h:24
void set2DMode(bool state)
Defines if the polyline is considered as 2D or 3D.
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
void setWidth(PointCoordinateType width)
Sets the width of the line.
A scalar field associated to display-related parameters.
Standard ECV plugin interface.
ecvMainAppInterface * m_app
Main application interface.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
void setClosed(bool state)
Sets whether the polyline is closed or not.
Definition: Polyline.h:29
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
static void SetRedrawRecursive(bool redraw=false)
Main application interface (for plugins)
virtual void updateUI()=0
virtual ccHObject * dbRootObject()=0
Returns DB root (as a ccHObject)
virtual QMainWindow * getMainWindow()=0
Returns main window.
virtual void refreshAll(bool only2D=false, bool forceRedraw=true)=0
Redraws all GL windows that have the 'refresh' flag on.
virtual const ccHObject::Container & getSelectedEntities() const =0
Returns currently selected entities ("read only")
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
virtual void onNewSelection(const ccHObject::Container &selectedEntities) override
Definition: qSRA.cpp:92
bool doComputeRadialDists(ccPointCloud *cloud, ccPolyline *polyline) const
Computes cloud-to-profile radial distances.
Definition: qSRA.cpp:382
QAction * m_doCompareCloudToProfile
Associated action.
Definition: qSRA.h:55
QAction * m_doLoadProfile
Associated action.
Definition: qSRA.h:53
void loadProfile() const
Loads profile from a dedicated file.
Definition: qSRA.cpp:146
qSRA(QObject *parent=nullptr)
Default constructor.
Definition: qSRA.cpp:40
QAction * m_doProjectCloudDists
Associated action.
Definition: qSRA.h:57
void doProjectCloudDistsInGrid(ccPointCloud *cloud, ccPolyline *polyline) const
Definition: qSRA.cpp:456
virtual QList< QAction * > getActions() override
Get a list of actions for this plugin.
Definition: qSRA.cpp:47
void projectCloudDistsInGrid() const
Projects the cloud distances into a 2D grid.
Definition: qSRA.cpp:408
void computeCloud2ProfileRadialDist() const
Computes cloud-to-profile radial distances.
Definition: qSRA.cpp:304
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
const char RADIAL_DIST_SF_NAME[]
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
@ CONE
Definition: CVTypes.h:123
@ POINT_CLOUD
Definition: CVTypes.h:104
@ POLY_LINE
Definition: CVTypes.h:112
static const std::string path
Definition: PointCloud.cpp:59
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
constexpr Rgb green(0, MAX, 0)
QString defaultDocPath()
Shortcut for getting the documents location path.
Definition: ecvFileUtils.h:30
const QString QSRA_DEFAULT_CONTAINER_NAME("Profile(s)")
ccHObject * GetDefaultContainer(ecvMainAppInterface *app)
Definition: qSRA.cpp:123
static ccPolyline * GetConeProfile(ccCone *cone)
Definition: qSRA.cpp:240