ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qColorimetricSegmenter.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 
9 
10 #include "HSV.h"
11 #include "HSVDialog.h"
12 #include "KmeansDlg.h"
13 #include "QuantiDialog.h"
14 #include "RgbDialog.h"
15 #include "ScalarDialog.h"
16 
17 // ACloudViewer
18 #include <CVLog.h>
19 #include <ecvPointCloud.h>
20 
21 // cloudViewer
23 
24 // System
25 #include <algorithm>
26 #include <map>
27 
28 // Qt
29 #include <QMainWindow>
30 
31 static void ShowDurationNow(
32  const std::chrono::high_resolution_clock::time_point& startTime) {
33  auto stopTime = std::chrono::high_resolution_clock::now();
34  auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
35  stopTime - startTime)
36  .count();
37 
38  // Print duration of execution
39  CVLog::Print("Time to execute: " + QString::number(duration_ms) +
40  " milliseconds");
41 }
42 
43 static inline bool Inside(ColorCompType lower,
44  ColorCompType value,
45  ColorCompType upper) {
46  Q_ASSERT(lower <= upper);
47  return (value >= lower && value <= upper);
48 }
49 
50 ColorimetricSegmenter::ColorimetricSegmenter(QObject* parent)
51  : QObject(parent),
52  ccStdPluginInterface(":/CC/plugin/ColorimetricSegmenter/info.json"),
53  m_action_filterRgb(nullptr)
54  /*, m_action_filterRgbWithSegmentation(nullptr)*/
55  ,
56  m_action_filterHSV(nullptr),
57  m_action_filterScalar(nullptr),
58  m_action_histogramClustering(nullptr),
59  m_action_kMeansClustering(nullptr),
60  m_addPointError(false) {}
61 
62 void ColorimetricSegmenter::onNewSelection(
63  const ccHObject::Container& selectedEntities) {
64  // Only enable our action if something is selected.
65  bool activateColorFilters = false;
66  bool activateScalarFilter = false;
67  for (ccHObject* entity : selectedEntities) {
68  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
69  if (entity->hasColors()) {
70  activateColorFilters = true;
71  } else if (entity->hasDisplayedScalarField()) {
72  activateScalarFilter = true;
73  }
74  }
75  }
76 
77  // Activate only if only one of them is activated
78  if (activateColorFilters && activateScalarFilter) {
79  activateColorFilters = activateScalarFilter = false;
80  }
81 
82  if (m_action_filterRgb)
83  m_action_filterRgb->setEnabled(activateColorFilters);
84  if (m_action_filterHSV)
85  m_action_filterHSV->setEnabled(activateColorFilters);
86  // if (m_action_filterRgbWithSegmentation)
87  // m_action_filterRgbWithSegmentation->setEnabled(activateColorFilters);
88  if (m_action_filterScalar)
89  m_action_filterScalar->setEnabled(activateScalarFilter);
90  if (m_action_histogramClustering)
91  m_action_histogramClustering->setEnabled(activateColorFilters);
92  if (m_action_kMeansClustering)
93  m_action_kMeansClustering->setEnabled(activateColorFilters);
94 }
95 
96 QList<QAction*> ColorimetricSegmenter::getActions() {
97  // RGB Filter
98  if (!m_action_filterRgb) {
99  m_action_filterRgb = new QAction("Filter RGB", this);
100  m_action_filterRgb->setToolTip(
101  "Filter the points of the selected cloud by RGB color");
102  m_action_filterRgb->setIcon(
103  QIcon(":/CC/plugin/ColorimetricSegmenter/images/icon_rgb.png"));
104 
105  // Connect appropriate signal
106  connect(m_action_filterRgb, &QAction::triggered, this,
107  &ColorimetricSegmenter::filterRgb);
108  }
109 
110  /*if (!m_action_filterRgbWithSegmentation)
111  {
112  // Here we use the default plugin name, description, and icon,
113  // but each action should have its own.
114  m_action_filterRgbWithSegmentation = new QAction("Filter RGB using
115  segmentation", this); m_action_filterRgbWithSegmentation->setToolTip("Filter
116  the points on the selected cloud by RGB color using segmentation");
117  m_action_filterRgbWithSegmentation->setIcon(getIcon());
118 
119  // Connect appropriate signal
120  connect(m_action_filterRgbWithSegmentation, &QAction::triggered,
121  this, &ColorimetricSegmenter::filterRgbWithSegmentation);
122  }*/
123 
124  // HSV Filter
125  if (!m_action_filterHSV) {
126  m_action_filterHSV = new QAction("Filter HSV", this);
127  m_action_filterHSV->setToolTip(
128  "Filter the points of the selected cloud by HSV color");
129  m_action_filterHSV->setIcon(
130  QIcon(":/CC/plugin/ColorimetricSegmenter/images/icon_hsv.png"));
131 
132  // Connect appropriate signal
133  connect(m_action_filterHSV, &QAction::triggered, this,
134  &ColorimetricSegmenter::filterHSV);
135  }
136 
137  // Scalar filter
138  if (!m_action_filterScalar) {
139  m_action_filterScalar = new QAction("Filter scalar", this);
140  m_action_filterScalar->setToolTip(
141  "Filter the points of the selected cloud using scalar field");
142  m_action_filterScalar->setIcon(QIcon(
143  ":/CC/plugin/ColorimetricSegmenter/images/icon_scalar.png"));
144 
145  // Connect appropriate signal
146  connect(m_action_filterScalar, &QAction::triggered, this,
147  &ColorimetricSegmenter::filterScalar);
148  }
149 
150  if (!m_action_histogramClustering) {
151  m_action_histogramClustering =
152  new QAction("Histogram Clustering", this);
153  m_action_histogramClustering->setToolTip(
154  "Quantify the number of colors using Histogram Clustering");
155  m_action_histogramClustering->setIcon(QIcon(
156  ":/CC/plugin/ColorimetricSegmenter/images/icon_quantif_h.png"));
157 
158  // Connect appropriate signal
159  connect(m_action_histogramClustering, &QAction::triggered, this,
160  &ColorimetricSegmenter::HistogramClustering);
161  }
162 
163  if (!m_action_kMeansClustering) {
164  // Here we use the default plugin name, description, and icon,
165  // but each action should have its own.
166  m_action_kMeansClustering = new QAction("Kmeans Clustering", this);
167  m_action_kMeansClustering->setToolTip(
168  "Quantify the number of colors using Kmeans Clustering");
169  m_action_kMeansClustering->setIcon(QIcon(
170  ":/CC/plugin/ColorimetricSegmenter/images/icon_quantif_k.png"));
171 
172  // Connect appropriate signal
173  connect(m_action_kMeansClustering, &QAction::triggered, this,
174  &ColorimetricSegmenter::KmeansClustering);
175  }
176 
177  return {m_action_filterRgb, m_action_filterHSV,
178  // m_action_filterRgbWithSegmentation,
179  m_action_filterScalar, m_action_histogramClustering,
180  m_action_kMeansClustering};
181 }
182 
183 // Get all point clouds that are selected in CC
184 // return a vector with ccPointCloud objects
185 std::vector<ccPointCloud*> ColorimetricSegmenter::getSelectedPointClouds() {
186  if (m_app == nullptr) {
187  // m_app should have already been initialized by CC when plugin is
188  // loaded
189  Q_ASSERT(false);
190  return std::vector<ccPointCloud*>{};
191  }
192 
193  ccHObject::Container selectedEntities = m_app->getSelectedEntities();
194  std::vector<ccPointCloud*> clouds;
195  for (size_t i = 0; i < selectedEntities.size(); ++i) {
196  if (selectedEntities[i]->isKindOf(CV_TYPES::POINT_CLOUD)) {
197  clouds.push_back(static_cast<ccPointCloud*>(selectedEntities[i]));
198  }
199  }
200 
201  return clouds;
202 }
203 
204 // Algorithm for the RGB filter
205 // It uses a color range with RGB values, and keeps the points with a color
206 // within that range.
207 void ColorimetricSegmenter::filterRgb() {
208  if (m_app == nullptr) {
209  // m_app should have already been initialized by CC when plugin is
210  // loaded
211  Q_ASSERT(false);
212 
213  return;
214  }
215 
216  // check valid window
217  if (!m_app->getActiveWindow()) {
218  m_app->dispToConsole("[ColorimetricSegmenter] No active 3D view",
220  return;
221  }
222 
223  std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
224  if (clouds.empty()) {
225  Q_ASSERT(false);
226  return;
227  }
228 
229  // Retrieve parameters from dialog
231 
232  rgbDlg.show(); // necessary for setModal to be retained
233 
234  if (!rgbDlg.exec()) return;
235 
236  // Start timer
237  auto startTime = std::chrono::high_resolution_clock::now();
238 
239  // Get all values to make the color range with RGB values
240  int redInf = static_cast<int>(
241  std::min(rgbDlg.red_first->value(), rgbDlg.red_second->value()));
242  int redSup = static_cast<int>(
243  std::max(rgbDlg.red_first->value(), rgbDlg.red_second->value()));
244  int greenInf = static_cast<int>(std::min(rgbDlg.green_first->value(),
245  rgbDlg.green_second->value()));
246  int greenSup = static_cast<int>(std::max(rgbDlg.green_first->value(),
247  rgbDlg.green_second->value()));
248  int blueInf = static_cast<int>(
249  std::min(rgbDlg.blue_first->value(), rgbDlg.blue_second->value()));
250  int blueSup = static_cast<int>(
251  std::max(rgbDlg.blue_first->value(), rgbDlg.blue_second->value()));
252 
253  if (rgbDlg.margin->value() > 0) {
254  // error margin
255  int marginError =
256  static_cast<int>(rgbDlg.margin->value() * 2.56); // 256 / 100%
257 
258  redInf -= marginError;
259  redSup += marginError;
260  greenInf -= marginError;
261  greenSup += marginError;
262  blueInf -= marginError;
263  blueSup += marginError;
264  }
265 
266  // Set to min or max value (0-255)
267  {
268  redInf = std::max(redInf, 0);
269  greenInf = std::max(greenInf, 0);
270  blueInf = std::max(blueInf, 0);
271 
272  redSup = std::min(redSup, 255);
273  greenSup = std::min(greenSup, 255);
274  blueSup = std::min(blueSup, 255);
275  }
276 
277  for (ccPointCloud* cloud : clouds) {
278  if (cloud && cloud->hasColors()) {
279  // Use only references for speed reasons
280  cloudViewer::ReferenceCloud filteredCloudInside(cloud);
281  cloudViewer::ReferenceCloud filteredCloudOutside(cloud);
282 
283  for (unsigned j = 0; j < cloud->size(); ++j) {
284  const ecvColor::Rgb& rgb = cloud->getPointColor(j);
285  if (Inside(static_cast<ColorCompType>(redInf), rgb.r,
286  static_cast<ColorCompType>(redSup)) &&
287  Inside(static_cast<ColorCompType>(greenInf), rgb.g,
288  static_cast<ColorCompType>(greenSup)) &&
289  Inside(static_cast<ColorCompType>(blueInf), rgb.b,
290  static_cast<ColorCompType>(blueSup))) {
291  addPoint(filteredCloudInside, j);
292  } else {
293  addPoint(filteredCloudOutside, j);
294  }
295 
296  if (m_addPointError) {
297  return;
298  }
299  }
300  QString name = "Rmin:" + QString::number(redInf) +
301  "/Gmin:" + QString::number(greenInf) +
302  "/Bmin:" + QString::number(blueInf) +
303  "/Rmax:" + QString::number(redSup) +
304  "/Gmax:" + QString::number(greenSup) +
305  "/Bmax:" + QString::number(blueSup);
306 
307  createClouds<const RgbDialog&>(rgbDlg, cloud, filteredCloudInside,
308  filteredCloudOutside, name);
309 
311  "[ColorimetricSegmenter] Cloud successfully filtered ! ",
313  }
314  }
315 
316  ShowDurationNow(startTime);
317 }
318 
319 void ColorimetricSegmenter::filterScalar() {
320  if (m_app == nullptr) {
321  // m_app should have already been initialized by CC when plugin is
322  // loaded
323  Q_ASSERT(false);
324 
325  return;
326  }
327 
328  // check valid window
329  if (!m_app->getActiveWindow()) {
330  m_app->dispToConsole("[ColorimetricSegmenter] No active 3D view",
332  return;
333  }
334 
335  // Retrieve parameters from dialog
336  ScalarDialog scalarDlg(m_app->pickingHub(), m_app->getMainWindow());
337 
338  scalarDlg.show(); // necessary for setModal to be retained
339 
340  if (!scalarDlg.exec()) return;
341 
342  // Start timer
343  auto startTime = std::chrono::high_resolution_clock::now();
344 
345  ScalarType marginError = scalarDlg.margin->value() / 100.0f;
346  ScalarType minVal = static_cast<ScalarType>(
347  std::min(scalarDlg.first->value(), scalarDlg.second->value()));
348  ScalarType maxVal = static_cast<ScalarType>(
349  std::max(scalarDlg.first->value(), scalarDlg.second->value()));
350  // DGM: this way of applying the error margin is a bit strange
351  minVal -= (marginError * minVal);
352  maxVal += (marginError * maxVal);
353 
354  std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
355  for (ccPointCloud* cloud : clouds) {
356  // Use only references for speed reasons
357  cloudViewer::ReferenceCloud filteredCloudInside(cloud);
358  cloudViewer::ReferenceCloud filteredCloudOutside(cloud);
359 
360  for (unsigned j = 0; j < cloud->size(); ++j) {
361  const ScalarType val = cloud->getPointScalarValue(j);
362  addPoint(val >= minVal && val <= maxVal ? filteredCloudInside
363  : filteredCloudOutside,
364  j);
365 
366  if (m_addPointError) {
367  return;
368  }
369  }
370  QString name = "min:" + QString::number(minVal) +
371  "/max:" + QString::number(maxVal);
372 
373  createClouds<const ScalarDialog&>(scalarDlg, cloud, filteredCloudInside,
374  filteredCloudOutside, name);
375 
377  "[ColorimetricSegmenter] Cloud successfully filtered ! ",
379  }
380 
381  ShowDurationNow(startTime);
382 }
383 
384 typedef QSharedPointer<cloudViewer::ReferenceCloud> _Region;
385 typedef std::vector<_Region> _RegionSet;
386 typedef std::vector<_RegionSet> SetOfRegionSet;
387 
397 static bool KNNRegions(ccPointCloud* basePointCloud,
398  const _RegionSet& regions,
399  const _Region& region,
400  unsigned k,
401  _RegionSet& neighbourRegions,
402  unsigned thresholdDistance) {
403  QScopedPointer<ccPointCloud> regionCloud(
404  basePointCloud->partialClone(region.data()));
405  if (!regionCloud) {
406  // not enough memory
407  return false;
408  }
409 
410  // compute distances
412  params = cloudViewer::DistanceComputationTools::
413  Cloud2CloudDistancesComputationParams();
414  {
415  params.kNNForLocalModel = k;
416  params.maxSearchDist = thresholdDistance;
417  }
418 
419  std::vector<double> distancesToCentralRegion;
420  distancesToCentralRegion.reserve(regions.size());
421 
422  for (const _Region& r : regions) {
423  QScopedPointer<ccPointCloud> neighbourCloud(
424  basePointCloud->partialClone(r.data()));
425  if (!neighbourCloud) {
426  // not enough memory
427  return false;
428  }
429  // DGM: warning, the computeCloud2CloudDistances method doesn't return a
430  // distance value (but a status / error) distances are stored in the
431  // active scalar field (one per point!)
433  computeCloud2CloudDistances(neighbourCloud.data(),
434  regionCloud.data(), params);
435  if (result >= 0) {
436  double meanDistance = 0.0;
437  for (unsigned i = 0; i < neighbourCloud->size(); ++i) {
438  meanDistance += static_cast<double>(
439  neighbourCloud->getPointScalarValue(i));
440  }
441  meanDistance /= neighbourCloud->size();
442 
443  distancesToCentralRegion.push_back(meanDistance);
444  } else {
445  // failed to compute the distances
446  return false;
447  }
448  }
449  regionCloud.reset(nullptr);
450 
451  // sort the regions by their distance
452  std::vector<size_t> regionIndices(regions.size());
453  for (size_t i = 0; i < regions.size(); ++i) regionIndices[i] = i;
454 
455  std::sort(regionIndices.begin(), regionIndices.end(),
456  [&](size_t a, size_t b) {
457  return distancesToCentralRegion[a] <
458  distancesToCentralRegion[b];
459  });
460 
461  // then extract the 'k' nearest regions.
462  for (size_t regionIndex : regionIndices) {
463  if (neighbourRegions.size() < k) {
464  neighbourRegions.push_back(regions[regionIndex]);
465  } else {
466  break;
467  }
468  }
469 
470  return true;
471 }
472 
481  int dr = static_cast<int>(c1.r) - c2.r;
482  int dg = static_cast<int>(c1.g) - c2.g;
483  int db = static_cast<int>(c1.b) - c2.b;
484  return sqrt(static_cast<double>(dr * dr + dg * dg + db * db));
485 }
486 
494  cloudViewer::ReferenceCloud* subset) {
495  if (!subset || subset->size() == 0) {
496  Q_ASSERT(false);
497  return ecvColor::white;
498  }
499 
500  size_t count = subset->size();
501  if (count == 0) {
502  return ecvColor::white;
503  } else if (count == 1) {
504  return cloud.getPointColor(subset->getPointGlobalIndex(0));
505  }
506 
507  // other formula to compute the average can be used
508  size_t redSum = 0, greenSum = 0, blueSum = 0;
509  for (unsigned j = 0; j < subset->size(); ++j) {
510  const ecvColor::Rgb& rgb =
511  cloud.getPointColor(subset->getPointGlobalIndex(j));
512  redSum += rgb.r;
513  greenSum += rgb.g;
514  blueSum += rgb.b;
515  }
516 
517  ecvColor::Rgb res(
518  static_cast<ColorCompType>(std::min(
519  redSum / count, static_cast<size_t>(ecvColor::MAX))),
520  static_cast<ColorCompType>(std::min(
521  greenSum / count, static_cast<size_t>(ecvColor::MAX))),
522  static_cast<ColorCompType>(std::min(
523  blueSum / count, static_cast<size_t>(ecvColor::MAX))));
524 
525  return res;
526 }
527 
537 double ColorimetricalDifference(const ccPointCloud& basePointCloud,
540  ecvColor::Rgb rgb1 = ComputeAverageColor(basePointCloud, c1);
541  ecvColor::Rgb rgb2 = ComputeAverageColor(basePointCloud, c2);
542 
543  return ColorimetricalDifference(rgb1, rgb2);
544 }
545 
546 bool ColorimetricSegmenter::RegionGrowing(RegionSet& regions,
547  ccPointCloud* pointCloud,
548  const unsigned TNN,
549  const double TPP,
550  const double TD) {
551  if (!pointCloud || pointCloud->size() == 0) {
552  Q_ASSERT(false);
553  return false;
554  }
555  size_t pointCount = pointCloud->size();
556 
557  try {
558  std::vector<unsigned> unlabeledPoints;
559  unlabeledPoints.resize(pointCount);
560  for (unsigned j = 0; j < pointCount; ++j) {
561  unlabeledPoints.push_back(j);
562  }
563 
564  std::vector<unsigned> pointIndices;
565 
567  pointCloud); // used to search nearest neighbors
568  octree->build();
569 
570  // while there is points in {P} that haven’t been labeled
571  while (!unlabeledPoints.empty()) {
572  // push an unlabeled point into stack Points
573  pointIndices.push_back(unlabeledPoints.back());
574  unlabeledPoints.pop_back();
575 
576  // initialize a new region Rc and add current point to R
577  Region rc(new cloudViewer::ReferenceCloud(pointCloud));
578  rc->addPointIndex(unlabeledPoints.back());
579 
580  // while stack Points is not empty
581  while (!pointIndices.empty()) {
582  // pop Points’ top element Tpoint
583  unsigned tPointIndex = pointIndices.back();
584  pointIndices.pop_back();
585 
586  // for each point p in {KNNTNN(Tpoint)}
589  {
590  nNSS.level = 1;
591  nNSS.queryPoint = *(pointCloud->getPoint(tPointIndex));
592  octree->getCellPos(octree->getCellCode(tPointIndex), 1,
593  nNSS.cellPos, false);
595  1, nNSS.cellCenter);
596  nNSS.maxSearchSquareDistd = TD;
597  nNSS.minNumberOfNeighbors = TNN;
598  }
600 
601  for (std::size_t i = 0; i < nNSS.pointsInNeighbourhood.size();
602  i++) {
603  unsigned p = nNSS.pointsInNeighbourhood[i].pointIndex;
604  // if p is labelled
605  if (std::find(unlabeledPoints.begin(),
606  unlabeledPoints.end(),
607  p) != unlabeledPoints.end()) {
608  continue;
609  }
610 
612  pointCloud->getPointColor(p),
613  pointCloud->getPointColor(tPointIndex)) < TPP) {
614  pointIndices.push_back(p);
615  rc->addPointIndex(p);
616  }
617  }
618  }
619  regions.push_back(rc);
620  }
621  } catch (const std::bad_alloc&) {
622  // not enough memory
623  return false;
624  }
625 
626  return true;
627 }
628 
635 static int FindRegion(const std::vector<_RegionSet>& container,
636  cloudViewer::ReferenceCloud* region) {
637  for (size_t i = 0; i < container.size(); ++i) {
638  const _RegionSet& l = container[i];
639  if (std::find(l.begin(), l.end(), region) != l.end()) {
640  return static_cast<int>(i);
641  }
642  }
643  return -1;
644 }
645 
646 bool ColorimetricSegmenter::RegionMergingAndRefinement(
647  RegionSet& mergedRegions,
648  ccPointCloud* basePointCloud,
649  const RegionSet& regions,
650  const unsigned TNN,
651  const double TRR,
652  const double TD,
653  const unsigned Min) {
654  std::vector<_RegionSet> homogeneous;
655 
656  // for each region Ri in {R}
657  for (const Region& ri : regions) {
658  // if Ri is not in {H}
659  _RegionSet* riSet = nullptr;
660  int riSetIndex = FindRegion(homogeneous, ri.data());
661  if (riSetIndex == -1) {
662  // create a new list to record Ri
663  homogeneous.resize(homogeneous.size() + 1);
664  riSet = &homogeneous.back();
665  riSet->push_back(ri);
666  } else {
667  riSet = &(homogeneous[riSetIndex]);
668  }
669 
670  // for each region Rj in {KNNTNN2,TD2(Ri)}
671  RegionSet knnResult;
672  if (!KNNRegions(basePointCloud, regions, ri, TNN, knnResult, TD)) {
673  // process failed
674  return false;
675  }
676 
677  for (const Region& rj : knnResult) {
678  // if CD(Ri,Rj)<TRR
679  if (ColorimetricalDifference(*basePointCloud, ri.data(),
680  rj.data()) < TNN) {
681  // if Rj is in {H}
682  int regionIndex = FindRegion(homogeneous, rj.data());
683  if (regionIndex < 0) {
684  // add Rj to the list which contains Ri
685  riSet->push_back(rj);
686  }
687  }
688  }
689  }
690 
691  // merge all the regions in the same list in {H} and get {R’}
692  for (const _RegionSet& l : homogeneous) {
693  Region merged(
694  new cloudViewer::ReferenceCloud(l[0]->getAssociatedCloud()));
695  for (const Region& li : l) {
696  if (li && !merged->add(*li)) {
697  // not enough memory
698  return false;
699  }
700  }
701  mergedRegions.push_back(merged);
702  }
703 
704  // std::vector<cloudViewer::ReferenceCloud*>* knnResult;
705  // for each region Ri in {R’}
706  /*for (cloudViewer::ReferenceCloud* r : *mergedRegionsRef)
707  {
708  // if sizeof(Ri)<Min
709  if(r->size() < Min)
710  {
711  // merge Ri to its nearest neighbors
712  KNNRegions(basePointCloud, mergedRegionsRef, r, 1,
713  knnResult, 0); mergedRegionsRef.
714  }
715  }*/
716 
717  // Return the merged and refined {R’}
718  return true;
719 }
720 
721 // filterRgbWithSegmentation parameters
722 static const unsigned TNN = 1;
723 static const double TPP = 2.0;
724 static const double TD = 2.0;
725 static const double TRR = 2.0;
726 static const unsigned Min = 2;
727 
728 void ColorimetricSegmenter::filterRgbWithSegmentation() {
729  if (m_app == nullptr) {
730  // m_app should have already been initialized by CC when plugin is
731  // loaded
732  Q_ASSERT(false);
733 
734  return;
735  }
736 
737  std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
738  if (clouds.empty()) {
739  Q_ASSERT(false);
740  return;
741  }
742 
743  // Retrieve parameters from dialog
745 
746  rgbDlg.show(); // necessary for setModal to be retained
747 
748  if (!rgbDlg.exec()) return;
749 
750  // Start timer
751  auto startTime = std::chrono::high_resolution_clock::now();
752 
753  // Get margin value (percent)
754  double marginError = rgbDlg.margin->value() / 100.0;
755 
756  // Get all values to make the color range with RGB values
757  int redInf = static_cast<int>(
758  rgbDlg.red_first->value() -
759  static_cast<int>(marginError * rgbDlg.red_first->value()));
760  int redSup = static_cast<int>(
761  rgbDlg.red_second->value() +
762  static_cast<int>(marginError * rgbDlg.red_second->value()));
763  int greenInf = static_cast<int>(
764  rgbDlg.green_first->value() -
765  static_cast<int>(marginError * rgbDlg.green_first->value()));
766  int greenSup = static_cast<int>(
767  rgbDlg.green_second->value() +
768  static_cast<int>(marginError * rgbDlg.green_second->value()));
769  int blueInf = static_cast<int>(
770  rgbDlg.blue_first->value() -
771  static_cast<int>(marginError * rgbDlg.blue_first->value()));
772  int blueSup = static_cast<int>(
773  rgbDlg.blue_second->value() +
774  static_cast<int>(marginError * rgbDlg.blue_second->value()));
775 
776  redInf = std::max(0, redInf);
777  greenInf = std::max(0, greenInf);
778  blueInf = std::max(0, blueInf);
779  redSup = std::min(255, redSup);
780  greenSup = std::min(255, greenSup);
781  blueSup = std::min(255, blueSup);
782 
783  for (ccPointCloud* cloud : clouds) {
784  if (cloud->hasColors()) {
785  RegionSet regions;
786  if (!RegionGrowing(regions, cloud, TNN, TPP, TD)) {
787  CVLog::Error("Process failed (not enough memory?)");
788  return;
789  }
790 
791  RegionSet mergedRegions;
792  RegionMergingAndRefinement(mergedRegions, cloud, regions, TNN, TRR,
793  TD, Min);
794  // m_app->dispToConsole(QString("[ColorimetricSegmenter] regions
795  // %1").arg(regions->size()),
796  // ecvMainAppInterface::STD_CONSOLE_MESSAGE);
797 
798  // retrieve the nearest region (in color range)
799  for (Region& r : mergedRegions) {
800  ecvColor::Rgb mean = ComputeAverageColor(*cloud, r.data());
801  if (Inside(static_cast<ColorCompType>(redInf), mean.r,
802  static_cast<ColorCompType>(redSup)) &&
803  Inside(static_cast<ColorCompType>(greenInf), mean.g,
804  static_cast<ColorCompType>(greenSup)) &&
805  Inside(static_cast<ColorCompType>(blueInf), mean.b,
806  static_cast<ColorCompType>(blueSup))) {
807  ccPointCloud* newCloud = cloud->partialClone(r.data());
808  if (newCloud) {
809  cloud->setEnabled(false);
810  if (cloud->getParent()) {
811  cloud->getParent()->addChild(newCloud);
812  }
813 
814  m_app->addToDB(newCloud, false, true, false, true);
815 
817  "[ColorimetricSegmenter] Cloud successfully "
818  "filtered with segmentation!",
820  } else {
822  "Not enough memory",
824  return;
825  }
826  }
827  }
828  }
829  }
830 
831  ShowDurationNow(startTime);
832 }
833 
834 // Algorithm for the HSV filter
835 // It uses the Hue-Saturation-Value (HSV) color space to filter the point cloud
836 void ColorimetricSegmenter::filterHSV() {
837  if (m_app == nullptr) {
838  // m_app should have already been initialized by CC when plugin is
839  // loaded
840  Q_ASSERT(false);
841  return;
842  }
843 
844  // Check valid window
845  if (!m_app->getActiveWindow()) {
846  m_app->dispToConsole("[ColorimetricSegmenter] No active 3D view",
848  return;
849  }
850 
851  std::vector<ccPointCloud*> clouds = getSelectedPointClouds();
852  if (clouds.empty()) {
853  Q_ASSERT(false);
854  return;
855  }
856 
857  // Retrieve parameters from dialog
859 
860  hsvDlg.show(); // necessary for setModal to be retained
861 
862  if (!hsvDlg.exec()) return;
863 
864  // Start timer
865  auto startTime = std::chrono::high_resolution_clock::now();
866 
867  // Get HSV values
868  Hsv hsv_first;
869  hsv_first.h = static_cast<uint16_t>(hsvDlg.hue_first->value());
870  hsv_first.s = static_cast<uint16_t>(hsvDlg.sat_first->value());
871  hsv_first.v = static_cast<uint16_t>(hsvDlg.val_first->value());
872 
873  // We use look-up tables for faster comparisons
874  static const uint8_t LOW = 0;
875  static const uint8_t MIDDLE = 1;
876  static const uint8_t HIGH = 2;
877  uint8_t level[101];
878  {
879  for (unsigned i = 0; i <= 25; ++i) level[i] = LOW;
880  for (unsigned i = 26; i <= 60; ++i) level[i] = MIDDLE;
881  for (unsigned i = 61; i <= 100; ++i) level[i] = HIGH;
882  }
883 
884  static const uint8_t RED = 0;
885  static const uint8_t YELLOW = 1;
886  static const uint8_t GREEN = 2;
887  static const uint8_t CYAN = 3;
888  static const uint8_t BLUE = 4;
889  static const uint8_t MAGENTA = 5;
890  uint8_t section[360];
891  {
892  for (unsigned i = 0; i <= 30; ++i) section[i] = RED;
893  for (unsigned i = 31; i <= 90; ++i) section[i] = YELLOW;
894  for (unsigned i = 91; i <= 150; ++i) section[i] = GREEN;
895  for (unsigned i = 151; i <= 210; ++i) section[i] = CYAN;
896  for (unsigned i = 211; i <= 270; ++i) section[i] = BLUE;
897  for (unsigned i = 271; i <= 330; ++i) section[i] = MAGENTA;
898  for (unsigned i = 331; i <= 359; ++i) section[i] = RED;
899  }
900 
901  for (ccPointCloud* cloud : clouds) {
902  if (cloud->hasColors()) {
903  // Use only references for speed reasons
904  cloudViewer::ReferenceCloud filteredCloudInside(cloud);
905  cloudViewer::ReferenceCloud filteredCloudOutside(cloud);
906 
907  // We manually add color ranges with HSV values
908  for (unsigned j = 0; j < cloud->size(); ++j) {
909  const ecvColor::Rgb& rgb = cloud->getPointColor(j);
910  Hsv hsv_current(rgb);
911  assert(hsv_current.h <= 359);
912  assert(hsv_current.s <= 100);
913  assert(hsv_current.v <= 100);
914 
915  if (level[hsv_first.s] == LOW &&
916  level[hsv_current.s] == LOW) // low saturation
917  {
918  // If Saturation is too small, considering Hue is useless
919  // We only check that Value is equivalent
920  addPoint(level[hsv_first.v] == level[hsv_current.v]
921  ? filteredCloudInside
922  : filteredCloudOutside,
923  j);
924  } else if (level[hsv_first.s] != LOW &&
925  level[hsv_current.s] !=
926  LOW) // middle to high saturation
927  {
928  if (level[hsv_first.v] == LOW &&
929  level[hsv_current.v] == LOW) // dark
930  {
931  addPoint(filteredCloudInside, j);
932  } else if (level[hsv_first.v] != LOW &&
933  level[hsv_current.v] != LOW) // non-dark
934  {
935  addPoint(section[hsv_first.h] == section[hsv_current.h]
936  ? filteredCloudInside
937  : filteredCloudOutside,
938  j);
939  } else {
940  addPoint(filteredCloudOutside, j);
941  }
942  } else {
943  addPoint(filteredCloudOutside, j);
944  }
945 
946  if (m_addPointError) {
947  return;
948  }
949  }
950 
951  QString name = "h:" + QString::number(hsv_first.h, 'f', 0) +
952  "/s:" + QString::number(hsv_first.s, 'f', 0) +
953  "/v:" + QString::number(hsv_first.v, 'f', 0);
954  createClouds<const HSVDialog&>(hsvDlg, cloud, filteredCloudInside,
955  filteredCloudOutside, name);
956 
958  "[ColorimetricSegmenter] Cloud successfully filtered ! ",
960  }
961  }
962 
963  ShowDurationNow(startTime);
964 }
965 
966 // Method to add point to a ReferenceCloud*
967 bool ColorimetricSegmenter::addPoint(cloudViewer::ReferenceCloud& filteredCloud,
968  unsigned int j) {
969  m_addPointError = !filteredCloud.addPointIndex(j);
970 
971  if (m_addPointError) {
972  // not enough memory
973  m_app->dispToConsole("[ColorimetricSegmenter] Error, filter canceled.");
974  }
975 
976  return m_addPointError;
977 }
978 
979 // Method to interact with the component "Which points to keep"
980 template <typename T>
981 void ColorimetricSegmenter::createClouds(
982  T& dlg,
983  ccPointCloud* cloud,
984  const cloudViewer::ReferenceCloud& filteredCloudInside,
985  const cloudViewer::ReferenceCloud& filteredCloudOutside,
986  QString name) {
987  if (dlg.retain->isChecked()) {
988  createCloud(cloud, filteredCloudInside, name + ".inside");
989  } else if (dlg.exclude->isChecked()) {
990  createCloud(cloud, filteredCloudOutside, name + ".outside");
991  } else if (dlg.both->isChecked()) {
992  createCloud(cloud, filteredCloudInside, name + ".inside");
993  createCloud(cloud, filteredCloudOutside, name + ".outside");
994  }
995 }
996 
997 // Method to create a new cloud
998 void ColorimetricSegmenter::createCloud(
999  ccPointCloud* cloud,
1000  const cloudViewer::ReferenceCloud& referenceCloud,
1001  QString name) {
1002  if (!cloud) {
1003  Q_ASSERT(false);
1004  return;
1005  }
1006 
1007  ccPointCloud* newCloud = cloud->partialClone(&referenceCloud);
1008  if (!newCloud) {
1009  m_app->dispToConsole("Not enough memory");
1010  return;
1011  }
1012 
1013  newCloud->setName(name);
1014  cloud->setEnabled(false);
1015  if (cloud->getParent()) {
1016  cloud->getParent()->addChild(newCloud);
1017  }
1018 
1019  m_app->addToDB(newCloud, false, true, false, true);
1020 }
1021 
1029 typedef std::map<size_t, std::vector<unsigned>> ClusterMap;
1030 static bool GetKeyCluster(const ccPointCloud& cloud,
1031  size_t clusterPerDim,
1032  ClusterMap& clusterMap) {
1033  Q_ASSERT(ecvColor::MAX == 255);
1034 
1035  try {
1036  for (unsigned i = 0; i < cloud.size(); i++) {
1037  const ecvColor::Rgb& rgb = cloud.getPointColor(i);
1038 
1039  size_t redCluster = (static_cast<size_t>(rgb.r) * clusterPerDim) >>
1040  8; // shift 8 bits (= division by 256)
1041  size_t greenCluster =
1042  (static_cast<size_t>(rgb.g) * clusterPerDim) >>
1043  8; // shift 8 bits (= division by 256)
1044  size_t blueCluster = (static_cast<size_t>(rgb.b) * clusterPerDim) >>
1045  8; // shift 8 bits (= division by 256)
1046 
1047  size_t index =
1048  redCluster + (greenCluster + blueCluster * clusterPerDim) *
1049  clusterPerDim;
1050 
1051  // we add the point to the right container
1052  clusterMap[index].push_back(i);
1053  }
1054  } catch (const std::bad_alloc&) {
1055  return false;
1056  }
1057 
1058  return true;
1059 }
1067  const std::vector<unsigned>& bucket) {
1068  size_t count = bucket.size();
1069  if (count == 0) {
1070  return ecvColor::white;
1071  } else if (count == 1) {
1072  return cloud.getPointColor(bucket.front());
1073  }
1074 
1075  // other formula to compute the average can be used
1076  size_t redSum = 0, greenSum = 0, blueSum = 0;
1077  for (unsigned pointIndex : bucket) {
1078  const ecvColor::Rgb& rgba = cloud.getPointColor(pointIndex);
1079  redSum += rgba.r;
1080  greenSum += rgba.g;
1081  blueSum += rgba.b;
1082  }
1083 
1084  ecvColor::Rgb res(
1085  static_cast<ColorCompType>(std::min(
1086  redSum / count, static_cast<size_t>(ecvColor::MAX))),
1087  static_cast<ColorCompType>(std::min(
1088  greenSum / count, static_cast<size_t>(ecvColor::MAX))),
1089  static_cast<ColorCompType>(std::min(
1090  blueSum / count, static_cast<size_t>(ecvColor::MAX))));
1091 
1092  return res;
1093 }
1094 
1099 static int ColorDistance(const ecvColor::Rgb& c1, const ecvColor::Rgb& c2) {
1100  return (static_cast<int>(c1.r) - c2.r) + (static_cast<int>(c1.b) - c2.b) +
1101  (static_cast<int>(c1.g) - c2.g);
1102 }
1103 
1109 void ColorimetricSegmenter::HistogramClustering() {
1110  if (m_app == nullptr) {
1111  // m_app should have already been initialized by CC when plugin is
1112  // loaded
1113  Q_ASSERT(false);
1114 
1115  return;
1116  }
1117 
1118  std::vector<ccPointCloud*> clouds =
1119  ColorimetricSegmenter::getSelectedPointClouds();
1120  if (clouds.empty()) {
1121  Q_ASSERT(false);
1122  return;
1123  }
1124 
1125  // creation of the window
1126  QuantiDialog quantiDlg(m_app->getMainWindow());
1127  if (!quantiDlg.exec()) return;
1128 
1129  // Start timer
1130  auto startTime = std::chrono::high_resolution_clock::now();
1131 
1132  int nbClusterByComponent = quantiDlg.area_quanti->value();
1133  if (nbClusterByComponent < 0) {
1134  Q_ASSERT(false);
1135  return;
1136  }
1137 
1138  for (ccPointCloud* cloud : clouds) {
1139  if (cloud->hasColors()) {
1140  ClusterMap clusterMap;
1141  if (!GetKeyCluster(*cloud,
1142  static_cast<size_t>(nbClusterByComponent),
1143  clusterMap)) {
1144  m_app->dispToConsole("Not enough memory",
1146  break;
1147  }
1148 
1149  ccPointCloud* histCloud = cloud->cloneThis();
1150  if (!histCloud) {
1151  m_app->dispToConsole("Not enough memory",
1153  break;
1154  }
1155 
1156  histCloud->setName(
1157  QString("HistogramClustering: Indice Q = %1 // colors = %2")
1158  .arg(nbClusterByComponent)
1159  .arg(nbClusterByComponent * nbClusterByComponent *
1160  nbClusterByComponent));
1161 
1162  for (auto it = clusterMap.begin(); it != clusterMap.end(); it++) {
1163  ecvColor::Rgb averageColor =
1164  ComputeAverageColor(*histCloud, it->second);
1165 
1166  for (unsigned pointIndex : it->second) {
1167  (*histCloud).setPointColor(pointIndex, averageColor);
1168  }
1169  }
1170 
1171  cloud->setEnabled(false);
1172  if (cloud->getParent()) {
1173  cloud->getParent()->addChild(histCloud);
1174  }
1175 
1176  m_app->addToDB(histCloud, false, true, false, true);
1177 
1179  "[ColorimetricSegmenter] Cloud successfully clustered!",
1181  }
1182  }
1183 
1184  ShowDurationNow(startTime);
1185 }
1186 
1194  unsigned K,
1195  int maxIterationCount) {
1196  // valid parameters?
1197  if (!theCloud || K == 0) {
1198  Q_ASSERT(false);
1199  return nullptr;
1200  }
1201 
1202  unsigned pointCount = theCloud->size();
1203  if (pointCount == 0) return nullptr;
1204 
1205  if (K >= pointCount) {
1207  "Cloud %1 has less point than the expected number of classes.");
1208  return nullptr;
1209  }
1210 
1211  ccPointCloud* KCloud = nullptr;
1212 
1213  try {
1214  std::vector<ecvColor::Rgb> clusterCenters; // K clusters centers
1215  std::vector<std::size_t>
1216  clusterIndex; // index of the cluster the point belongs to
1217 
1218  clusterIndex.resize(pointCount);
1219  clusterCenters.resize(K);
1220 
1221  // init (regularly sampled) classes centers
1222  double step = static_cast<double>(pointCount) / K;
1223  for (unsigned j = 0; j < K; ++j) {
1224  // TODO: this initialization is pretty biased... To be improved?
1225  clusterCenters[j] = theCloud->getPointColor(
1226  static_cast<unsigned>(std::ceil(step * j)));
1227  }
1228 
1229  // let's start
1230  int iteration = 0;
1231  for (; iteration < maxIterationCount; ++iteration) {
1232  bool meansHaveMoved = false;
1233 
1234  // assign each point (color) to the nearest cluster
1235  for (unsigned i = 0; i < pointCount; ++i) {
1236  const ecvColor::Rgb& color = theCloud->getPointColor(i);
1237 
1238  std::size_t minK = 0;
1239  int minDistsToMean =
1240  std::abs(ColorDistance(color, clusterCenters[minK]));
1241 
1242  // we look for the nearest cluster center
1243  for (unsigned j = 1; j < K; ++j) {
1244  int distToMean =
1245  std::abs(ColorDistance(color, clusterCenters[j]));
1246  if (distToMean < minDistsToMean) {
1247  minDistsToMean = distToMean;
1248  minK = j;
1249  }
1250  }
1251 
1252  clusterIndex[i] = minK;
1253  }
1254 
1255  // update the clusters centers
1256  std::vector<std::vector<unsigned>> clusters;
1257  clusters.resize(K);
1258  for (unsigned i = 0; i < pointCount; ++i) {
1259  unsigned index = static_cast<unsigned>(
1260  clusterIndex[static_cast<std::size_t>(i)]);
1261  clusters[index].push_back(i);
1262  }
1263 
1264  CVLog::Print("Iteration " + QString::number(iteration));
1265  for (unsigned j = 0; j < K; ++j) {
1266  const std::vector<unsigned>& cluster = clusters[j];
1267  if (cluster.empty()) {
1268  continue;
1269  }
1270 
1271  ecvColor::Rgb newMean = ComputeAverageColor(*theCloud, cluster);
1272 
1273  if (!meansHaveMoved &&
1274  ColorDistance(clusterCenters[j], newMean) != 0) {
1275  meansHaveMoved = true;
1276  }
1277 
1278  clusterCenters[j] = newMean;
1279  }
1280 
1281  if (!meansHaveMoved) {
1282  break;
1283  }
1284  }
1285 
1286  KCloud = theCloud->cloneThis();
1287  if (!KCloud) {
1288  // not enough memory
1289  return nullptr;
1290  }
1291  KCloud->setName("Kmeans clustering: K = " + QString::number(K) +
1292  " / it = " + QString::number(iteration));
1293 
1294  // set color for each cluster
1295  for (unsigned i = 0; i < pointCount; i++) {
1296  KCloud->setPointColor(i, clusterCenters[clusterIndex[i]]);
1297  }
1298 
1299  } catch (const std::bad_alloc&) {
1300  // not enough memory
1301  return nullptr;
1302  }
1303 
1304  return KCloud;
1305 }
1306 
1310 void ColorimetricSegmenter::KmeansClustering() {
1311  std::vector<ccPointCloud*> clouds =
1312  ColorimetricSegmenter::getSelectedPointClouds();
1313  if (clouds.empty()) {
1314  Q_ASSERT(false);
1315  return;
1316  }
1317 
1318  KmeansDlg kmeansDlg(m_app->getMainWindow());
1319  if (!kmeansDlg.exec()) return;
1320 
1321  assert(kmeansDlg.spinBox_k->value() >= 0);
1322  unsigned K = static_cast<unsigned>(kmeansDlg.spinBox_k->value());
1323  int iterationCount = kmeansDlg.spinBox_it->value();
1324 
1325  // Start timer
1326  auto startTime = std::chrono::high_resolution_clock::now();
1327 
1328  for (ccPointCloud* cloud : clouds) {
1329  ccPointCloud* kcloud =
1330  ComputeKmeansClustering(cloud, K, iterationCount);
1331  if (!kcloud) {
1332  m_app->dispToConsole(QString("[ColorimetricSegmenter] Failed to "
1333  "cluster cloud %1")
1334  .arg(cloud->getName()),
1336  continue;
1337  }
1338 
1339  cloud->setEnabled(false);
1340  if (cloud->getParent()) {
1341  cloud->getParent()->addChild(kcloud);
1342  }
1343 
1344  m_app->addToDB(kcloud, false, true, false, true);
1346  "[ColorimetricSegmenter] Cloud successfully clustered!",
1348  }
1349 
1350  ShowDurationNow(startTime);
1351 }
std::string name
int count
math::float4 color
cmdLineReadable * params[]
core::Tensor result
Definition: VtkUtils.cpp:76
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
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
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.)
ccPointCloud * cloneThis(ccPointCloud *destCloud=nullptr, bool ignoreChildren=false)
Clones this entity.
bool hasColors() const override
Returns whether colors are enabled or not.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color corresponding to a given point.
ccPointCloud * partialClone(const cloudViewer::ReferenceCloud *selection, int *warnings=nullptr, bool withChildEntities=true) const
Creates a new point cloud object from a ReferenceCloud (selection)
Standard ECV plugin interface.
ecvMainAppInterface * m_app
Main application interface.
The octree structure used throughout the library.
Definition: DgmOctree.h:39
void getCellPos(CellCode code, unsigned char level, Tuple3i &cellPos, bool isCodeTruncated) const
Definition: DgmOctree.cpp:498
unsigned findNearestNeighborsStartingFromCell(NearestNeighboursSearchStruct &nNSS, bool getOnlyPointsWithValidScalar=false) const
Definition: DgmOctree.cpp:1655
void computeCellCenter(CellCode code, unsigned char level, CCVector3 &center, bool isCodeTruncated=false) const
Definition: DgmOctree.h:862
int build(GenericProgressCallback *progressCb=nullptr)
Builds the structure.
Definition: DgmOctree.cpp:196
const CellCode & getCellCode(unsigned index) const
Returns the ith cell code.
Definition: DgmOctree.h:963
static int computeCloud2CloudDistances(GenericIndexedCloudPersist *comparedCloud, GenericIndexedCloudPersist *referenceCloud, Cloud2CloudDistancesComputationParams &params, GenericProgressCallback *progressCb=nullptr, DgmOctree *compOctree=nullptr, DgmOctree *refOctree=nullptr)
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.
unsigned size() const override
Returns the number of points.
virtual unsigned getPointGlobalIndex(unsigned localIndex) const
RGB color structure.
Definition: ecvColorTypes.h:49
virtual QMainWindow * getMainWindow()=0
Returns main window.
virtual QWidget * getActiveWindow()=0
virtual const ccHObject::Container & getSelectedEntities() const =0
Returns currently selected entities ("read only")
virtual void addToDB(ccHObject *obj, bool updateZoom=false, bool autoExpandDBTree=true, bool checkDimensions=false, bool autoRedraw=true)=0
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
virtual ccPickingHub * pickingHub()
int min(int a, int b)
Definition: cutil_math.h:53
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
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
@ POINT_CLOUD
Definition: CVTypes.h:104
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
constexpr Rgb white(MAX, MAX, MAX)
constexpr ColorCompType MAX
Max value of a single color component (default type)
Definition: ecvColorTypes.h:34
cloudViewer::DgmOctree * octree
std::map< size_t, std::vector< unsigned > > ClusterMap
static const unsigned Min
static ccPointCloud * ComputeKmeansClustering(ccPointCloud *theCloud, unsigned K, int maxIterationCount)
static double ColorimetricalDifference(ecvColor::Rgb c1, ecvColor::Rgb c2)
colorimetricalDifference Compute colorimetrical difference between two RGB color values.
static bool Inside(ColorCompType lower, ColorCompType value, ColorCompType upper)
std::vector< _RegionSet > SetOfRegionSet
static const double TPP
static const unsigned TNN
QSharedPointer< cloudViewer::ReferenceCloud > _Region
static bool KNNRegions(ccPointCloud *basePointCloud, const _RegionSet &regions, const _Region &region, unsigned k, _RegionSet &neighbourRegions, unsigned thresholdDistance)
KNNRegions Determines the neighboring regions of a region.
static const double TD
static int ColorDistance(const ecvColor::Rgb &c1, const ecvColor::Rgb &c2)
static int FindRegion(const std::vector< _RegionSet > &container, cloudViewer::ReferenceCloud *region)
findRegion Find a given region in a vector of reference clouds.
static const double TRR
static ecvColor::Rgb ComputeAverageColor(const ccPointCloud &cloud, cloudViewer::ReferenceCloud *subset)
std::vector< _Region > _RegionSet
static void ShowDurationNow(const std::chrono::high_resolution_clock::time_point &startTime)
static bool GetKeyCluster(const ccPointCloud &cloud, size_t clusterPerDim, ClusterMap &clusterMap)
HSV color.
Definition: HSV.h:31
uint16_t v
Definition: HSV.h:66
uint16_t s
Definition: HSV.h:66
uint16_t h
Definition: HSV.h:66
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
double maxSearchSquareDistd
Maximum neihgbours distance.
Definition: DgmOctree.h:196
Tuple3i cellPos
Position in the octree of the cell including the query point.
Definition: DgmOctree.h:184
CCVector3 cellCenter
Coordinates of the center of the cell including the query point.
Definition: DgmOctree.h:189
unsigned minNumberOfNeighbors
Minimal number of neighbours to find.
Definition: DgmOctree.h:177
Cloud-to-cloud "Hausdorff" distance computation parameters.