ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
G3PointAction.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 "G3PointAction.h"
9 
10 // ECV_PLUGINS
11 #include <ecvMainAppInterface.h>
12 #include <ecvQtHelpers.h>
13 
14 // CV_CORE_LIB
15 #include <CVGeom.h>
16 #include <DgmOctree.h>
18 #include <Neighbourhood.h>
19 
20 // CV_DB_LIB
21 #include <ecvDisplayTools.h>
22 #include <ecvHObject.h>
23 #include <ecvNormalVectors.h>
24 #include <ecvPointCloud.h>
25 #include <ecvProgressDialog.h>
26 
27 // Qt
28 #include <QApplication>
29 #include <QCoreApplication>
30 #include <QElapsedTimer>
31 #include <QMainWindow>
32 #include <QMutex>
33 #include <QOpenGLShaderProgram>
34 #include <QPushButton>
35 #include <QThreadPool>
36 #include <QtConcurrent>
37 #include <algorithm>
38 #include <fstream>
39 #include <iostream>
40 #include <random>
41 
42 // Eigen
43 #include <G3PointDialog.h>
44 #include <G3PointDisclaimer.h>
45 #include <WolmanCustomPlot.h>
46 #include <math.h>
47 
48 #include <Eigen/Geometry>
49 #include <set>
50 
51 namespace G3Point {
52 std::shared_ptr<G3PointAction> G3PointAction::s_g3PointAction;
53 
54 QPointer<G3PointPlots> G3PointAction::s_g3PointPlots;
55 
57  : m_app(app), m_dlg(nullptr), m_grainsAsEllipsoids(nullptr) {
58  assert(cloud);
59  setCloud(cloud);
60 }
61 
63  if (s_g3PointAction) {
64  if (m_dlg) {
65  delete m_dlg;
66  }
67  }
68 }
69 
71  ecvMainAppInterface* app) {
72  // disclaimer accepted?
73  if (!G3PointDisclaimer::show(app)) return;
74 
75  s_g3PointPlots = new G3PointPlots(cloud->getName());
76 
77  if (!s_g3PointAction) // create the singleton if needed
78  {
79  s_g3PointAction.reset(new G3PointAction(cloud, app));
80  } else {
81  int sfIdx = cloud->getScalarFieldIndexByName("g3point_label");
82  if (sfIdx == -1) {
84  "[G3PointAction::GetG3PointAction] no g3point_label scalar "
85  "field, reset action");
86  s_g3PointAction.reset(new G3PointAction(cloud, app));
87  } else {
89  "[G3PointAction::GetG3PointAction] use existing "
90  "g3point_label scalar field");
91  s_g3PointAction->setCloud(cloud);
92  }
93  }
94  s_g3PointAction->showDlg();
95 }
96 
97 RGBAColorsTableType getRandomColors(size_t randomColorsNumber) {
98  Q_ASSERT(randomColorsNumber > 1);
99 
100  RGBAColorsTableType randomColors;
101  if (!randomColors.reserveSafe(static_cast<unsigned>(randomColorsNumber))) {
102  CVLog::Error(QObject::tr("Not enough memory!"));
103  }
104 
105  // generate random colors
106  int max = 255;
107  std::mt19937 gen(42); // to seed mersenne twister.
108  std::uniform_int_distribution<uint16_t> dist(
109  0, max); // 1-byte types are not allowed
110 
111  for (int i = 0; i < randomColorsNumber; ++i) {
112  ecvColor::Rgb col;
113  col.r = static_cast<unsigned char>(dist(gen));
114  col.g = static_cast<unsigned char>(dist(gen));
115  // col.b = static_cast<unsigned char>(dist(gen));
116  col.b = max - static_cast<ColorCompType>(
117  (static_cast<double>(col.r) +
118  static_cast<double>(col.g)) /
119  2); // cast to double to avoid overflow (whatever
120  // the type of ColorCompType!!!)
121  randomColors.addElement(ecvColor::Rgba(col, max));
122  }
123 
124  return randomColors;
125 }
126 
127 bool G3PointAction::sfConvertToRandomRGB(
128  const ccHObject::Container& selectedEntities,
129  QWidget* parent) // take as is from ccEntityAction.cpp
130 {
131  int s_randomColorsNumber = 256;
132 
133  Q_ASSERT(s_randomColorsNumber > 1);
134 
135  RGBAColorsTableType* randomColors = new RGBAColorsTableType;
136  if (!randomColors->reserveSafe(
137  static_cast<unsigned>(s_randomColorsNumber))) {
138  CVLog::Error(QObject::tr("Not enough memory!"));
139  return false;
140  }
141 
142  // generate random colors
143  int max = 255;
144 
145  std::mt19937 gen(42); // to seed mersenne twister.
146  std::uniform_int_distribution<uint16_t> dist(
147  0, max); // 1-byte types are not allowed
148 
149  for (int i = 0; i < s_randomColorsNumber; ++i) {
150  ecvColor::Rgb col;
151  col.r = static_cast<unsigned char>(dist(gen));
152  col.g = static_cast<unsigned char>(dist(gen));
153  col.b = static_cast<unsigned char>(dist(gen));
154  // col.b = max -
155  // static_cast<ColorCompType>((static_cast<double>(col.r) +
156  // static_cast<double>(col.g)) / 2); //cast to double to avoid overflow
157  //(whatever the type of ColorCompType!!!)
158  randomColors->addElement(ecvColor::Rgba(col, max));
159  }
160 
161  // apply random colors
162  for (ccHObject* ent : selectedEntities) {
163  ccGenericPointCloud* cloud = nullptr;
164 
165  bool lockedVertices = false;
166  cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
167  if (lockedVertices) {
169  "[G3Point::sfConvertToRandomRGB] "
170  "DisplayLockedVerticesWarning");
171  continue;
172  }
173  if (cloud != nullptr) // TODO
174  {
175  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
177  // if there is no displayed SF --> nothing to do!
178  if (sf && sf->currentSize() >= pc->size()) {
179  if (!pc->resizeTheRGBTable(false)) {
180  CVLog::Error(QObject::tr("Not enough memory!"));
181  break;
182  } else {
183  ScalarType minSF = sf->getMin();
184  ScalarType maxSF = sf->getMax();
185 
186  ScalarType step =
187  (maxSF - minSF) / (s_randomColorsNumber - 1);
188  if (step == 0) step = static_cast<ScalarType>(1.0);
189 
190  for (unsigned i = 0; i < pc->size(); ++i) {
191  ScalarType val = sf->getValue(i);
192  unsigned colIndex =
193  static_cast<unsigned>((val - minSF) / step);
194  if (colIndex == s_randomColorsNumber) --colIndex;
195 
196  pc->setPointColor(i, randomColors->getValue(colIndex));
197  }
198 
199  pc->showColors(true);
200  pc->showSF(false); // just in case
201  }
202  }
203 
204  m_cloud->redrawDisplay();
205  }
206  }
207 
208  return true;
209 }
210 
211 void G3PointAction::addToStack(int index,
212  const Eigen::ArrayXi& n_donors,
213  const Eigen::ArrayXXi& donors,
214  std::vector<int>& stack) {
215  stack.push_back(index);
216 
217  for (int k = 0; k < n_donors(index); k++) {
218  addToStack(donors(index, k), n_donors, donors, stack);
219  }
220 }
221 
222 int G3PointAction::segmentLabels(bool useParallelStrategy) {
223  CVLog::Print("[segment_labels]");
224  // for each point, find in the neighborhood the point with the minimum slope
225  // (the receiver)
226  Eigen::ArrayXd min_slopes(m_neighborsSlopes.rowwise().minCoeff());
227  Eigen::ArrayXi index_of_min_slope = Eigen::ArrayXi::Zero(m_cloud->size());
228  Eigen::ArrayXi receivers(m_cloud->size());
229 
230  for (unsigned index = 0; index < m_cloud->size(); index++) {
231  double min_slope = min_slopes(index);
232  int count = 0;
233  for (int k = 0; k < m_kNN; k++) {
234  if (m_neighborsSlopes(index, k) == min_slope) {
235  index_of_min_slope(index) = k;
236  count++;
237  if (count > 1)
238  std::cout << "[segment_labels] slope already seen, index "
239  << index << ", k " << k << std::endl;
240  }
241  }
242  receivers(index) = m_neighborsIndexes(index, index_of_min_slope(index));
243  }
244 
245  // if the minimum slope is positive, the receiver is a local maximum
246  int nb_maxima = (min_slopes > 0).count();
247  Eigen::ArrayXi localMaximumIndexes = Eigen::ArrayXi::Zero(nb_maxima);
248  int l = 0;
249  for (unsigned int k = 0; k < m_cloud->size(); k++) {
250  if (min_slopes(k) > 0) {
251  localMaximumIndexes(l) = k;
252  receivers(k) = k;
253  l++;
254  }
255  }
256 
257  // identify the donors for each receiver
258  CVLog::Print("[segment_labels] identify the donors for each receiver");
259  Eigen::ArrayXi nDonors = Eigen::ArrayXi::Zero(m_cloud->size());
260  Eigen::ArrayXXi donors = Eigen::ArrayXXi::Zero(m_cloud->size(), m_kNN);
261 
262  CVLog::Print("[segment_labels] loop");
263  for (unsigned int k = 0; k < m_cloud->size(); k++) {
264  int receiver = receivers(k);
265  if (receiver != k) // this receiver is not a local maximum
266  {
267  if (nDonors(receiver) < m_kNN - 1) {
268  nDonors(receiver) = nDonors(receiver) + 1;
269  donors(receiver, nDonors(receiver) - 1) = k;
270  } else {
271  CVLog::Warning(QString("maximum number of donors reached, k "
272  "%1, receiver(k) %2, nDonors %3")
273  .arg(k)
274  .arg(receiver)
275  .arg(nDonors(receiver)));
276  }
277  }
278  }
279 
280  // build the stacks
281  CVLog::Print("[segment_labels] build the stacks");
282  Eigen::ArrayXi labels = Eigen::ArrayXi::Zero(m_cloud->size());
283  Eigen::ArrayXi labelsnpoint = Eigen::ArrayXi::Zero(m_cloud->size());
284  std::vector<std::vector<int>> stacks;
285 
286  int sfIdx = m_cloud->getScalarFieldIndexByName("g3point_label");
287  if (sfIdx == -1) {
288  sfIdx = m_cloud->addScalarField("g3point_label");
289  if (sfIdx == -1) {
290  CVLog::Error(
291  "[G3Point::segment_labels] impossible to create scalar "
292  "field g3point_label");
293  }
294  }
295 
296  cloudViewer::ScalarField* g3point_label = m_cloud->getScalarField(sfIdx);
297  RGBAColorsTableType randomColors =
298  getRandomColors(localMaximumIndexes.size());
299 
300  if (!m_cloud->resizeTheRGBTable(false)) {
301  CVLog::Error(QObject::tr("Not enough memory!"));
302  return -1;
303  }
304 
305  for (int k = 0; k < localMaximumIndexes.size(); k++) {
306  int localMaximumIndex = localMaximumIndexes(k);
307  std::vector<int> stack;
308  addToStack(localMaximumIndex, nDonors, donors, stack);
309  // labels
310  for (auto i : stack) {
311  labels(i) = k;
312  labelsnpoint(i) = static_cast<int>(stack.size());
313  if (g3point_label) {
314  g3point_label->setValue(i, k);
315  m_cloud->setPointColor(i, randomColors.getValue(k));
316  }
317  }
318  stacks.push_back(stack);
319  }
320 
321  if (g3point_label) {
322  g3point_label->computeMinAndMax();
323  }
324 
325  m_cloud->setCurrentDisplayedScalarField(sfIdx);
326  m_cloud->showColors(true);
327  m_cloud->showSF(false);
328 
329  if (m_app) {
330  m_app->refreshAll();
331  m_app->updateUI();
332  }
333 
334  int nLabels = localMaximumIndexes.size();
335 
336  return nLabels;
337 }
338 
339 double G3PointAction::angleRot2VecMat(const Eigen::Vector3d& a,
340  const Eigen::Vector3d& b) {
341  double angle;
342  Eigen::Vector3d c;
343  double d;
344 
345  c = a.cross(b);
346 
347  d = a.dot(b);
348 
349  angle = atan2(c.norm(), d) * 180 / M_PI;
350 
351  return angle;
352 }
353 
354 Eigen::ArrayXXd G3PointAction::computeMeanAngleBetweenNormalsAtBorders() {
355  // Find the indexborder nodes (no donor and many other labels in the
356  // neighbourhood)
357  Eigen::ArrayXXi duplicated_labels(m_cloud->size(), m_kNN);
358  for (int n = 0; n < m_kNN; n++) {
359  duplicated_labels(Eigen::all, n) = m_labels;
360  }
361  Eigen::ArrayXXi labels_of_neighbors(m_cloud->size(), m_kNN);
362  for (int index = 0; index < static_cast<int>(m_cloud->size()); index++) {
363  for (int n = 0; n < m_kNN; n++) {
364  labels_of_neighbors(index, n) =
365  m_labels(m_neighborsIndexes(index, n));
366  }
367  }
368 
369  Eigen::ArrayXi temp = m_kNN - (labels_of_neighbors == duplicated_labels)
370  .cast<int>()
371  .rowwise()
372  .sum();
373  auto condition = ((temp >= m_kNN / 4) && (m_ndon == 0));
374  Eigen::ArrayXi indborder(condition.count());
375 
376  int l = 0;
377  for (int c = 0; c < condition.size(); c++) {
378  if (condition(c)) {
379  indborder(l) = c;
380  l++;
381  }
382  }
383 
384  // Compute the angle of the normal vector between the neighbours of each
385  // grain / label
386  size_t nlabels = m_stacks.size();
387  Eigen::ArrayXXd A = Eigen::ArrayXXd::Zero(nlabels, nlabels);
388  Eigen::ArrayXXd Aangle(nlabels, nlabels);
389  Aangle.fill(NAN);
390  Eigen::ArrayXXi N = Eigen::ArrayXXi::Zero(nlabels, nlabels);
391 
392  for (auto i : indborder) {
393  auto neighbors = m_neighborsIndexes(
394  i, Eigen::all); // indexes of the neighbors of i
395  Eigen::Vector3d N1(m_normals(i, Eigen::all)); // normal at i
396  for (auto j : neighbors) {
397  // Take the normals vector for i and j
398  Eigen::Vector3d N2(m_normals(j, Eigen::all)); // normal at j
399  double angle = angleRot2VecMat(N1, N2);
400  if ((m_labels(i) != -1) &&
401  (m_labels(j) != -1)) // points which belong to the discarded
402  // grains have the -1 label
403  {
404  if ((m_labels(i) > A.rows()) || (m_labels(j) > A.rows())) {
405  std::cout << "ERROR " << m_labels(i) << " " << m_labels(j)
406  << "(nlabels " << nlabels << ")" << std::endl;
407  } else {
408  A(m_labels(i), m_labels(j)) =
409  A(m_labels(i), m_labels(j)) + angle;
410  N(m_labels(i), m_labels(j)) =
411  N(m_labels(i), m_labels(j)) + 1;
412  }
413  }
414  }
415  }
416 
418  for (int r = 0; r < nlabels; r++) {
419  for (int c = 0; c < nlabels; c++) {
420  if (N(r, c) != 0) {
421  Aangle(r, c) = A(r, c) / N(r, c);
422  }
423  }
424  }
425 
426  return Aangle;
427 }
428 
429 bool G3PointAction::checkStacks(const std::vector<std::vector<int>>& stacks,
430  int count) {
431  std::set<int> indexes;
432  bool ret = true;
433  int errorCount = 0;
434 
435  // the stacks shall contain each point, only one time
436  for (auto& stack : stacks) {
437  for (int index : stack) {
438  if (indexes.count(index)) {
440  "[G3PointAction::check_stacks] index already in the "
441  "set " +
442  QString::number(index));
443  ret = false;
444  errorCount++;
445  }
446  indexes.insert(index);
447  }
448  }
449 
450  if (errorCount) {
451  CVLog::Warning("[G3PointAction::check_stacks] number of duplicates " +
452  QString::number(errorCount));
453  ret = false;
454  }
455 
456  // the number of points in the stacks shall be equal to count
457  if (indexes.size() != count) {
458  CVLog::Warning("[G3PointAction::check_stacks] point in stacks " +
459  QString::number(indexes.size()) + ", expected " +
460  QString::number(m_cloud->size()) +
461  " (may be due to duplicates in the point cloud)");
462  ret = false;
463  }
464 
465  return ret;
466 }
467 
468 bool G3PointAction::updateLocalMaximumIndexes() {
469  size_t nlabels = m_stacks.size();
470 
471  Eigen::ArrayXi localMaximumIndexes = Eigen::ArrayXi::Ones(nlabels) * (-1);
472 
473  for (int k = 0; k < nlabels; k++) {
474  auto& stack = m_stacks[k];
475  size_t nPoints = stack.size();
476  Eigen::ArrayXd elevations(nPoints);
477  for (int index = 0; index < nPoints; index++) {
478  const CCVector3* point = m_cloud->getPoint(stack[index]);
479  elevations(index) = point->z;
480  }
481  Eigen::Index maxIndex;
482  elevations.maxCoeff(&maxIndex);
483  localMaximumIndexes(k) = stack[maxIndex];
484  }
485 
486  if ((localMaximumIndexes == -1).any()) {
487  CVLog::Error(
488  "[G3PointAction::updateLocalMaximumIndexes] CANCEL value error "
489  "in the indexes of the local maximima");
490  return false;
491  }
492 
493  m_localMaximumIndexes = localMaximumIndexes;
494 
495  return true;
496 }
497 
498 bool G3PointAction::updateLabelsAndColors() {
499  CVLog::Print(QString("[G3PointAction::update_labels_and_colors]"));
500 
501  // g3point_label scalar field
502  int sfIdx = m_cloud->getScalarFieldIndexByName("g3point_label");
503  if (sfIdx == -1) {
504  sfIdx = m_cloud->addScalarField("g3point_label");
505  if (sfIdx == -1) {
506  CVLog::Error(
507  "[G3PointAction::update_labels_and_colors] impossible to "
508  "create scalar field g3point_label");
509  return false;
510  }
511  }
512  cloudViewer::ScalarField* g3point_label = m_cloud->getScalarField(sfIdx);
513 
514  RGBAColorsTableType randomColors = getRandomColors(m_stacks.size());
515 
516  if (!m_cloud->resizeTheRGBTable(false)) {
517  CVLog::Error(
518  QObject::tr("[G3PointAction::update_labels_and_colors] Not "
519  "enough memory!"));
520  return false;
521  }
522 
523  for (unsigned index = 0; index < m_cloud->size();
524  index++) // points which are not in the stacks will have the label -1
525  {
526  g3point_label->setValue(index, -1);
527  }
528 
529  m_labels = -1;
530 
531  for (int k = 0; k < m_stacks.size(); k++) {
532  const std::vector<int>& stack = m_stacks[k];
533  // labels
534  for (auto i : stack) {
535  m_labels(i) = k;
536  m_labelsnpoint(i) = static_cast<int>(stack.size());
537  if (g3point_label) {
538  g3point_label->setValue(i, k);
539  m_cloud->setPointColor(i, randomColors.getValue(k));
540  }
541  }
542  }
543 
544  if (g3point_label) {
545  g3point_label->computeMinAndMax();
546  }
547 
548  m_cloud->setCurrentDisplayedScalarField(sfIdx);
549  m_cloud->showColors(true);
550  m_cloud->showSF(false);
551 
552  m_cloud->redrawDisplay();
553  // m_cloud->prepareDisplayForRefresh();
554 
555  if (m_app) {
556  m_app->refreshAll();
557  m_app->updateUI();
558  }
559 
560  return true;
561 }
562 
563 bool G3PointAction::exportLocalMaximaAsCloud(
564  const Eigen::ArrayXi& localMaximumIndexes) {
565  // create cloud
566  // QString cloudName = m_cloud->getName() + "_g3point";
567  QString cloudName = "g3point_summits";
568  ccPointCloud* cloud = new ccPointCloud(cloudName);
569 
570  RGBAColorsTableType randomColors =
571  getRandomColors(localMaximumIndexes.size());
572 
573  for (auto index : localMaximumIndexes) {
574  cloud->addPoint(*m_cloud->getPoint(index));
575  }
576 
577  // allocate colors if necessary
578  if (cloud->resizeTheRGBTable()) {
579  for (int index = 0; index < static_cast<int>(cloud->size()); index++) {
580  cloud->setPointColor(index, randomColors.getValue(index));
581  }
582  }
583 
584  cloud->showColors(true);
585 
586  int nbChildren = m_cloud->getChildrenNumber();
587  std::vector<ccHObject*> toDelete;
588  for (int k = 0; k < nbChildren; k++) {
589  auto child = m_cloud->getChild(k);
590 
591  if (child->getName() == cloudName) {
592  toDelete.push_back(child);
593  }
594  }
595  for (auto& child : toDelete) {
596  m_app->removeFromDB(child, true);
597  }
598 
599  // parent->addChild(cloud, ccHObject::DP_PARENT_OF_OTHER, 0);
600  // m_app->addToDB(cloud);
601  m_cloud->addChild(cloud);
602  m_app->addToDB(cloud);
603 
604  cloud->setEnabled(false);
605 
606  return true;
607 }
608 
609 bool G3PointAction::processNewStacks(std::vector<std::vector<int>>& newStacks,
610  int pointCount) {
611  if (!checkStacks(newStacks, pointCount)) {
612  CVLog::Error(
613  "[G3PointAction::processNewStacks] newStacks is not valid");
614  return false;
615  }
616 
617  // new stacks are valid, set the class attribute
618  m_stacks = newStacks;
619 
620  updateLocalMaximumIndexes();
621 
622  updateLabelsAndColors();
623 
624  exportLocalMaximaAsCloud(m_localMaximumIndexes);
625 
626  return true;
627 }
628 
630  cloudViewer::ScalarField* g3PointLabel) {
631  m_stacks.clear();
632 
633  // get all the different labels
634  std::set<float> labels;
635  for (int idx = 0; idx < g3PointLabel->size(); idx++) {
636  labels.insert(g3PointLabel->getValue(idx));
637  }
638 
639  // rebuild the stacks
640  for (auto label : labels) {
641  std::vector<int> stack;
642  for (int idx = 0; idx < static_cast<int>(m_cloud->size()); idx++) {
643  if (g3PointLabel->getValue(idx) == label) {
644  stack.push_back(idx);
645  }
646  }
647  m_stacks.push_back(stack);
648  }
649 
650  if (processNewStacks(m_stacks, m_cloud->size())) {
651  return true;
652  } else {
653  return false;
654  }
655 }
656 
657 bool G3PointAction::merge(XXb& condition) {
658  std::vector<std::vector<int>> newStacks;
659  Eigen::ArrayXi newLabels = Eigen::ArrayXi::Ones(m_labels.size()) * (-1);
660  int countNewLabels = 0;
661  size_t nlabels = m_stacks.size();
662 
663  if (condition.rows() != m_stacks.size()) // check that condition is valid
664  {
665  CVLog::Error("[G3PointAction::merge] the shape of the condition (" +
666  QString::number(condition.rows()) + ", " +
667  QString::number(condition.cols()) +
668  ") is not coherent with the stacks size " +
669  QString::number(m_stacks.size()));
670  return false;
671  }
672 
673  for (int label = 0; label < nlabels; label++) {
674  if (newLabels(label) == -1) // the label has not already been merged
675  {
676  newLabels(label) = countNewLabels;
677  newStacks.push_back(
678  m_stacks[label]); // initialize a newStack with the stack
679  // of the current label
680  countNewLabels++;
681  }
682 
683  for (int otherLabel = 0; otherLabel < nlabels; otherLabel++) {
684  if (otherLabel == label) {
685  continue; // do not try to merge a label with itself
686  }
687 
688  // shall we merge otherLabel with label?
689  if (!condition(label, otherLabel)) {
690  std::vector<int>& labelStack = newStacks[newLabels(label)];
691 
692  if (newLabels(otherLabel) !=
693  -1) // the other label has already been merged
694  {
695  std::vector<int>& otherLabelStack =
696  newStacks[newLabels(otherLabel)];
697  if (newLabels(label) >
698  newLabels(otherLabel)) // merge label in otherLabel
699  {
700  // add the label stack to the otherLabel stack
701  otherLabelStack.insert(
702  otherLabelStack.end(), labelStack.begin(),
703  labelStack.end()); // add the stack to the
704  // label stack
705  // empty the label stack
706  labelStack.clear();
707  // update the label
708  newLabels(label) = newLabels(otherLabel);
709  }
710  if (newLabels(label) <
711  newLabels(otherLabel)) // merge otherLabel in label
712  {
713  // add the otherLabel stack to the label stack
714  labelStack.insert(
715  labelStack.end(), otherLabelStack.begin(),
716  otherLabelStack.end()); // add the stack to the
717  // label stack
718  // empty the otherLabel stack
719  otherLabelStack.clear();
720  // update the otherLabel
721  newLabels(otherLabel) = newLabels(label);
722  }
723  } else // merge otherLabel and label
724  {
725  std::vector<int>& otherLabelStack = m_stacks[otherLabel];
726  // add the otherLabel stack to the label stack
727  labelStack.insert(
728  labelStack.end(), otherLabelStack.begin(),
729  otherLabelStack.end()); // add the stack to the
730  // label stack
731  // update the otherLabel
732  newLabels(otherLabel) = newLabels(label);
733  }
734  }
735  }
736  }
737 
738  // remove empty stacks
739  std::vector<std::vector<int>> newStacksWithoutEmpty;
740  for (auto& stack : newStacks) {
741  if (!stack.empty()) {
742  newStacksWithoutEmpty.push_back(stack);
743  }
744  }
745 
746  newStacks = newStacksWithoutEmpty;
747 
748  CVLog::Print(
749  "[G3PointAction::merge] keep " + QString::number(newStacks.size()) +
750  "/" + QString::number(m_stacks.size()) + " labels (" +
751  QString::number(m_stacks.size() - newStacks.size()) + " removed)");
752 
753  if (!processNewStacks(newStacks, m_cloud->size())) {
754  CVLog::Error("[G3PointAction::merge] processing newStacks failed");
755  return false;
756  }
757 
758  return true;
759 }
760 
761 bool G3PointAction::keep(Xb& condition) {
762  std::vector<std::vector<int>> newStacks;
763  size_t pointCount = 0;
764 
765  for (int index = 0; index < condition.size(); index++) {
766  std::vector<int>& stack = m_stacks[index];
767  if (condition(index)) // if the condition is met, we keep the stack
768  {
769  newStacks.push_back(stack);
770  pointCount = pointCount + stack.size();
771  }
772  }
773 
774  CVLog::Print(
775  "[G3PointAction::keep] keep " + QString::number(newStacks.size()) +
776  "/" + QString::number(m_stacks.size()) + " gains (" +
777  QString::number(m_stacks.size() - newStacks.size()) + " removed)");
778 
779  if (!processNewStacks(newStacks, static_cast<int>(pointCount))) {
780  CVLog::Error("[G3PointAction::keep] processing newStacks failed");
781  }
782 
783  return true;
784 }
785 
786 template <typename T>
787 bool G3PointAction::EigenArrayToFile(QString name, T array) {
788  const Eigen::IOFormat CSVFormat(Eigen::StreamPrecision,
789  Eigen::DontAlignCols, ", ", "\n");
790  std::ofstream file(name.toLatin1());
791  file << array.format(CSVFormat);
792  return true;
793 }
794 
796  CVLog::Print("[cluster labels]");
797  size_t nlabels = m_stacks.size();
798 
799  m_maxAngle1 = m_dlg->getMaxAngle1();
800  m_radiusFactor = m_dlg->getRadiusFactor();
801 
802  // Compute the distances between the sinks associated to each label
803  Eigen::ArrayXXd D1(nlabels, nlabels);
804  if (m_localMaximumIndexes.size() != nlabels) {
805  CVLog::Error("[G3PointAction::cluster] m_localMaximumIndexes size (" +
806  QString::number(m_localMaximumIndexes.size()) +
807  ") different from nlabels " + QString::number(nlabels));
808  return false;
809  }
810  for (int i = 0; i < nlabels; i++) {
811  for (int j = 0; j < nlabels; j++) {
812  D1(i, j) = (*m_cloud->getPoint(m_localMaximumIndexes(i)) -
813  *m_cloud->getPoint(m_localMaximumIndexes(j)))
814  .norm();
815  }
816  }
817 
818  // Estimate the distances between labels using the areas
819  int k = 0;
820  Eigen::ArrayXXd D2 = Eigen::ArrayXXd::Zero(nlabels, nlabels);
821  Eigen::ArrayXd radius = Eigen::ArrayXd::Zero(nlabels);
822  for (auto& stack : m_stacks) // Radius of each label (assuming the surface
823  // corresponds to a disk)
824  {
825  radius(k) = sqrt(m_area(stack).sum() / M_PI);
826  k++;
827  }
828  for (int i = 0; i < nlabels;
829  i++) // Compute inter-distances by summing radius
830  {
831  for (int j = 0; j < nlabels; j++) {
832  D2(i, j) = radius(i) + radius(j);
833  }
834  }
835 
836  // If the radius of the sink is above the distance to the other sink (by a
837  // factor of rad_factor), set Dist to 1
838  Eigen::ArrayXXi Dist = Eigen::ArrayXXi::Zero(nlabels, nlabels);
839  Dist = (m_radiusFactor * D2 > D1).select(1, Dist);
840  for (int i = 0; i < nlabels; i++) // set the values of the diagonal to 0
841  {
842  Dist(i, i) = 0;
843  }
844 
845  // If labels are neighbours, set Nneigh to 1
846  Eigen::ArrayXXi Nneigh = Eigen::ArrayXXi::Zero(nlabels, nlabels);
847  k = 0;
848  for (auto& stack : m_stacks) {
849  Eigen::ArrayXXi labels(stack.size(), m_kNN);
850  for (int index = 0; index < stack.size(); index++) {
851  for (int n = 0; n < m_kNN; n++) {
852  labels(index, n) =
853  m_labels(m_neighborsIndexes(stack[index], n));
854  }
855  }
856  auto reshaped = labels.reshaped();
857  std::set<int> unique_elements(reshaped.begin(), reshaped.end());
858  for (auto unique : unique_elements) {
859  Nneigh(k, unique) = 1;
860  }
861  k++;
862  }
863 
864  Eigen::ArrayXXd A = computeMeanAngleBetweenNormalsAtBorders();
865 
866  // merge labels if sinks are
867  // => close to each other (Dist == 1)
868  // => neighbours (Nneigh == 1)
869  // => normals are similar (A < m_maxAngle1)
870  // => A is not NaN
871 
872  if (!checkStacks(m_stacks, m_cloud->size())) {
873  CVLog::Error("m_stacks is not valid");
874  return false;
875  }
876 
877  // create the condition matrix and force the symmetry of the matrix
878  XXb condition = (Dist < 1) || (Nneigh < 1) || (A > m_maxAngle1) || (A != A);
879  XXb symmetrical_condition =
880  (condition == condition.transpose()).select(condition, true);
881  symmetrical_condition.count();
882  condition = symmetrical_condition;
883 
884  std::vector<std::vector<int>> newStacks;
885  Eigen::ArrayXi newLabels = Eigen::ArrayXi::Ones(m_labels.size()) * (-1);
886  int countNewLabels = 0;
887 
888  for (int label = 0; label < nlabels; label++) {
889  if (newLabels(label) == -1) // the label has not already been merged
890  {
891  newLabels(label) = countNewLabels;
892  newStacks.push_back(
893  m_stacks[label]); // initialize a newStack with the stack
894  // of the current label
895  countNewLabels++;
896  }
897 
898  for (int otherLabel = 0; otherLabel < nlabels; otherLabel++) {
899  if (otherLabel == label) {
900  continue; // do not try to merge a label with itself
901  }
902 
903  // shall we merge otherLabel with label?
904  if (!condition(label, otherLabel)) {
905  std::vector<int>& labelStack = newStacks[newLabels(label)];
906 
907  if (newLabels(otherLabel) !=
908  -1) // the other label has already been merged
909  {
910  std::vector<int>& otherLabelStack =
911  newStacks[newLabels(otherLabel)];
912  if (newLabels(label) >
913  newLabels(otherLabel)) // merge label in otherLabel
914  {
915  // add the label stack to the otherLabel stack
916  otherLabelStack.insert(
917  otherLabelStack.end(), labelStack.begin(),
918  labelStack.end()); // add the stack to the
919  // label stack
920  // empty the label stack
921  labelStack.clear();
922  // update the label
923  newLabels(label) = newLabels(otherLabel);
924  }
925  if (newLabels(label) <
926  newLabels(otherLabel)) // merge otherLabel in label
927  {
928  // add the otherLabel stack to the label stack
929  labelStack.insert(
930  labelStack.end(), otherLabelStack.begin(),
931  otherLabelStack.end()); // add the stack to the
932  // label stack
933  // empty the otherLabel stack
934  otherLabelStack.clear();
935  // update the otherLabel
936  newLabels(otherLabel) = newLabels(label);
937  }
938  } else // merge otherLabel and label
939  {
940  std::vector<int>& otherLabelStack = m_stacks[otherLabel];
941  // add the otherLabel stack to the label stack
942  labelStack.insert(
943  labelStack.end(), otherLabelStack.begin(),
944  otherLabelStack.end()); // add the stack to the
945  // label stack
946  // update the otherLabel
947  newLabels(otherLabel) = newLabels(label);
948  }
949  }
950  }
951  }
952 
953  // remove empty stacks
954  std::vector<std::vector<int>> newStacksWithoutEmpty;
955  for (auto& stack : newStacks) {
956  if (!stack.empty()) {
957  newStacksWithoutEmpty.push_back(stack);
958  }
959  }
960 
961  newStacks = newStacksWithoutEmpty;
962 
963  CVLog::Print(
964  "[G3PointAction::merge] keep " + QString::number(newStacks.size()) +
965  "/" + QString::number(m_stacks.size()) + " labels (" +
966  QString::number(m_stacks.size() - newStacks.size()) + " removed)");
967 
968  if (!processNewStacks(newStacks, m_cloud->size())) {
969  CVLog::Error("[G3PointAction::cluster] new stacks are not valid");
970  return false;
971  }
972 
973  return true;
974 }
975 
977  if (m_stacks.empty()) {
979  "[G3PointAction::fit] stacks are empty, try to rebuild them "
980  "using g3point_label scalar field");
981  int idx = m_cloud->getScalarFieldIndexByName("g3point_label");
982  if (idx == -1) {
984  "[G3PointAction::fit] no existing g3point_label scalar "
985  "field");
986  return;
987  }
988  cloudViewer::ScalarField* g3PointLabel = m_cloud->getScalarField(idx);
989  if (!buildStacksFromG3PointLabelSF(g3PointLabel)) {
991  "[G3PointAction::fit] not possible to build stacks from "
992  "existing g3point_scalar field");
993  return;
994  }
995  }
996 
997  // plot display grains as ellipsoids
998  m_grainColors.reset(new RGBAColorsTableType(
999  getRandomColors(m_localMaximumIndexes.size())));
1000  m_grainsAsEllipsoids =
1001  new GrainsAsEllipsoids(m_cloud, m_app, m_stacks, *m_grainColors);
1002  m_grainsAsEllipsoids->setName("g3point_ellipsoids");
1003 
1004  // add connections with the dialog
1005  connect(m_dlg, &G3PointDialog::onlyOneClicked, m_grainsAsEllipsoids,
1007  connect(m_dlg, &G3PointDialog::allClicked, m_grainsAsEllipsoids,
1009  connect(m_dlg, &G3PointDialog::onlyOneChanged, m_grainsAsEllipsoids,
1011  connect(m_dlg, &G3PointDialog::transparencyChanged, m_grainsAsEllipsoids,
1013  connect(m_dlg, &G3PointDialog::drawSurfaces, m_grainsAsEllipsoids,
1015  connect(m_dlg, &G3PointDialog::drawLines, m_grainsAsEllipsoids,
1017  connect(m_dlg, &G3PointDialog::drawPoints, m_grainsAsEllipsoids,
1019  connect(m_dlg, &G3PointDialog::glPointSize, m_grainsAsEllipsoids,
1021 
1022  m_dlg->setOnlyOneMax(static_cast<int>(m_stacks.size()));
1023  m_dlg->emitSignals(); // force to send parameters to m_grainsAsEllipsoids
1024 
1025  // m_cloud->getParent()->addChild(m_grainsAsEllipsoids.get());
1026  // m_app->addToDB(m_cloud);
1027  m_cloud->addChild(m_grainsAsEllipsoids);
1028  m_app->addToDB(m_grainsAsEllipsoids);
1029 
1030  m_app->updateUI();
1031 }
1032 
1034  if (m_grainsAsEllipsoids) {
1035  m_grainsAsEllipsoids->exportResultsAsCloud();
1036  }
1037 }
1038 
1039 Eigen::VectorXf arange(double start, double stop, double step) {
1040  // Values are generated within the half-open interval [0, stop)
1041  double range = (stop - start);
1042  int n_steps = floor(range / step) + 1;
1043  Eigen::VectorXf vector(n_steps);
1044  for (int k = 0; k < n_steps; k++) {
1045  vector(k) = start + k * step;
1046  }
1047  return vector;
1048 }
1049 
1050 template <typename T>
1051 void myPrint(QString name, T array) {
1052  std::cout << name.toStdString() << "\n" << array << std::endl;
1053 }
1054 
1055 // from https://stackoverflow.com/questions/11964552/finding-quartiles
1056 // this gives the same values as python with the default method
1057 // in Python, use hazen as a method to get the same results as in Matlab
1058 // Hyndman and Fan, 1996) R. J. Hyndman and Y. Fan, “Sample quantiles in
1059 // statistical packages”, The American Statistician, 50(4), pp. 361-365, 1996
1060 template <typename T1, typename T2>
1061 typename T1::value_type quant(T1& x, const T2& q) {
1062  assert(q >= 0.0 && q <= 1.0);
1063 
1064  std::sort(x.begin(), x.end());
1065  const auto n = x.size();
1066  const auto id = (n - 1) * q;
1067  const auto lo = floor(id);
1068  const auto hi = ceil(id);
1069  const auto qs = x[lo];
1070  const auto h = (id - lo);
1071 
1072  return (1.0 - h) * qs + h * x[hi];
1073 }
1074 
1075 template <typename T>
1076 double std_dev(const T& vec) {
1077  // un-biased estimation of the standard deviation => divide by vec.size() -
1078  // 1
1079  return std::sqrt((vec - vec.mean()).square().sum() / (vec.size() - 1));
1080 }
1081 
1083 
1084 void G3PointAction::showWolman(const Eigen::ArrayXf& d_sample,
1085  const Eigen::Array3d& dq_final,
1086  const Eigen::Array3d& edq) {
1087  // QCustomPlot
1088  if (!s_g3PointPlots) s_g3PointPlots = new G3PointPlots(m_cloud->getName());
1089  s_g3PointPlots->addToTabWidget(
1090  new WolmanCustomPlot(d_sample, dq_final, edq));
1091  s_g3PointPlots->show();
1092 }
1093 
1095  int n_iter = m_dlg->getWolmanNbIter();
1096 
1097  if (!m_grainsAsEllipsoids) {
1098  CVLog::Error(
1099  "[G3PointAction::wolman] no ellipsoid, not possible to do "
1100  "Wolman analysis");
1101  return false;
1102  }
1103 
1104  // get the ellipsoid y axes andd labels
1105  int nEllipsoids = static_cast<int>(m_grainsAsEllipsoids->m_center.size());
1106  Eigen::VectorXf b_axis(nEllipsoids);
1107  Eigen::VectorXi ellipsoidLabels(nEllipsoids);
1108  for (int i = 0; i < nEllipsoids; i++) {
1109  b_axis(i) = 2 * m_grainsAsEllipsoids->m_radii[i]
1110  .y(); // files are in radius, we need diameters
1111  ellipsoidLabels(i) = i;
1112  }
1113 
1114  // rebuild vectors with the coordinates and the labels of the original
1115  // points
1116  int n_points = m_grainsAsEllipsoids->m_cloud->size();
1117  Eigen::ArrayXf x(n_points);
1118  Eigen::ArrayXf y(n_points);
1119  Eigen::ArrayXf z(n_points);
1120  Eigen::ArrayXi pointsLabels(n_points);
1121  int sfIdx = m_grainsAsEllipsoids->m_cloud->getScalarFieldIndexByName(
1122  "g3point_label");
1123  if (sfIdx == -1) {
1124  CVLog::Error("[G3PointAction::wolman] no g3point_label");
1125  return false;
1126  }
1127  cloudViewer::ScalarField* g3point_label = m_cloud->getScalarField(sfIdx);
1128  for (int i = 0; i < n_points; i++) {
1129  const CCVector3* P = m_grainsAsEllipsoids->m_cloud->getPoint(i);
1130  x(i) = P->x;
1131  y(i) = P->y;
1132  z(i) = P->z;
1133  pointsLabels(i) = g3point_label->getValue(i);
1134  }
1135 
1136  float dx = 1.1 * b_axis.maxCoeff();
1137 
1138  std::vector<Eigen::ArrayXf> d;
1139 
1140  QMutex mutex; // To protect d.push_back
1141  std::vector<int> iter_indices(n_iter);
1142  std::iota(iter_indices.begin(), iter_indices.end(), 0);
1143 
1144  ecvProgressDialog pDlg(true, m_app->getMainWindow());
1145  pDlg.setMethodTitle("Wolman Analysis");
1146  pDlg.setMaximum(n_iter);
1147  pDlg.show();
1148 
1149  // atomic counter for progress
1150  std::atomic<int> progress_count(0);
1151 
1152  QtConcurrent::blockingMap(iter_indices, [&](int k) {
1153  if (pDlg.wasCanceled()) return;
1154 
1155  // local variables
1156  Eigen::ArrayXf x_grid;
1157  Eigen::ArrayXf y_grid;
1158  Eigen::ArrayXf distances;
1159  Eigen::Index minLoc;
1160 
1161  // Thread-local RNG
1162  static thread_local std::mt19937 urbg(std::random_device{}());
1163  std::uniform_real_distribution<float> dist{0, 1};
1164 
1165  float r0 = dist(urbg);
1166  float r1 = dist(urbg);
1167 
1168  x_grid = arange(x.minCoeff() - r0 * dx, x.maxCoeff(), dx);
1169  y_grid = arange(y.minCoeff() - r1 * dx, y.maxCoeff(), dx);
1170  int nx = x_grid.size();
1171  int ny = y_grid.size();
1172  Eigen::ArrayXXf dist_grid(
1173  nx, ny); // Rename 'dist' to avoid conflict with RNG
1174  Eigen::ArrayXXf iWolman(nx, ny);
1175  for (int ix = 0; ix < nx; ix++) {
1176  for (int iy = 0; iy < ny; iy++) {
1177  distances = ((x - x_grid(ix)).pow(2) + (y - y_grid(iy)).pow(2))
1178  .sqrt();
1179  dist_grid(ix, iy) = distances.minCoeff(&minLoc);
1180  iWolman(ix, iy) = minLoc;
1181  }
1182  }
1183 
1184  XXb condition = (dist_grid < dx / 10);
1185  Eigen::ArrayXf iWolmanSelection(condition.count());
1186  int indexInWolmanSelection = 0;
1187 
1188  for (int ix = 0; ix < nx; ix++) {
1189  for (int iy = 0; iy < ny; iy++) {
1190  if (condition(ix, iy)) {
1191  iWolmanSelection(indexInWolmanSelection) = iWolman(ix, iy);
1192  indexInWolmanSelection++;
1193  }
1194  }
1195  }
1196  Eigen::ArrayXi wolmanSelection;
1197  wolmanSelection = pointsLabels(iWolmanSelection);
1198 
1199  std::unordered_set<int> setOfA(wolmanSelection.begin(),
1200  wolmanSelection.end());
1201  std::vector<int> y_ind;
1202  for (auto label : wolmanSelection) {
1203  if (m_grainsAsEllipsoids->m_fitNotOK.count(label)) {
1204  continue;
1205  } else {
1206  y_ind.push_back(label);
1207  }
1208  }
1209  Eigen::ArrayXf d_item(y_ind.size());
1210  for (int i = 0; i < y_ind.size(); i++) {
1211  d_item(i) = b_axis(y_ind[i]) * 1000; // conversion to mm
1212  }
1213 
1214  {
1215  QMutexLocker locker(&mutex);
1216  d.push_back(d_item);
1217  }
1218  });
1219 
1220  Eigen::ArrayXXf dq(n_iter, 3);
1221  Eigen::ArrayXf d_sample = d[0];
1222  dq(0, Eigen::all) << quant(d[0], 0.1), quant(d[0], 0.5), quant(d[0], 0.9);
1223  for (int i = 1; i < n_iter; i++) {
1224  Eigen::ArrayXf tmp(d_sample.size() + d[i].size());
1225  tmp << d_sample, d[i];
1226  d_sample = tmp;
1227  dq(i, Eigen::all) << quant(d[i], 0.1), quant(d[i], 0.5),
1228  quant(d[i], 0.9);
1229  }
1230 
1231  // std::cout << "d_sample " << d_sample << std::endl;
1232 
1233  // compute standard deviation
1234  Eigen::Array3d edq{std_dev(dq(Eigen::all, 0)), std_dev(dq(Eigen::all, 1)),
1235  std_dev(dq(Eigen::all, 2))};
1236  Eigen::Array3d dq_final{quant(d_sample, 0.1), quant(d_sample, 0.5),
1237  quant(d_sample, 0.9)};
1238 
1239  // std::cout << "d_sample\n" << d_sample << std::endl;
1240  CVLog::Print(
1241  QString("[G3PointAction::wolman] quantl 0.1 0.5 0.9 \n%1 %2 %3")
1242  .arg(dq_final(0))
1243  .arg(dq_final(1))
1244  .arg(dq_final(2)));
1245 
1246  CVLog::Print(
1247  QString("[G3PointAction::wolman] std_dev 0.1 0.5 0.9 \n%1 %2 %3")
1248  .arg(edq(0))
1249  .arg(edq(1))
1250  .arg(edq(2)));
1251 
1252  showWolman(d_sample, dq_final, edq);
1253 
1254  return true;
1255 }
1256 
1258  if (!m_grainsAsEllipsoids) {
1259  CVLog::Error(
1260  "[G3PointAction::angles] no ellipsoid, not possible to do "
1261  "angles analysis");
1262  return false;
1263  }
1264 
1265  float delta = static_cast<float>(1e32);
1266  int n_ellipsoids =
1267  static_cast<float>(m_grainsAsEllipsoids->m_rotationMatrix.size());
1268  QVector<double> granuloAngleMView(n_ellipsoids);
1269  QVector<double> granuloAngleXView(n_ellipsoids);
1270 
1271  for (int i = 0; i < n_ellipsoids; i++) {
1272  float u, v, w;
1273 
1274  Eigen::Vector3f p2{m_grainsAsEllipsoids->m_rotationMatrix[i](0, 0),
1275  m_grainsAsEllipsoids->m_rotationMatrix[i](1, 0),
1276  m_grainsAsEllipsoids->m_rotationMatrix[i](2, 0)};
1277 
1278  // x-y plot - mapview (angle with y axis)
1279  Eigen::Vector3f p1{m_grainsAsEllipsoids->m_center[i].x(),
1280  m_grainsAsEllipsoids->m_center[i].y() + delta,
1281  m_grainsAsEllipsoids->m_center[i].z()};
1282  float angle = atan2(p1.cross(p2).norm(), p1.dot(p2));
1283  u = p2(0);
1284  v = p2(1);
1285  if ((angle > M_PI / 2) || (angle < -M_PI / 2)) {
1286  u = -u;
1287  v = -v;
1288  }
1289  granuloAngleMView[i] = (atan(v / u) + M_PI / 2) * 180 / M_PI;
1290 
1291  // x-z plot
1292  p1 << m_grainsAsEllipsoids->m_center[i].x(),
1293  m_grainsAsEllipsoids->m_center[i].y(),
1294  m_grainsAsEllipsoids->m_center[i].z() + delta;
1295  angle = atan2(p1.cross(p2).norm(), p1.dot(p2));
1296  v = p2(0);
1297  w = p2(1);
1298  if ((angle > M_PI / 2) || (angle < -M_PI / 2)) {
1299  v = -v;
1300  w = -w;
1301  }
1302  granuloAngleXView[i] = (atan(v / w) + M_PI / 2) * 180 / M_PI;
1303  }
1304 
1305  // QCustomPlot
1306  if (!s_g3PointPlots) s_g3PointPlots = new G3PointPlots(m_cloud->getName());
1307  int nbBins = m_dlg->getAnglesNbBins();
1308  s_g3PointPlots->addToTabWidget(
1309  new AnglesCustomPlot(granuloAngleMView, "Azimut", nbBins));
1310  s_g3PointPlots->addToTabWidget(
1311  new AnglesCustomPlot(granuloAngleXView, "Dip", nbBins));
1312  s_g3PointPlots->show();
1313 
1314  return true;
1315 }
1316 
1318  CVLog::Print("[cleanLabels]");
1319 
1320  m_maxAngle2 = m_dlg->getMaxAngle2();
1321  m_nMin = m_dlg->getNMin();
1322  m_minFlatness = m_dlg->getMinFlatness();
1323 
1324  // merge points considering the normals at the border
1325  {
1326  CVLog::Print(
1327  "[cleanLabels] merge points considering the normals at the "
1328  "border");
1329  Eigen::ArrayXXd A = computeMeanAngleBetweenNormalsAtBorders();
1330  XXb condition = (A > m_maxAngle2) ||
1331  (A != A); // add true on the diagonal (important for
1332  // the if hereafter)
1333  XXb symmetrical_condition =
1334  (condition == condition.transpose()).select(condition, true);
1335 
1336  merge(symmetrical_condition);
1337 
1338  QApplication::processEvents();
1339  }
1340 
1341  // remove small labels
1342  {
1343  CVLog::Print("[cleanLabels] remove small labels");
1344  Eigen::ArrayXi stackSize(m_stacks.size());
1345  for (size_t k = 0; k < m_stacks.size(); k++) {
1346  stackSize(k) = static_cast<int>(m_stacks[k].size());
1347  }
1348  Xb condition = (stackSize > m_nMin);
1349  size_t numberOfGrainsToKeep = condition.count();
1350  if (numberOfGrainsToKeep == m_stacks.size()) {
1351  CVLog::Print("[cleanLabels] all grains are larger than " +
1352  QString::number(m_nMin) +
1353  " points, nothing to remove");
1354  } else if (numberOfGrainsToKeep) {
1355  keep(condition);
1356  } else {
1357  CVLog::Error(
1358  "[cleanLabels] no remaining grain after removing those "
1359  "with less than " +
1360  QString::number(m_nMin) + " points");
1361  return false;
1362  }
1363  QApplication::processEvents();
1364  }
1365 
1366  // clean labels
1367  {
1368  CVLog::Print("[cleanLabels] remove flattish labels");
1369  Eigen::ArrayX3d s(m_stacks.size(), 3);
1370  for (size_t k = 0; k < m_stacks.size(); k++) {
1371  std::vector<int>& stack = m_stacks[k];
1372  // get the points of the label
1373  size_t nPoints = stack.size();
1374  Eigen::MatrixX3d points(nPoints, 3);
1375  for (int index = 0; index < nPoints; index++) {
1376  const CCVector3* point = m_cloud->getPoint(stack[index]);
1377  points(index, 0) = point->x;
1378  points(index, 1) = point->y;
1379  points(index, 2) = point->z;
1380  }
1381  // compute the centroid of the label
1382  Eigen::RowVector3d centroid = points.colwise().mean();
1383  points.rowwise() -= centroid;
1384  // SVD decomposition A = U S V∗
1385  s(k, Eigen::all) = points.jacobiSvd().singularValues();
1386  }
1387  // filtering condition: (l2 / l0 > min_flatness) or (l1 / l0 > 2 *
1388  // min_flatness)
1389  Xb condition =
1390  (s(Eigen::all, 2) / s(Eigen::all, 0) > m_minFlatness) ||
1391  (s(Eigen::all, 1) / s(Eigen::all, 0) > 2. * m_minFlatness);
1392  size_t numberOfGrainsToKeep = condition.count();
1393  if (numberOfGrainsToKeep == m_stacks.size()) {
1394  CVLog::Print("[cleanLabels] no flattish grain, nothing to remove");
1395  } else if (numberOfGrainsToKeep) {
1396  keep(condition);
1397  } else {
1398  CVLog::Error(
1399  "[cleanLabels] no remaining grain after removing the "
1400  "flattish ones");
1401  return false;
1402  }
1403  }
1404 
1405  return true;
1406 }
1407 
1408 void G3PointAction::addToStackBraunWillett(int index,
1409  const Eigen::ArrayXi& delta,
1410  const Eigen::ArrayXi& Di,
1411  std::vector<int>& stack,
1412  int local_maximum) {
1413  stack.push_back(index);
1414 
1415  for (int k = delta[index]; k < delta[index + 1]; k++) {
1416  if (Di[k] != local_maximum) // avoid infinite loop
1417  {
1418  addToStackBraunWillett(Di[k], delta, Di, stack, local_maximum);
1419  }
1420  }
1421 }
1422 
1423 int G3PointAction::segmentLabelsBraunWillett() {
1424  CVLog::Print("[segment_labels_braun_willett]");
1425 
1426  bool steepestSlope = m_dlg->isSteepestSlope();
1427 
1428  // for each point, find in the neighborhood the point with the extreme
1429  // slope, depending on the mode (the receiver)
1430  Eigen::ArrayXd extreme_slopes;
1431  if (steepestSlope) {
1432  CVLog::Print(
1433  "[segment_labels] classical steepest slope algorithm [Braun, "
1434  "Willett 2013]");
1435  extreme_slopes = m_neighborsSlopes.rowwise().maxCoeff();
1436  } else {
1437  CVLog::Print(
1438  "[segment_labels] reversed version of the steepest slope "
1439  "algorithm [Braun, Willett 2013]");
1440  extreme_slopes = m_neighborsSlopes.rowwise().minCoeff();
1441  }
1442  Eigen::ArrayXi index_of_extreme_slope =
1443  Eigen::ArrayXi::Zero(m_cloud->size());
1444  Eigen::ArrayXi receivers(m_cloud->size());
1445 
1446  for (unsigned index = 0; index < m_cloud->size(); index++) {
1447  double extreme_slope = extreme_slopes(index);
1448  for (int k = 0; k < m_kNN; k++) {
1449  if (m_neighborsSlopes(index, k) == extreme_slope) {
1450  index_of_extreme_slope(index) = k;
1451  break;
1452  }
1453  }
1454  receivers(index) =
1455  m_neighborsIndexes(index, index_of_extreme_slope(index));
1456  }
1457 
1458  // if the minimum slope is positive, the receiver is a local maximum
1459  int nb_maxima;
1460  if (steepestSlope) {
1461  nb_maxima = (extreme_slopes <= 0).count();
1462  } else {
1463  nb_maxima = (extreme_slopes >= 0).count();
1464  }
1465  m_initial_localMaximumIndexes = Eigen::ArrayXi::Zero(
1466  nb_maxima); // we need nb_maxima for the initialization
1467  int l = 0;
1468  for (unsigned int k = 0; k < m_cloud->size(); k++) {
1469  if (steepestSlope) {
1470  if (extreme_slopes(k) <= 0) {
1471  m_initial_localMaximumIndexes(l) = k;
1472  receivers(k) = k;
1473  l++;
1474  }
1475  } else {
1476  if (extreme_slopes(k) >= 0) {
1477  m_initial_localMaximumIndexes(l) = k;
1478  receivers(k) = k;
1479  l++;
1480  }
1481  }
1482  }
1483 
1484  // get the number of donors per receiver (di) and build the list of donors
1485  // per receiver (Dij)
1486  CVLog::Print(
1487  "[segment_labels_braun_willett] identify the donors for each "
1488  "receiver");
1489  Eigen::ArrayXi di =
1490  Eigen::ArrayXi::Zero(m_cloud->size()); // number of donors
1491  std::vector<std::vector<int>> Dij; // lists of donors (one list per point)
1492  for (unsigned int k = 0; k < m_cloud->size();
1493  k++) // initialize the lists of donors
1494  {
1495  std::vector<int> list_of_donors;
1496  Dij.push_back(list_of_donors);
1497  }
1498  CVLog::Print("[segment_labels_braun_willett] create di and Dij");
1499  for (unsigned int k = 0; k < m_cloud->size(); k++) {
1500  int receiver = receivers(k);
1501  di[receiver] = di[receiver] +
1502  1; // increment the number of donors of the receiver
1503  Dij[receiver].push_back(
1504  k); // add the donor to the list of donors of the receiver
1505  }
1506 
1507  m_ndon = di;
1508 
1509  // build Di, the list of donors
1510  Eigen::ArrayXi Di =
1511  Eigen::ArrayXi::Zero(m_cloud->size()); // list of donors
1512  int idx = 0;
1513  for (auto& list_ : Dij) // build the list of donors
1514  {
1515  for (int point : list_) {
1516  Di[idx] = point;
1517  idx++;
1518  }
1519  }
1520 
1521  // build delta, the index array
1522  Eigen::ArrayXi delta = Eigen::ArrayXi::Zero(m_cloud->size() +
1523  1); // index of the first donor
1524  delta[m_cloud->size()] = m_cloud->size();
1525  std::vector<int> delta_vec(m_cloud->size());
1526  for (int i = m_cloud->size() - 1; i >= 0; i--) {
1527  delta[i] = delta[i + 1] - di[i];
1528  delta_vec[i] = delta[i];
1529  }
1530 
1531  // build the stacks
1532  CVLog::Print("[segment_labels_braun_willett] build the stacks");
1533 
1534  int sfIdx =
1535  m_cloud->getScalarFieldIndexByName("g3point_initial_segmentation");
1536  if (sfIdx == -1) {
1537  sfIdx = m_cloud->addScalarField("g3point_initial_segmentation");
1538  if (sfIdx == -1) {
1539  CVLog::Error(
1540  "[G3Point::segment_labels] impossible to create scalar "
1541  "field g3point_initial_segmentation");
1542  }
1543  }
1544  cloudViewer::ScalarField* g3point_label = m_cloud->getScalarField(sfIdx);
1545 
1546  RGBAColorsTableType randomColors =
1547  getRandomColors(m_initial_localMaximumIndexes.size());
1548 
1549  if (!m_cloud->resizeTheRGBTable(false)) {
1550  CVLog::Error(QObject::tr("Not enough memory!"));
1551  return -1;
1552  }
1553 
1554  for (int k = 0; k < m_initial_localMaximumIndexes.size(); k++) {
1555  int localMaximumIndex = m_initial_localMaximumIndexes(k);
1556  std::vector<int> stack;
1557  addToStackBraunWillett(localMaximumIndex, delta, Di, stack,
1558  localMaximumIndex);
1559  // labels
1560  for (auto i : stack) {
1561  m_initial_labels(i) = k;
1562  m_initial_labelsnpoint(i) = static_cast<float>(stack.size());
1563  if (g3point_label) {
1564  g3point_label->setValue(i, k);
1565  m_cloud->setPointColor(i, randomColors.getValue(k));
1566  }
1567  }
1568  m_initialStacks.push_back(stack);
1569  }
1570 
1571  if (!checkStacks(m_initialStacks,
1572  m_cloud->size())) // check the stacks coherency
1573  {
1574  m_cloud->deleteScalarField(sfIdx);
1575  return -1;
1576  }
1577 
1578  if (g3point_label) {
1579  g3point_label->computeMinAndMax();
1580  }
1581 
1582  m_cloud->setCurrentDisplayedScalarField(sfIdx);
1583  m_cloud->showColors(true);
1584  m_cloud->showSF(false);
1585 
1586  m_cloud->redrawDisplay();
1587  // m_cloud->prepareDisplayForRefresh();
1588 
1589  if (m_app) {
1590  m_app->refreshAll();
1591  m_app->updateUI();
1592  }
1593 
1594  int nLabels = m_initial_localMaximumIndexes.size();
1595 
1596  return nLabels;
1597 }
1598 
1599 void G3PointAction::getNeighborsDistancesSlopes(unsigned index,
1600  std::vector<char>& duplicates) {
1601  const CCVector3* P = m_cloud->getPoint(index);
1602 
1603  m_nNSS.level = m_bestOctreeLevel;
1604  double maxSquareDist = 0;
1605  int neighborhoodSize = 0;
1606  cloudViewer::ReferenceCloud Yk(m_cloud);
1607 
1608  // get the nearest neighbors
1609  if (m_octree->findPointNeighbourhood(
1610  P, &Yk, static_cast<unsigned>(m_kNN + 1), m_bestOctreeLevel,
1611  maxSquareDist, 0,
1612  &neighborhoodSize) >= static_cast<unsigned>(m_kNN)) {
1613  assert(m_kNN == m_neighborsIndexes.cols());
1614  for (int k = 0; k < m_kNN; k++) {
1615  // store the index of the neighbor
1616  m_neighborsIndexes(index, k) = Yk.getPointGlobalIndex(k + 1);
1617  // compute the distance to the neighbor
1618  const CCVector3* neighbor = Yk.getPoint(k + 1);
1619  float distance = (*P - *neighbor).norm();
1620  m_neighborsDistances(index, k) = distance;
1621  // compute the slope to the neighbor
1622  if (distance != 0) {
1623  m_neighborsSlopes(index, k) = (P->z - neighbor->z) / distance;
1624  } else {
1625  m_neighborsSlopes(index, k) =
1626  0.; // it is possible to have duplicates in the cloud
1627  duplicates[index] = 1;
1628  }
1629  }
1630  }
1631 }
1632 
1633 void G3PointAction::computeNodeSurfaces() {
1634  m_area = M_PI * m_neighborsDistances.rowwise().minCoeff().square();
1635 }
1636 
1637 bool G3PointAction::computeNormalsAndOrientThem() {
1638  double radius = 1.; // one should set the radius value
1639 
1641  ccNormalVectors::Orientation orientation =
1642  ccNormalVectors::Orientation::UNDEFINED;
1643 
1644  orientation = ccNormalVectors::Orientation::PLUS_Z;
1645  model = CV_LOCAL_MODEL_TYPES::LS;
1646 
1647  QScopedPointer<ecvProgressDialog> progressDialog(nullptr);
1648 
1649  if (!m_cloud->getOctree()) {
1650  if (!m_cloud->computeOctree(progressDialog.data())) {
1651  CVLog::Error("Failed to compute octree for cloud " +
1652  m_cloud->getName());
1653  return false;
1654  }
1655  }
1656 
1657  float thisCloudRadius = radius;
1658  if (std::isnan(thisCloudRadius)) {
1660  {
1661  params.aimedPopulationPerCell = 16;
1662  params.aimedPopulationRange = 4;
1663  params.minCellPopulation = 6;
1664  params.minAboveMinRatio = 0.97;
1665  }
1666  thisCloudRadius = ccOctree::GuessBestRadius(
1667  m_cloud, params, m_cloud->getOctree().data());
1668  if (thisCloudRadius == 0) {
1669  return CVLog::Error(
1670  "Failed to determine best normal radius for cloud " +
1671  m_cloud->getName());
1672  }
1673  CVLog::Print("Cloud " + m_cloud->getName() +
1674  " radius = " + QString::number(thisCloudRadius));
1675  }
1676 
1677  CVLog::Print("computeNormalsWithOctree started...");
1678  bool success = m_cloud->computeNormalsWithOctree(
1679  model, orientation, thisCloudRadius, progressDialog.data());
1680  if (success) {
1681  CVLog::Print("computeNormalsWithOctree success");
1682  } else {
1683  CVLog::Error("computeNormalsWithOctree failed");
1684  return false;
1685  }
1686 
1687  return true;
1688 }
1689 
1690 void G3PointAction::orientNormals(const Eigen::Vector3d& sensorCenter) {
1691  int pointCloud = m_cloud->size();
1692 
1693  for (int i = 0; i < pointCloud; i++) {
1694  const CCVector3* point = m_cloud->getPoint(i);
1695  Eigen::Vector3d P1 =
1696  sensorCenter - Eigen::Vector3d(point->x, point->y, point->z);
1697  Eigen::Vector3d P2 = m_normals(i, Eigen::all);
1698  double angle = atan2(P1.cross(P2).norm(), P1.dot(P2));
1699  if ((angle < -M_PI / 2) || (angle > M_PI / 2)) {
1700  m_normals(i, 0) = -m_normals(i, 0);
1701  m_normals(i, 1) = -m_normals(i, 1);
1702  m_normals(i, 2) = -m_normals(i, 2);
1703  }
1704  }
1705 }
1706 
1707 bool G3PointAction::findNearestNeighborsNanoFlann(
1708  const unsigned globalIndex,
1710  const KDTree* kdTree) {
1711  // Prepare query
1712  const CCVector3* Q = m_cloud->getPoint(globalIndex);
1713  float query[3] = {Q->x, Q->y, Q->z};
1714 
1715  std::vector<size_t> retIndexes(m_kNN);
1716  std::vector<float> outDistsSqr(m_kNN);
1717 
1718  // Perform search
1719  nanoflann::KNNResultSet<float> resultSet(m_kNN);
1720  resultSet.init(&retIndexes[0], &outDistsSqr[0]);
1721  if (kdTree->findNeighbors(resultSet, &query[0])) {
1722  points->resize(m_kNN);
1723  for (int i = 0; i < m_kNN; ++i) {
1724  points->setPointIndex(i, retIndexes[i]);
1725  }
1726  return true;
1727  } else {
1728  return false;
1729  }
1730 }
1731 
1732 bool G3PointAction::computeNormWithFlann(unsigned index,
1733  NormsTableType* theNorms,
1734  const G3PointAction::KDTree* kdTree) {
1735  CCVector3 N;
1736 
1737  QScopedPointer<cloudViewer::ReferenceCloud> points(
1738  new cloudViewer::ReferenceCloud(m_cloud));
1739  if (findNearestNeighborsNanoFlann(index, points.data(), kdTree)) {
1740  cloudViewer::Neighbourhood neighbourhood(points.data());
1741  N = *neighbourhood.getLSPlaneNormal();
1742  } else {
1743  return false;
1744  }
1745 
1746  theNorms->setValue(index, N);
1747 
1748  return true;
1749 }
1750 
1751 bool G3PointAction::computeNormals() {
1752  // if there are normals, already, propose to keep them
1753  if (m_cloud->hasNormals()) {
1754  QMessageBox msgBox;
1755  msgBox.setInformativeText("Recompute normals?");
1756  msgBox.setText("There are existing normals, keep them or recompute.");
1757  QPushButton* keepButton =
1758  msgBox.addButton(tr("Keep"), QMessageBox::ActionRole);
1759  QPushButton* recomputeButton =
1760  msgBox.addButton(tr("Recompute"), QMessageBox::AcceptRole);
1761  QPushButton* cancelButton =
1762  msgBox.addButton(tr("Cancel"), QMessageBox::AcceptRole);
1763 
1764  msgBox.exec();
1765 
1766  if (msgBox.clickedButton() == keepButton) {
1767  return true;
1768  } else if (msgBox.clickedButton() == cancelButton) {
1769  return false;
1770  }
1771  }
1772 
1773  unsigned pointCount = m_cloud->size();
1774 
1775  if (!m_cloud || m_cloud->size() == 0) {
1776  CVLog::Error("Invalid cloud.");
1777  return false;
1778  }
1779 
1780  CloudAdaptor adaptor(m_cloud);
1781 
1782  // Build KD-tree (parameter: number of leaf nodes to inspect per query)
1783 
1784  size_t leaf_max_size = 10;
1785  nanoflann::KDTreeSingleIndexAdaptorFlags flags =
1787  unsigned int n_thread_build = 0; // 0 => nanoflann automatically determines
1788  // the number of threads to use
1789 
1790  nanoflann::KDTreeSingleIndexAdaptorParams params(leaf_max_size, flags,
1791  n_thread_build);
1792  QSharedPointer<KDTree> m_kdTree(new KDTree(3, adaptor, params));
1793  m_kdTree->buildIndex();
1794 
1795  // we instantiate 3D normal vectors
1796  QSharedPointer<NormsTableType> theNorms(new NormsTableType);
1797  QScopedPointer<NormsIndexesTableType> normsIndexes(
1798  new NormsIndexesTableType);
1799  static const CCVector3 blankN(0, 0, 0);
1800  if (!theNorms->resizeSafe(pointCount, true, &blankN)) {
1801  normsIndexes->resize(0);
1802  return false;
1803  }
1804 
1805  CVLog::Print("[computeNormals]");
1806 #ifdef QT_DEBUG
1807  // manually call the static per-point method!
1808  for (unsigned index = 0; index < pointCount; ++index) {
1809  computeNormWithFlann(index, theNorms.data(), m_kNN, m_kdTree.data(),
1810  m_cloud);
1811  }
1812 #else
1813  std::vector<unsigned> pointsIndexes;
1814  pointsIndexes.resize(pointCount);
1815  for (unsigned i = 0; i < pointCount; ++i) {
1816  pointsIndexes[i] = i;
1817  }
1818  int threadCount = std::max(1, ccQtHelpers::GetMaxThreadCount() - 2);
1819  CVLog::Print(
1820  "[computeNormals] parallel strategy, thread "
1821  "count " +
1822  QString::number(threadCount));
1823  QThreadPool::globalInstance()->setMaxThreadCount(threadCount);
1824  QtConcurrent::blockingMap(pointsIndexes, [=](int index) {
1825  computeNormWithFlann(index, theNorms.data(), m_kdTree.data());
1826  });
1827 #endif
1828 
1829  if (!m_cloud->hasNormals()) {
1830  if (!m_cloud->resizeTheNormsTable()) {
1831  CVLog::Error(QString("Not enough memory to compute normals on "
1832  "cloud '%1'")
1833  .arg(m_cloud->getName()));
1834  return false;
1835  }
1836  }
1837 
1838  // we hide normals during process
1839  m_cloud->showNormals(false);
1840 
1841  // compress the normals
1842  for (unsigned i = 0; i < theNorms->currentSize(); i++) {
1843  const CCVector3& N = theNorms->at(i);
1845  m_cloud->setPointNormalIndex(i, nCode);
1846  }
1847 
1848  // preferred orientation
1849  CVLog::Print("[computeNormals] orient normals, PLUS_Z ");
1852 
1853  return true;
1854 }
1855 
1856 bool G3PointAction::queryNeighbors(ccPointCloud* cloud,
1857  ecvMainAppInterface* appInterface,
1858  bool useParallelStrategy) {
1859  QString errorStr;
1860 
1861  ecvProgressDialog progressDlg(true, appInterface->getMainWindow());
1862  progressDlg.show();
1863  progressDlg.setAutoClose(false); // we don't want the progress dialog to
1864  // 'pop' for each feature
1865  cloudViewer::GenericProgressCallback* progressCb = &progressDlg;
1866  m_octree = m_cloud->getOctree();
1867 
1868  // get the octree
1869  if (!m_octree) {
1870  CVLog::Print(QString("Computing octree of cloud %1 (%2 points)")
1871  .arg(m_cloud->getName())
1872  .arg(m_cloud->size()));
1873  if (progressCb) progressCb->start();
1874  QCoreApplication::processEvents();
1875  m_octree = m_cloud->computeOctree(progressCb);
1876  if (!m_octree) {
1877  errorStr = "Failed to compute octree (not enough memory?)";
1878  return false;
1879  }
1880  }
1881 
1882  m_bestOctreeLevel = m_octree->findBestLevelForAGivenPopulationPerCell(
1883  static_cast<unsigned>(std::max(3, m_kNN)));
1884 
1885  size_t nPoints = m_cloud->size();
1887  std::vector<unsigned> pointsIndexes(nPoints);
1888  std::vector<char> duplicates(nPoints, 0);
1889 
1890  if (useParallelStrategy) {
1891  for (unsigned i = 0; i < nPoints; ++i) {
1892  pointsIndexes[i] = i;
1893  }
1894  int threadCount = std::max(1, ccQtHelpers::GetMaxThreadCount() - 2);
1895  CVLog::Print(
1896  QString("[query_neighbor] parallel strategy, thread count %1")
1897  .arg(threadCount));
1898  QThreadPool::globalInstance()->setMaxThreadCount(threadCount);
1899  QtConcurrent::blockingMap(pointsIndexes, [&](int index) {
1900  getNeighborsDistancesSlopes(index, duplicates);
1901  });
1902  } else {
1903  // manually call the static per-point method!
1904  for (unsigned i = 0; i < nPoints; ++i) {
1905  getNeighborsDistancesSlopes(i, duplicates);
1906  }
1907  }
1908 
1909  auto trueCount = std::count(duplicates.begin(), duplicates.end(), 1);
1910  if (trueCount) {
1911  CVLog::Warning("[G3Point] You have duplicates (" +
1912  QString::number(trueCount) +
1913  "), the algorithm will continue but you may think of "
1914  "cleaning your cloud.");
1915  }
1916 
1917  return true;
1918 }
1919 
1921  init();
1922 
1923  // Find neighbors of each point of the cloud
1924  bool useParallelStrategy;
1925 #ifdef NDEBUG
1926  useParallelStrategy = true;
1927 #else
1928  useParallelStrategy = false;
1929 #endif
1930  queryNeighbors(m_cloud, m_app, useParallelStrategy);
1931 
1932  computeNodeSurfaces();
1933 
1934  computeNormals();
1935 
1936  // compute the centroid
1937  unsigned pointCount = m_cloud->size();
1938  CCVector3d G(0, 0, 0);
1939  {
1940  for (unsigned i = 0; i < pointCount; ++i) {
1941  const CCVector3* P = m_cloud->getPoint(i);
1942  G += *P;
1943  }
1944  G /= pointCount;
1945  }
1946 
1947  Eigen::Vector3d sensorCenter(G.x, G.y, 1000);
1948  orientNormals(sensorCenter);
1949 
1950  // Perform initial segmentation
1951  int nLabels = segmentLabelsBraunWillett();
1952  if (nLabels == -1) {
1953  CVLog::Error(
1954  "[G3Point::segment] initial segmentation failed, abort "
1955  "segmentation");
1956  return;
1957  }
1958 
1959  exportLocalMaximaAsCloud(m_initial_localMaximumIndexes);
1960 
1961  m_app->dispToConsole("[G3Point] initial segmentation: " +
1962  QString::number(nLabels) + " labels",
1964 
1965  m_dlg->enableClusterAndOrClean(true);
1966 
1967  // in case we want to test the fitting of ellipsoids now, without clustering
1968  // nor cleaning
1969  m_labels = m_initial_labels;
1970  m_labelsnpoint = m_initial_labelsnpoint;
1971  m_localMaximumIndexes = m_initial_localMaximumIndexes;
1972  m_stacks = m_initialStacks;
1973 }
1974 
1976  // initialize variables for clustering and / or cleaning to the initial
1977  // segmentation
1978  m_labels = m_initial_labels;
1979  m_labelsnpoint = m_initial_labelsnpoint;
1980  m_localMaximumIndexes = m_initial_localMaximumIndexes;
1981  m_stacks = m_initialStacks;
1982 
1983  if (m_dlg->clusterIsChecked()) {
1984  if (!cluster()) {
1985  CVLog::Error(
1986  "[G3PointAction::clusterAndOrClean] clustering failed");
1987  return;
1988  }
1989  }
1990 
1991  if (m_dlg->cleanIsChecked()) {
1992  if (!cleanLabels()) {
1993  CVLog::Error("[G3PointAction::clusterAndOrClean] cleaning failed");
1994  return;
1995  }
1996  }
1997 }
1998 
2000  // Find the indexborder nodes (no donor and many other labels in the
2001  // neighbourhood)
2002  Eigen::ArrayXXi duplicatedLabelsInColumns(m_cloud->size(), m_kNN);
2003  for (int n = 0; n < m_kNN; n++) {
2004  duplicatedLabelsInColumns(Eigen::all, n) = m_labels;
2005  }
2006  Eigen::ArrayXXi labelsOfNeighbors(m_cloud->size(), m_kNN);
2007  for (int index = 0; index < static_cast<float>(m_cloud->size()); index++) {
2008  for (int n = 0; n < m_kNN; n++) {
2009  labelsOfNeighbors(index, n) =
2010  m_labels(m_neighborsIndexes(index, n));
2011  }
2012  }
2013 
2014  Eigen::ArrayXi temp =
2015  m_kNN - (labelsOfNeighbors == duplicatedLabelsInColumns)
2016  .cast<int>()
2017  .rowwise()
2018  .sum();
2019  auto condition = ((temp >= m_kNN / 4) && (m_ndon == 0));
2020 
2021  // create cloud
2022  cloudViewer::ReferenceCloud referenceCloud(m_cloud);
2023  for (int index = 0; index < condition.size(); index++) {
2024  if (condition(index)) {
2025  referenceCloud.addPointIndex(index);
2026  }
2027  }
2028 
2029  ccPointCloud* borderCloud = m_cloud->partialClone(&referenceCloud);
2030 
2031  // add cloud to the database
2032  borderCloud->setName(m_cloud->getName() + "_borders");
2033  m_cloud->getParent()->addChild(borderCloud, ccHObject::DP_PARENT_OF_OTHER,
2034  0);
2035  m_app->addToDB(borderCloud);
2036 }
2037 
2038 void G3PointAction::init() {
2039  // initialize the matrices which will contain the results
2040  m_neighborsIndexes = Eigen::ArrayXXi::Zero(m_cloud->size(), m_kNN);
2041  m_neighborsDistances = Eigen::ArrayXXd::Zero(m_cloud->size(), m_kNN);
2042  m_neighborsSlopes = Eigen::ArrayXXd::Zero(m_cloud->size(), m_kNN);
2043  m_normals = Eigen::ArrayXXd::Zero(m_cloud->size(), 3);
2044 
2045  m_labels = Eigen::ArrayXi::Zero(m_cloud->size());
2046  m_labelsnpoint = Eigen::ArrayXi::Zero(m_cloud->size());
2047  m_initial_labels = Eigen::ArrayXi::Zero(m_cloud->size());
2048  m_initial_labelsnpoint = Eigen::ArrayXi::Zero(m_cloud->size());
2049 
2050  m_stacks.clear(); // needed in case of several runs
2051  m_initialStacks.clear(); // needed in case of several runs
2052 }
2053 
2054 void G3PointAction::showDlg() {
2055  if (!m_dlg) {
2056  m_dlg = new G3PointDialog(m_cloud->getName());
2057 
2058  connect(m_dlg, &G3PointDialog::kNNEditingFinished,
2059  s_g3PointAction.get(), &G3PointAction::setKNN);
2060  connect(m_dlg, &G3PointDialog::segment, s_g3PointAction.get(),
2062  connect(m_dlg, &G3PointDialog::clusterAndOrClean, s_g3PointAction.get(),
2064  connect(m_dlg, &G3PointDialog::getBorders, s_g3PointAction.get(),
2066 
2067  connect(m_dlg, &G3PointDialog::fit, s_g3PointAction.get(),
2069  connect(m_dlg, &G3PointDialog::exportResults, s_g3PointAction.get(),
2071  connect(m_dlg, &G3PointDialog::wolman, s_g3PointAction.get(),
2073  connect(m_dlg, &G3PointDialog::angles, s_g3PointAction.get(),
2075 
2076  connect(m_dlg, &QDialog::finished, s_g3PointAction.get(),
2078  connect(m_dlg, &QDialog::finished, s_g3PointAction.get(),
2079  &G3Point::G3PointAction::resetDlg); // dialog is defined with
2080  // Qt::WA_DeleteOnClose
2081  } else {
2082  m_dlg->enableClusterAndOrClean(false);
2083  }
2084 
2085  m_dlg->show();
2086 }
2087 
2088 void G3PointAction::resetDlg() {
2089  // dialog is defined with Qt::WA_DeleteOnClose, you have to reset the
2090  // pointer when it's closed by the user
2091  m_dlg = nullptr;
2092 }
2093 
2095  m_neighborsIndexes.resize(0, 0);
2096  m_neighborsDistances.resize(0, 0);
2097  m_neighborsSlopes.resize(0, 0);
2098  m_normals.resize(0, 0);
2099 
2100  m_labels.resize(0);
2101  m_labelsnpoint.resize(0);
2102  m_localMaximumIndexes.resize(0);
2103  m_ndon.resize(0);
2104  m_area.resize(0);
2105 
2106  m_stacks.clear();
2107 
2108  m_octree.clear();
2109 }
2110 
2111 bool G3PointAction::setCloud(ccPointCloud* cloud) {
2112  m_cloud = cloud;
2113 
2114  // at this step, it is possible to initialize the stacks if the cloud has a
2115  // g3point_label scalar field
2116 
2117  int sfIdx = m_cloud->getScalarFieldIndexByName("g3point_label");
2118  if (sfIdx != -1) {
2119  init();
2120 
2121  // copy the g3point_label scalar field
2122  int sfIdxBackup =
2123  m_cloud->getScalarFieldIndexByName("g3point_label_backup");
2124  if (sfIdxBackup == -1) // the scalar field g3point_label_backup does
2125  // not exist, create it
2126  {
2127  sfIdxBackup = m_cloud->addScalarField("g3point_label_backup");
2128  if (sfIdxBackup == -1) {
2129  CVLog::Error(
2130  "[G3PointAction::setCloud] impossible to create scalar "
2131  "field g3point_label_backup");
2132  return false;
2133  } else {
2135  "[G3PointAction::setCloud] duplicate existing scalar "
2136  "field g3point_label => g3point_label_backup");
2137  }
2138  }
2139 
2140  cloudViewer::ScalarField* g3PointLabelSF =
2141  m_cloud->getScalarField(sfIdx);
2142  cloudViewer::ScalarField* g3PointLabelBackupSF =
2143  m_cloud->getScalarField(sfIdxBackup);
2144 
2145  // get the set of labels
2146  std::set<ScalarType> labelsSet;
2147  for (unsigned int index = 0; index < m_cloud->size(); index++) {
2148  labelsSet.insert(g3PointLabelSF->getValue(index));
2149  }
2150  // remove -1 from the set
2151  labelsSet.erase(-1);
2152  // build a map to handle cases were g3point_label is not a regular range
2153  // with step 1 keys = labels values = index
2154  std::map<ScalarType, int> labelsMap;
2155  int index = 0;
2156  for (auto label : labelsSet) {
2157  labelsMap[label] = index;
2158  index++;
2159  }
2160 
2161  // build stacks from g3point_index
2162  int nPointsInGrains = 0;
2163 
2164  ecvProgressDialog pDlg(true, m_app->getMainWindow());
2165  pDlg.setMethodTitle("Processing g3point_label");
2166  pDlg.setInfo("Classifying points...");
2167  pDlg.setMaximum(m_cloud->size());
2168  pDlg.show();
2169  QApplication::processEvents();
2170 
2171  std::vector<std::vector<int>> newStacks(labelsSet.size());
2172 
2173  // Optimize: reduce processEvents calls
2174  // Update progress every 1% or at least every 10000 points
2175  unsigned updateStep = std::max<unsigned>(10000, m_cloud->size() / 100);
2176 
2177  for (unsigned int index = 0; index < m_cloud->size(); index++) {
2178  ScalarType label = g3PointLabelSF->getValue(index);
2179  g3PointLabelBackupSF->setValue(
2180  index, label); // save the originale G3Point label
2181  if (label != -1) // -1 is a generic index corresponding to all
2182  // points which do not belong to valid grains
2183  {
2184  newStacks[labelsMap[label]].push_back(index);
2185  nPointsInGrains++;
2186  }
2187 
2188  if (index % updateStep == 0) {
2189  if (pDlg.wasCanceled()) {
2190  return false;
2191  }
2192  pDlg.setValue(index);
2193  }
2194  }
2195 
2196  CVLog::Print(
2197  "[G3PointAction::setCloud] g3point_label available, found " +
2198  QString::number(nPointsInGrains) + "/" +
2199  QString::number(cloud->size()) + " points belonging to " +
2200  QString::number(newStacks.size()) + " grains");
2201 
2202  processNewStacks(newStacks, nPointsInGrains);
2203  }
2204 
2205  return true;
2206 }
2207 
2208 void G3PointAction::setKNN() { m_kNN = m_dlg->getkNN(); }
2209 
2211  if (appInterface == nullptr) {
2212  // The application interface should have already been initialized when
2213  // the plugin is loaded
2214  Q_ASSERT(false);
2215 
2216  return;
2217  }
2218 
2219  // we need one point cloud
2220  if (!appInterface->haveOneSelection()) {
2221  appInterface->dispToConsole("Select only one cloud!",
2223  return;
2224  }
2225 
2226  // a real point cloud
2227  const ccHObject::Container& selectedEntities =
2228  appInterface->getSelectedEntities();
2229 
2230  ccHObject* ent = selectedEntities[0];
2231  if (!ent->isA(CV_TYPES::POINT_CLOUD)) {
2232  appInterface->dispToConsole("Select a cloud!",
2234  return;
2235  }
2236 
2237  GetG3PointAction(ccHObjectCaster::ToPointCloud(ent), appInterface);
2238 }
2239 
2240 } // namespace G3Point
CV_LOCAL_MODEL_TYPES
Definition: CVConst.h:121
@ LS
Definition: CVConst.h:123
@ QUADRIC
Definition: CVConst.h:125
constexpr double M_PI
Pi.
Definition: CVConst.h:19
std::string name
int count
int points
cmdLineReadable * params[]
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
double getMaxAngle1()
void enableClusterAndOrClean(bool state)
bool isSteepestSlope()
void exportResults()
void onlyOneClicked(bool state)
void drawPoints(bool state)
void kNNEditingFinished()
void onlyOneChanged(int idx)
void clusterAndOrClean()
void allClicked(bool state)
void setOnlyOneMax(int idx)
void drawLines(bool state)
double getMinFlatness()
void transparencyChanged(double transparency)
void glPointSize(int size)
double getMaxAngle2()
double getRadiusFactor()
void drawSurfaces(bool state)
bool clusterIsChecked()
void getBorders()
static bool show(ecvMainAppInterface *app)
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< float, CloudAdaptor >, CloudAdaptor, 3 > KDTree
Definition: G3PointAction.h:96
static bool EigenArrayToFile(QString name, T array)
bool buildStacksFromG3PointLabelSF(cloudViewer::ScalarField *g3PointLabel)
void showWolman(const Eigen::ArrayXf &d_sample, const Eigen::Array3d &dq_final, const Eigen::Array3d &edq)
bool keep(Xb &condition)
bool merge(XXb &condition)
G3PointAction(ccPointCloud *cloud, ecvMainAppInterface *app=nullptr)
static void GetG3PointAction(ccPointCloud *cloud, ecvMainAppInterface *app=nullptr)
bool processNewStacks(std::vector< std::vector< int >> &newStacks, int pointCount)
static void createAction(ecvMainAppInterface *appInterface)
std::vector< Eigen::Array3f > m_center
std::vector< Eigen::Array3f > m_radii
void showOnlyOne(bool state)
void drawPoints(bool state)
void setGLPointSize(int size)
void showAll(bool state)
void drawSurfaces(bool state)
void drawLines(bool state)
ccPointCloud * m_cloud
std::set< int > m_fitNotOK
void setTransparency(double transparency)
std::vector< Eigen::Matrix3f > m_rotationMatrix
Array of compressed 3D normals (single index)
Array of (uncompressed) 3D normals (Nx,Ny,Nz)
Array of RGBA colors for each point.
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 resizeSafe(size_t count, bool initNewElements=false, const Type *valueForNewElements=nullptr)
Resizes memory (no exception thrown)
Definition: ecvArray.h:70
bool reserveSafe(size_t count)
Reserves memory (no exception thrown)
Definition: ecvArray.h:56
void setValue(size_t index, const Type &value)
Definition: ecvArray.h:102
unsigned currentSize() const
Definition: ecvArray.h:112
void addElement(const Type &value)
Definition: ecvArray.h:105
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.
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.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
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.
@ DP_PARENT_OF_OTHER
Definition: ecvHObject.h:266
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
virtual void redrawDisplay(bool forceRedraw=true, bool only2D=false)
Redraws associated display.
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
static bool UpdateNormalOrientations(ccGenericPointCloud *theCloud, NormsIndexesTableType &theNormsCodes, Orientation preferredOrientation)
Updates normals orientation based on a preferred orientation.
Orientation
'Default' orientations
@ PLUS_Z
N.z always positive.
static CompressedNormType GetNormIndex(const PointCoordinateType N[])
Returns the compressed index corresponding to a normal vector.
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
static PointCoordinateType GuessBestRadius(ccGenericPointCloud *cloud, const BestRadiusParams &params, cloudViewer::DgmOctree *cloudOctree=nullptr, cloudViewer::GenericProgressCallback *progressCb=nullptr)
Tries to guess the best 'local radius' for octree-based computation.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
bool resizeTheNormsTable()
Resizes the compressed normals array.
void setPointNormalIndex(size_t pointIndex, CompressedNormType norm)
Sets a particular point compressed normal.
NormsIndexesTableType * normals() const
Returns pointer on compressed normals indexes table.
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 resizeTheRGBTable(bool fillWithWhite=false)
Resizes the RGB colors array.
bool computeNormalsWithOctree(CV_LOCAL_MODEL_TYPES model, ccNormalVectors::Orientation preferredOrientation, PointCoordinateType defaultRadius, ecvProgressDialog *pDlg=nullptr)
Compute the normals by approximating the local surface around each point.
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)
ccPointCloud * partialClone(const cloudViewer::ReferenceCloud *selection, int *warnings=nullptr, bool withChildEntities=true) const
Creates a new point cloud object from a ReferenceCloud (selection)
static int GetMaxThreadCount()
Returns the ideal number of threads/cores with Qt Concurrent.
Definition: ecvQtHelpers.h:39
A scalar field associated to display-related parameters.
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.
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
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
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
unsigned currentSize() const
Definition: ScalarField.h:100
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
RGB color structure.
Definition: ecvColorTypes.h:49
RGBA color structure.
Main application interface (for plugins)
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 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 removeFromDB(ccHObject *obj, bool autoDelete=true)=0
Removes an entity from main db tree.
Graphical progress indicator (thread-safe)
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
int max(int a, int b)
Definition: cutil_math.h:48
unsigned int CompressedNormType
Compressed normals type.
Definition: ecvBasicTypes.h:16
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
@ POINT_CLOUD
Definition: CVTypes.h:104
T1::value_type quant(T1 &x, const T2 &q)
double std_dev(const T &vec)
Eigen::VectorXf arange(double start, double stop, double step)
void myPrint(QString name, T array)
RGBAColorsTableType getRandomColors(size_t randomColorsNumber)
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
constexpr utility::nullopt_t None
Definition: TensorKey.h:20
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
flann::Index< flann::L2< float > > KDTree
struct Index Index
Definition: sqlite3.c:14646
Parameters for the GuessBestRadius method.
Definition: ecvOctree.h:122
Container of in/out parameters for nearest neighbour(s) search.
Definition: DgmOctree.h:161
unsigned char level
Level of subdivision of the octree at which to start the search.
Definition: DgmOctree.h:171
Definition: lsd.c:149
int y
Definition: lsd.c:149
int x
Definition: lsd.c:149