ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qPoissonRecon.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 "qPoissonRecon.h"
9 
10 // dialog
11 #include "ui_poissonReconParamDlg.h"
12 
13 // Qt
14 #include <QDialog>
15 #include <QInputDialog>
16 #include <QMainWindow>
17 #include <QtConcurrentRun>
18 #include <QtCore>
19 #include <QtGui>
20 
21 // PoissonRecon
22 #include <PoissonReconLib.h>
23 
24 // qCC_db
25 #include <ecvMesh.h>
26 #include <ecvPointCloud.h>
27 #include <ecvProgressDialog.h>
28 #include <ecvScalarField.h>
29 
30 // System
31 #if defined(CV_WINDOWS)
32 #include "Windows.h"
33 #else
34 #include <time.h>
35 #include <unistd.h>
36 #endif
37 
38 template <typename Real>
40 public:
41  explicit PointCloudWrapper(const ccPointCloud& cloud) : m_cloud(cloud) {}
42 
43  virtual size_t size() const { return m_cloud.size(); }
44  virtual bool hasNormals() const { return m_cloud.hasNormals(); }
45  virtual bool hasColors() const { return m_cloud.hasColors(); }
46  virtual void getPoint(size_t index, Real* coords) const {
47  if (index >= m_cloud.size()) {
48  assert(false);
49  return;
50  }
51  // point
52  const CCVector3* P = m_cloud.getPoint(static_cast<unsigned>(index));
53  coords[0] = static_cast<Real>(P->x);
54  coords[1] = static_cast<Real>(P->y);
55  coords[2] = static_cast<Real>(P->z);
56  }
57 
58  virtual void getNormal(size_t index, Real* coords) const {
59  if (index >= m_cloud.size() || !m_cloud.hasNormals()) {
60  assert(false);
61  return;
62  }
63 
64  const CCVector3& N =
65  m_cloud.getPointNormal(static_cast<unsigned>(index));
66  coords[0] = static_cast<Real>(N.x);
67  coords[1] = static_cast<Real>(N.y);
68  coords[2] = static_cast<Real>(N.z);
69  }
70 
71  virtual void getColor(size_t index, Real* rgb) const {
72  if (index >= m_cloud.size() || !m_cloud.hasColors()) {
73  assert(false);
74  return;
75  }
76 
77  const ecvColor::Rgb& color =
78  m_cloud.getPointColor(static_cast<unsigned>(index));
79  rgb[0] = static_cast<Real>(color.r);
80  rgb[1] = static_cast<Real>(color.g);
81  rgb[2] = static_cast<Real>(color.b);
82  }
83 
84 protected:
86 };
87 
88 template <typename Real>
89 class MeshWrapper : public PoissonReconLib::IMesh<Real> {
90 public:
91  explicit MeshWrapper(ccMesh& mesh,
92  ccPointCloud& vertices,
93  cloudViewer::ScalarField* densitySF = nullptr)
94  : m_mesh(mesh),
95  m_vertices(vertices),
96  m_densitySF(densitySF),
97  m_error(false) {}
98 
100  if (m_error) {
101  // no need to go further
102  return false;
103  }
104  if (m_mesh.size() == m_mesh.capacity() &&
105  !m_mesh.reserve(m_mesh.size() + 1024)) {
106  m_error = true;
107  return false;
108  }
109  return true;
110  }
111 
113  if (m_error) {
114  // no need to go further
115  return false;
116  }
117  if (m_vertices.size() == m_vertices.capacity() &&
118  !m_vertices.reserve(m_vertices.size() + 4096)) {
119  m_error = true;
120  return false;
121  }
122  return true;
123  }
124 
125  virtual void addVertex(const Real* coords) override {
126  if (!checkVertexCapacity()) {
127  return;
128  }
129  CCVector3 P = CCVector3::fromArray(coords);
130  m_vertices.addPoint(P);
131  }
132 
133  virtual void addNormal(const Real* coords) override {
134  if (!checkVertexCapacity()) {
135  return;
136  }
138  m_error = true;
139  return;
140  }
141  CCVector3 N = CCVector3::fromArray(coords);
142  m_vertices.addNorm(N);
143  }
144 
145  virtual void addColor(const Real* rgb) override {
146  if (!checkVertexCapacity()) {
147  return;
148  }
149  if (!m_vertices.hasColors()) {
151  m_error = true;
152  return;
153  }
154  }
156  (Real)255, std::max((Real)0, rgb[0]))),
157  static_cast<ColorCompType>(std::min(
158  (Real)255, std::max((Real)0, rgb[1]))),
159  static_cast<ColorCompType>(std::min(
160  (Real)255, std::max((Real)0, rgb[2]))));
161  }
162 
163  virtual void addDensity(double d) override {
164  if (!m_densitySF) {
165  return;
166  }
167  if (m_densitySF->size() == m_densitySF->capacity() &&
168  !m_densitySF->reserveSafe(m_densitySF->size() + 4096)) {
169  m_error = true;
170  return;
171  }
172  m_densitySF->addElement(static_cast<ScalarType>(d));
173  }
174 
175  void addTriangle(size_t i1, size_t i2, size_t i3) override {
176  if (!checkMeshCapacity()) {
177  return;
178  }
179  m_mesh.addTriangle(static_cast<unsigned>(i1), static_cast<unsigned>(i2),
180  static_cast<unsigned>(i3));
181  }
182 
183  bool isInErrorState() const { return m_error; }
184 
185 protected:
188  bool m_error;
190 };
191 
192 // dialog for qPoissonRecon plugin
193 class PoissonReconParamDlg : public QDialog,
194  public Ui::PoissonReconParamDialog {
195 public:
196  explicit PoissonReconParamDlg(QWidget* parent = nullptr)
197  : QDialog(parent, Qt::Tool), Ui::PoissonReconParamDialog() {
198  setupUi(this);
199  }
200 };
201 
202 qPoissonRecon::qPoissonRecon(QObject* parent /*=0*/)
203  : QObject(parent),
204  ccStdPluginInterface(":/CC/plugin/qPoissonRecon/info.json"),
205  m_action(nullptr) {}
206 
207 void qPoissonRecon::onNewSelection(
208  const ccHObject::Container& selectedEntities) {
209  if (m_action)
210  m_action->setEnabled(selectedEntities.size() == 1 &&
211  selectedEntities[0]->isA(CV_TYPES::POINT_CLOUD));
212 }
213 
214 QList<QAction*> qPoissonRecon::getActions() {
215  // default action
216  if (!m_action) {
217  m_action = new QAction(getName(), this);
218  m_action->setToolTip(getDescription());
219  m_action->setIcon(getIcon());
220  // connect signal
221  connect(m_action, &QAction::triggered, this, &qPoissonRecon::doAction);
222  }
223 
224  return QList<QAction*>{m_action};
225 }
226 
228 static ccPointCloud* s_cloud = nullptr;
229 static ccMesh* s_mesh = nullptr;
230 static ccPointCloud* s_meshVertices = nullptr;
232 
234  // invalid parameters
235  if (!s_cloud || !s_mesh || !s_meshVertices) {
236  return false;
237  }
238 
240  s_densitySF);
242 
243  if (!PoissonReconLib::Reconstruct(s_params, cloudWrapper, meshWrapper) ||
244  meshWrapper.isInErrorState()) {
245  return false;
246  }
247 
248  return true;
249 }
250 
252  assert(m_app);
253  if (!m_app) {
254  return;
255  }
256 
257  // we need one point cloud
258  if (!m_app->haveOneSelection()) {
259  m_app->dispToConsole("Select only one cloud!",
261  return;
262  }
263 
264  // a real point cloud
265  const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
266 
267  ccHObject* ent = selectedEntities[0];
268  if (!ent->isA(CV_TYPES::POINT_CLOUD)) {
269  m_app->dispToConsole("Select a cloud!",
271  return;
272  }
273 
274  // with normals!
275  ccPointCloud* pc = static_cast<ccPointCloud*>(ent);
276  if (!pc->hasNormals()) {
277  m_app->dispToConsole("Cloud must have normals!",
279  return;
280  }
281 
282  static unsigned s_lastEntityID = 0;
283  static double s_defaultResolution = 0.0;
284  static bool s_depthMode = true;
285  if (s_defaultResolution == 0.0 || s_lastEntityID != pc->getUniqueID()) {
286  s_defaultResolution = pc->getOwnBB().getDiagNormd() / 200.0;
287  s_lastEntityID = pc->getUniqueID();
288  }
289 
290  bool cloudHasColors = pc->hasColors();
292  prpDlg.importColorsCheckBox->setVisible(cloudHasColors);
293  if (s_depthMode)
294  prpDlg.depthRadioButton->setChecked(true);
295  else
296  prpDlg.resolutionRadioButton->setChecked(true);
297 
298  // init dialog with semi-persistent settings
299  prpDlg.depthSpinBox->setValue(s_params.depth);
300  prpDlg.resolutionDoubleSpinBox->setValue(s_defaultResolution);
301  prpDlg.samplesPerNodeSpinBox->setValue(s_params.samplesPerNode);
302  prpDlg.importColorsCheckBox->setChecked(s_params.withColors);
303  prpDlg.densityCheckBox->setChecked(s_params.density);
304  prpDlg.weightDoubleSpinBox->setValue(s_params.pointWeight);
305  prpDlg.linearFitCheckBox->setChecked(s_params.linearFit);
306  switch (s_params.boundary) {
308  prpDlg.boundaryComboBox->setCurrentIndex(0);
309  break;
311  prpDlg.boundaryComboBox->setCurrentIndex(1);
312  break;
314  prpDlg.boundaryComboBox->setCurrentIndex(2);
315  break;
316  default:
317  assert(false);
318  break;
319  }
320 
321  if (!prpDlg.exec()) return;
322 
323  // set parameters with dialog settings
324  s_depthMode = prpDlg.depthRadioButton->isChecked();
325  s_defaultResolution = prpDlg.resolutionDoubleSpinBox->value();
326 
327  s_params.depth = (s_depthMode ? prpDlg.depthSpinBox->value() : 0);
329  static_cast<float>(s_depthMode ? 0.0 : s_defaultResolution);
331  static_cast<float>(prpDlg.samplesPerNodeSpinBox->value());
332  s_params.withColors = prpDlg.importColorsCheckBox->isChecked();
333  s_params.density = prpDlg.densityCheckBox->isChecked();
335  static_cast<float>(prpDlg.weightDoubleSpinBox->value());
336  s_params.linearFit = prpDlg.linearFitCheckBox->isChecked();
337  switch (prpDlg.boundaryComboBox->currentIndex()) {
338  case 0:
340  break;
341  case 1:
343  break;
344  case 2:
346  break;
347  default:
348  assert(false);
349  break;
350  }
351 
352  /*** RECONSTRUCTION PROCESS ***/
353 
354  assert(s_cloud == nullptr);
355  assert(s_mesh == nullptr);
356  assert(s_meshVertices == nullptr);
357 
358  ccScalarField* densitySF = nullptr;
359  ccPointCloud* newPC = new ccPointCloud("vertices");
360  ccMesh* newMesh = new ccMesh(newPC);
361  newMesh->addChild(newPC);
362 
363  // run in a separate thread
364  bool result = false;
365  {
366  // start message
367  m_app->dispToConsole(QString("[PoissonRecon] Job started (level %1)")
368  .arg(s_params.depth),
370 
371  // progress dialog (Qtconcurrent::run can't be canceled!)
372  QProgressDialog pDlg("Initialization", QString(), 0, 0,
373  m_app->getMainWindow());
374  pDlg.setWindowTitle("Poisson Reconstruction");
375  pDlg.show();
376  // QApplication::processEvents();
377 
378  QString progressLabel("Reconstruction in progress\n");
379  if (s_depthMode)
380  progressLabel += QString("level: %1").arg(s_params.depth);
381  else
382  progressLabel +=
383  QString("resolution: %1").arg(s_params.finestCellWidth);
384  progressLabel += QString(" [%1 thread(s)]").arg(s_params.threads);
385 
386  pDlg.setLabelText(progressLabel);
387  QApplication::processEvents();
388 
389  // run in a separate thread
390  s_cloud = pc;
391  s_mesh = newMesh;
392  s_meshVertices = newPC;
393 
394  if (s_params.density) {
395  s_densitySF = (densitySF = new ccScalarField("Density"));
396  }
397 
398  QFuture<bool> future = QtConcurrent::run(doReconstruct);
399 
400  // wait until process is finished!
401  while (!future.isFinished()) {
402 #if defined(CV_WINDOWS)
403  ::Sleep(500);
404 #else
405  usleep(500 * 1000);
406 #endif
407 
408  pDlg.setValue(pDlg.value() + 1);
409  QApplication::processEvents();
410  }
411 
412  result = future.result();
413 
414  s_cloud = nullptr;
415  s_mesh = nullptr;
416  s_meshVertices = nullptr;
417 
418  pDlg.hide();
419  QApplication::processEvents();
420  }
421 
422  if (!result) {
423  if (densitySF) {
424  densitySF->release();
425  densitySF = nullptr;
426  }
427  delete newMesh;
428  newMesh = nullptr;
429  m_app->dispToConsole("Reconstruction failed!",
431  return;
432  }
433 
434  // success message
436  QString("[PoissonRecon] Job finished (%1 triangles, %2 vertices)")
437  .arg(newMesh->size())
438  .arg(newPC->size()),
440 
441  newMesh->setName(QString("Mesh[%1] (level %2)")
442  .arg(pc->getName())
443  .arg(s_params.depth));
444  newPC->setEnabled(false);
445  newMesh->setVisible(true);
446  newMesh->computeNormals(true);
447  if (!cloudHasColors) {
448  newPC->unallocateColors();
449  newPC->showColors(false);
450  }
451  newMesh->showColors(newPC->hasColors());
452 
453  if (densitySF) {
454  densitySF->computeMinAndMax();
455  densitySF->showNaNValuesInGrey(false);
456  int sfIdx = newPC->addScalarField(densitySF);
457  newPC->setCurrentDisplayedScalarField(sfIdx);
458  newPC->showSF(true);
459  newMesh->showColors(newPC->colorsShown());
460  newMesh->showSF(true);
461  }
462 
463  // copy Global Shift & Scale information
464  newPC->setGlobalShift(pc->getGlobalShift());
465  newPC->setGlobalScale(pc->getGlobalScale());
466 
467  // output mesh
468  m_app->addToDB(newMesh);
469  m_app->setSelectedInDB(ent, false);
470  m_app->setSelectedInDB(newMesh, true);
471 
472  // currently selected entities parameters may have changed!
473  m_app->updateUI();
474  // currently selected entities appearance may have changed!
475  m_app->refreshAll();
476 }
math::float4 color
core::Tensor result
Definition: VtkUtils.cpp:76
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
ccPointCloud & m_vertices
virtual void addVertex(const Real *coords) override
void addTriangle(size_t i1, size_t i2, size_t i3) override
virtual void addNormal(const Real *coords) override
bool checkVertexCapacity()
cloudViewer::ScalarField * m_densitySF
bool checkMeshCapacity()
bool isInErrorState() const
ccMesh & m_mesh
MeshWrapper(ccMesh &mesh, ccPointCloud &vertices, cloudViewer::ScalarField *densitySF=nullptr)
virtual void addColor(const Real *rgb) override
virtual void addDensity(double d) override
virtual void getColor(size_t index, Real *rgb) const
virtual void getPoint(size_t index, Real *coords) const
virtual void getNormal(size_t index, Real *coords) const
PointCloudWrapper(const ccPointCloud &cloud)
virtual size_t size() const
const ccPointCloud & m_cloud
virtual bool hasNormals() const
virtual bool hasColors() const
Input cloud interface.
Output mesh interface.
static bool Reconstruct(const Parameters &params, const PoissonReconLib::ICloud< float > &inCloud, PoissonReconLib::IMesh< float > &ouMesh)
Reconstruct a mesh from a point cloud (float version)
PoissonReconParamDlg(QWidget *parent=nullptr)
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
virtual QString getName() const override
Returns (short) name (for menu entry, etc.)
virtual QString getDescription() const override
Returns long name/description (for tooltip, etc.)
virtual QIcon getIcon() const override
Returns icon.
virtual bool colorsShown() const
Returns whether colors are shown or not.
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
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
Triangular mesh.
Definition: ecvMesh.h:35
bool reserve(std::size_t n)
Reserves the memory to store the vertex indexes (3 per triangle)
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
virtual unsigned size() const override
Returns the number of triangles.
unsigned capacity() const override
Returns max capacity.
bool computeNormals(bool perVertex)
Computes normals.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual unsigned getUniqueID() const
Returns object unique ID.
Definition: ecvObject.h:86
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
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
void unallocateColors()
Erases the cloud colors.
void addNorm(const CCVector3 &N)
Pushes a normal vector on stack (shortcut)
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 reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheNormsTable()
Reserves memory to store the compressed normals.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
bool hasColors() const override
Returns whether colors are enabled or not.
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color corresponding to a given point.
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
A scalar field associated to display-related parameters.
void showNaNValuesInGrey(bool state)
void computeMinAndMax() override
Determines the min and max values.
virtual void setGlobalScale(double scale)
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
Standard ECV plugin interface.
ecvMainAppInterface * m_app
Main application interface.
double getDiagNormd() const
Returns diagonal length (double precision)
Definition: BoundingBox.h:175
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
unsigned capacity() const
Returns cloud capacity (i.e. reserved size)
const CCVector3 * getPoint(unsigned index) const override
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
void addElement(ScalarType value)
Definition: ScalarField.h:99
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
RGB color structure.
Definition: ecvColorTypes.h:49
virtual void updateUI()=0
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")
bool haveOneSelection() const
Checks if we have exactly one selection.
virtual void setSelectedInDB(ccHObject *obj, bool selected)=0
Selects or unselects an entity (in db tree)
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
QAction * m_action
Associated action.
Definition: qPoissonRecon.h:41
void doAction()
Slot called when associated ation is triggered.
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
static bool s_depthMode
static double s_defaultResolution
@ POINT_CLOUD
Definition: CVTypes.h:104
void Sleep(int milliseconds)
Definition: Helper.cpp:278
static PoissonReconLib::Parameters s_params
static cloudViewer::ScalarField * s_densitySF
static ccPointCloud * s_meshVertices
static ccPointCloud * s_cloud
bool doReconstruct()
static ccMesh * s_mesh
Algorithm parameters.
BoundaryType boundary
Boundary type for the finite elements.