ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
facetsClassifier.h
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #pragma once
9 
10 // Qt
11 #include <QApplication>
12 #include <QProgressDialog>
13 
14 // CV_DB_LIB
15 #include "ecvFacet.h"
16 #include "ecvPointCloud.h"
17 #include "ecvPolyline.h"
18 
19 static const QString s_OriFamilyKey = "orientation.family.index";
20 static const QString s_OriFamilyNameKey = "orientation.family.name";
21 static const QString s_OriSubFamilyKey = "orientation.subfamily.index";
22 
23 // default ratio for the 'dark' version of a color
24 const double c_darkColorRatio = 0.25;
25 
27 public:
29  typedef std::vector<ccFacet*> FacetSet;
30 
33  const ccFacet* f2) {
34  CCVector3 AB = f1->getCenter() - f2->getCenter();
35  return std::min(fabs(AB.dot(f1->getNormal())),
36  fabs(AB.dot(f2->getNormal())));
37  }
38 
41  double dip,
42  double dipDir,
43  unsigned subFamilyIndex,
44  unsigned subFamilyCount,
45  ecvColor::Rgb* darkCol = 0) {
46  // convert dip & dip dir. to HSV
47  double H = dipDir;
48  if (H == 360.0) // H is in [0;360[
49  H = 0;
50  double S = dip / 90.0; // S is in [0;1]
51  double L = 0.5;
52 
53  if (subFamilyCount > 1) {
54  assert(subFamilyIndex >= 1);
55  // FIXME: how could we do this?!
56  // V = 0.5 + 0.5 *
57  // static_cast<double>(subFamilyIndex-1)/static_cast<double>(subFamilyCount-1);
58  }
59 
60  col = ecvColor::Convert::hsl2rgb(H, S, L);
61  if (darkCol) {
63  }
64  }
65 
66  static void GetFamilyIndexes(ccFacet* facet,
67  unsigned dSteps,
68  unsigned ddSteps,
69  double angularStep_deg,
70  unsigned& iDip,
71  unsigned& iDipDir) {
72  assert(facet);
73  CCVector3 N = facet->getNormal();
74 
75  PointCoordinateType dipDir = 0, dip = 0;
77 
78  iDip = static_cast<unsigned>(floor(dip / angularStep_deg));
79  if (iDip == dSteps) iDip--;
80  iDipDir = static_cast<unsigned>(floor(dipDir / angularStep_deg));
81  if (iDipDir == ddSteps) iDipDir--;
82  }
83 
84  static inline QString GetFamilyName(double dip,
85  double dipDir,
86  double angularStep_deg) {
87  // return QString("(%1+/-%2) /
88  // (%3+/-%4)").arg(dip).arg(angularStep_deg/2).arg(dipDir).arg(angularStep_deg/2);
89  return QString("%1_%2").arg(dipDir).arg(dip);
90  }
91 
92  static inline QString GetSubFamilyName(int subFamilyIndex) {
93  return QString("f%1").arg(subFamilyIndex, 4, 10, QChar('0'));
94  }
95 
97  static bool ProcessFamiliy(ccHObject* parent,
98  FacetSet& family,
99  unsigned familyIndex,
100  unsigned iDip,
101  unsigned iDipDir,
102  double angularStep_deg,
103  double maxDist) {
104  size_t count = family.size();
105  if (count == 0) return true;
106 
107  double dip = (iDip + 0.5) * angularStep_deg;
108  double dipDir = (iDipDir + 0.5) * angularStep_deg;
109 
110  QString familyName = GetFamilyName(dip, dipDir, angularStep_deg);
111 
112  // create family group
113  ccHObject* familyGroup = new ccHObject(
114  QString("F%1_").arg(familyIndex, 2, 10, QChar('0')) +
115  familyName);
116  if (parent) parent->addChild(familyGroup);
117 
118  // tag all facets consequently
119  for (FacetSet::iterator it = family.begin(); it != family.end(); ++it) {
120  (*it)->setMetaData(s_OriFamilyKey,
121  QVariant(static_cast<uint>(familyIndex)));
122  (*it)->setMetaData(s_OriFamilyNameKey, QVariant(familyName));
123  }
124 
125  // now we are going to regroup the facets in sub-families
126  unsigned subFamilyIndex = 0;
127 
128  if (count == 1 || count == 2) {
129  // 1 or 2 subsets at most!
130  family[0]->setMetaData(
132  QVariant(static_cast<uint>(++subFamilyIndex)));
133  ccHObject* subFamilyGroup =
134  new ccHObject(GetSubFamilyName(subFamilyIndex));
135  familyGroup->addChild(subFamilyGroup);
136  assert(family[0]->getParent() == 0);
137  subFamilyGroup->addChild(family[0]);
138 
139  if (count == 2) {
141  CommputeHDistBetweenFacets(family[0], family[1]);
142  if (dist <= maxDist) {
143  // same sub-family as #1
144  family[1]->setMetaData(
146  QVariant(static_cast<uint>(subFamilyIndex)));
147  assert(family[1]->getParent() == 0);
148  subFamilyGroup->addChild(family[1]);
149  } else {
150  // new sub-family
151  family[1]->setMetaData(
153  QVariant(static_cast<uint>(++subFamilyIndex)));
154  ccHObject* subFamilyGroup2 =
155  new ccHObject(GetSubFamilyName(subFamilyIndex));
156  familyGroup->addChild(subFamilyGroup2);
157  assert(family[1]->getParent() == 0);
158  subFamilyGroup2->addChild(family[1]);
159  }
160  }
161  } else {
162  // create relative distance matrix
163  cloudViewer::SquareMatrixf distMat(static_cast<unsigned>(count));
164  if (distMat.isValid()) {
165  for (unsigned it1 = 0; it1 + 1 != count; ++it1) {
166  for (unsigned it2 = it1 + 1; it2 != count; ++it2) {
168  family.at(it1), family.at(it2));
169  distMat.setValue(it1, it2, static_cast<float>(dist));
170  distMat.setValue(it2, it1, static_cast<float>(dist));
171  }
172  }
173 
174  // regroup elements
175  while (true) {
176  // we look for the two nearest facets
177  std::pair<unsigned, unsigned> bestCouple(0, 0);
178  PointCoordinateType bestCoupleDist = -1;
179  for (unsigned i = 0; i + 1 < count; ++i) {
180  if (distMat.getValue(i, i) ==
181  0) // not already assigned
182  {
183  for (unsigned j = i + 1; j < count; ++j) {
184  if (distMat.getValue(j, j) ==
185  0) // not already assigned
186  {
187  if (bestCoupleDist < 0 ||
188  bestCoupleDist >
189  distMat.getValue(i, j)) {
190  bestCouple.first = i;
191  bestCouple.second = j;
192  bestCoupleDist = distMat.getValue(i, j);
193  }
194  }
195  }
196  }
197  }
198 
199  if (bestCoupleDist < 0 || bestCoupleDist > maxDist)
200  break; // no more available couples!
201 
202  // othrewise we push this couple in the new family set
203  std::vector<unsigned> subFamily(2);
204  subFamily[0] = bestCouple.first;
205  subFamily[1] = bestCouple.second;
206  // flag them as 'assigned'
207  distMat.setValue(bestCouple.first, bestCouple.first, -1);
208  distMat.setValue(bestCouple.second, bestCouple.second, -1);
209 
210  // now look for other available facets for this sub-family
211  while (true) {
212  PointCoordinateType bestDist = -1;
213  unsigned bestIndex = 0;
214  for (unsigned i = 0; i < count; ++i) {
215  if (distMat.getValue(i, i) ==
216  0) // not already assigned
217  {
218  PointCoordinateType minDist = -1;
219  for (unsigned j = 0; j < subFamily.size();
220  ++j) {
221  const PointCoordinateType& dist =
222  distMat.getValue(i, subFamily[j]);
223  if (dist < maxDist) {
224  // still elligible?
225  if (minDist < 0 || dist < minDist)
226  minDist = dist;
227  } else {
228  minDist = -1;
229  break;
230  }
231  } // end of sub-family test
232 
233  if (minDist >= 0 &&
234  (bestDist < 0 || minDist < bestDist)) {
235  bestDist = minDist;
236  bestIndex = i;
237  }
238  }
239  }
240 
241  if (bestDist >= 0) {
242  // add candidate to sub-family
243  subFamily.push_back(bestIndex);
244  // flag it as 'assigned'
245  distMat.setValue(bestIndex, bestIndex, -1);
246  } else {
247  break;
248  }
249  }
250  // end of sub-family
251 
252  // we can set the class for all the sub-family elements
253  {
254  ++subFamilyIndex;
255  ccHObject* subFamilyGroup =
256  new ccHObject(GetSubFamilyName(subFamilyIndex));
257  familyGroup->addChild(subFamilyGroup);
258  for (unsigned j = 0; j < subFamily.size(); ++j) {
259  ccFacet* facet = family.at(subFamily[j]);
261  QVariant(static_cast<uint>(
262  subFamilyIndex)));
263  assert(facet->getParent() == 0);
264  subFamilyGroup->addChild(facet);
265  }
266  }
267  }
268  // no more sub-families
269 
270  // don't forget to set the class for the remaining isolated
271  // elements
272  {
273  for (unsigned i = 0; i < count; ++i) {
274  if (distMat.getValue(i, i) ==
275  0) // not already assigned
276  {
277  ccFacet* facet = family[i];
278  assert(facet->getParent() == 0);
280  QVariant(static_cast<uint>(
281  ++subFamilyIndex)));
282  ccHObject* subFamilyGroup = new ccHObject(
283  GetSubFamilyName(subFamilyIndex));
284  familyGroup->addChild(subFamilyGroup);
285  subFamilyGroup->addChild(facet);
286  }
287  }
288  }
289 
290  } else {
291  // not enough memory
292  for (unsigned i = 0; i < count; ++i)
293  familyGroup->addChild(family[i]);
294  return false;
295  }
296  }
297 
298  // eventually we set the right colors for each facet
299  unsigned subFamilyCount = subFamilyIndex;
300  if (subFamilyCount) {
301  assert(subFamilyCount > 0);
302  ecvColor::Rgb col, darkCol;
303  for (unsigned i = 0; i < count; ++i) {
304  ccFacet* facet = family.at(i);
305  subFamilyIndex = facet->getMetaData(s_OriSubFamilyKey).toUInt();
306  GenerateSubfamilyColor(col, dip, dipDir, subFamilyIndex,
307  subFamilyCount, &darkCol);
308  facet->setColor(col);
309  if (facet->getContour()) {
310  facet->getContour()->setColor(darkCol);
311  facet->getContour()->setWidth(2);
312  }
313  }
314  }
315 
316  return true;
317  }
318 
320  static bool ByOrientation(ccHObject* facetGroup,
321  double angularStep_deg,
322  double maxDist) {
323  assert(facetGroup && angularStep_deg > 0 && maxDist >= 0);
324 
325  ccHObject::Container facets;
326  if (facetGroup)
327  facetGroup->filterChildren(facets, true, CV_TYPES::FACET);
328 
329  size_t facetCount = facets.size();
330  if (facetCount == 0) {
331  // nothing to do
332  return true;
333  }
334 
335  // remove all facets from the group first (without deleting them!)
336  {
337  for (size_t i = 0; i < facetCount; ++i) {
338  ccFacet* facet = static_cast<ccFacet*>(facets[i]);
339  assert(facet && facet->getParent());
340  facet->getParent()->removeDependencyWith(facet);
341  facet->getParent()->removeChild(facet);
342  }
343  // and remove all remaining children from the input group!
344  facetGroup->removeAllChildren();
345  }
346 
347  // dip steps (dip in [0,90])
348  unsigned dSteps = static_cast<unsigned>(ceil(90.0 / angularStep_deg));
349  // dip direction steps (dip dir. in [0,360])
350  unsigned ddSteps = static_cast<unsigned>(ceil(360.0 / angularStep_deg));
351 
352  bool error = false;
353  if (facetCount == 1) {
354  ccFacet* facet = static_cast<ccFacet*>(facets.front());
355 
356  // unique facet = unique family
357  FacetSet family(1, facet);
358 
359  unsigned iDip = 0, iDipDir = 0;
360  GetFamilyIndexes(facet, dSteps, ddSteps, angularStep_deg, iDip,
361  iDipDir);
362 
363  error = !ProcessFamiliy(facetGroup, family, 1, iDip, iDipDir,
364  angularStep_deg, maxDist);
365  } else {
366  unsigned gridSize = dSteps * ddSteps;
367 
368  // grid to store all orientation 'families'
369  FacetSet** grid = new FacetSet*[gridSize];
370  if (!grid) {
371  // not enough memory
372  error = true;
373  } else {
374  memset(grid, 0, sizeof(FacetSet*) * gridSize);
375 
376  QProgressDialog pDlg("Families classification", QString(), 0,
377  static_cast<int>(facetCount));
378  pDlg.show();
379  QApplication::processEvents();
380 
381  // project each facet in grid
382  unsigned setCount = 0;
383  for (size_t i = 0; i < facetCount; ++i) {
384  ccFacet* facet = static_cast<ccFacet*>(facets[i]);
385 
386  unsigned iDip = 0, iDipDir = 0;
387  GetFamilyIndexes(facet, dSteps, ddSteps, angularStep_deg,
388  iDip, iDipDir);
389 
390  unsigned facetIndex = iDipDir + iDip * ddSteps;
391  assert(facetIndex < gridSize);
392  FacetSet* set = grid[facetIndex];
393  if (!set) {
394  set = new FacetSet;
395  if (!set) {
396  // not enough memory
397  error = true;
398  break;
399  }
400  grid[iDipDir + iDip * ddSteps] = set;
401  ++setCount;
402  }
403 
404  try {
405  set->push_back(facet);
406  } catch (const std::bad_alloc&) {
407  // not enough memory
408  error = true;
409  break;
410  }
411 
412  pDlg.setValue(static_cast<int>(i));
413  }
414 
415  if (!error) {
416  unsigned familyIndex = 0;
417 
418  QProgressDialog pDlg("Sub-families classification",
419  QString(), 0,
420  static_cast<int>(setCount));
421  pDlg.show();
422  QApplication::processEvents();
423 
424  // now scan the grid and tag all 'families'
425  FacetSet** set = grid;
426  int progress = 0;
427  for (unsigned j = 0; j < dSteps; ++j) {
428  for (unsigned i = 0; i < ddSteps; ++i, ++set) {
429  // new family?
430  if (*set) {
432  facetGroup, **set, ++familyIndex, j, i,
433  angularStep_deg, maxDist);
434  if (error) {
435  // stop the process
436  // i = ddSteps;
437  j = dSteps;
438  break;
439  }
440 
441  pDlg.setValue(++progress);
442  }
443  }
444  }
445  }
446  }
447 
448  // release grid
449  {
450  for (unsigned i = 0; i < gridSize; ++i)
451  if (grid[i]) delete grid[i];
452  delete[] grid;
453  grid = 0;
454  }
455  }
456 
457  // check that no parent-less facets remain!
458  {
459  for (size_t i = 0; i < facetCount; ++i) {
460  ccFacet* facet = static_cast<ccFacet*>(facets[i]);
461  assert(facet->getParent() || error);
462  if (!facet->getParent()) facetGroup->addChild(facet);
463  }
464  }
465 
466  // update associated display to all children as the whole structure may
467  // have changed!
468  // facetGroup->setDisplay_recursive(facetGroup->getDisplay());
469 
470  return !error;
471  }
472 };
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int count
static void GetFamilyIndexes(ccFacet *facet, unsigned dSteps, unsigned ddSteps, double angularStep_deg, unsigned &iDip, unsigned &iDipDir)
static QString GetFamilyName(double dip, double dipDir, double angularStep_deg)
static QString GetSubFamilyName(int subFamilyIndex)
static void GenerateSubfamilyColor(ecvColor::Rgb &col, double dip, double dipDir, unsigned subFamilyIndex, unsigned subFamilyCount, ecvColor::Rgb *darkCol=0)
Generates a given sub-family color.
std::vector< ccFacet * > FacetSet
Set of facets (pointers to)
static bool ProcessFamiliy(ccHObject *parent, FacetSet &family, unsigned familyIndex, unsigned iDip, unsigned iDipDir, double angularStep_deg, double maxDist)
Subdivides a set of facets with similar orientation.
static PointCoordinateType CommputeHDistBetweenFacets(const ccFacet *f1, const ccFacet *f2)
Computes minimal 'orthogonal' distance between two facets.
static bool ByOrientation(ccHObject *facetGroup, double angularStep_deg, double maxDist)
Classifies the facets based on their orientation.
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Facet.
Definition: ecvFacet.h:25
ccPolyline * getContour()
Returns contour polyline (if any)
Definition: ecvFacet.h:86
CCVector3 getNormal() const override
Returns the entity normal.
Definition: ecvFacet.h:63
void setColor(const ecvColor::Rgb &rgb)
Sets the facet unique color.
const CCVector3 & getCenter() const
Returns the facet center.
Definition: ecvFacet.h:78
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
void removeAllChildren()
Removes all children.
void removeDependencyWith(ccHObject *otherObject)
Removes any dependency flags with a given object.
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
void removeChild(ccHObject *child)
unsigned filterChildren(Container &filteredChildren, bool recursive=false, CV_CLASS_ENUM filter=CV_TYPES::OBJECT, bool strict=false) const
Collects the children corresponding to a certain pattern.
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
void setWidth(PointCoordinateType width)
Sets the width of the line.
bool isValid() const
Returns matrix validity.
Definition: SquareMatrix.h:138
void setValue(unsigned row, unsigned column, Scalar value)
Sets a particular matrix value.
Definition: SquareMatrix.h:163
Scalar getValue(unsigned row, unsigned column) const
Returns a particular matrix value.
Definition: SquareMatrix.h:168
static Rgb hsl2rgb(float H, float S, float L)
Converts a HSL color to RGB color space.
RGB color structure.
Definition: ecvColorTypes.h:49
unsigned int uint
Definition: cutil_math.h:28
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
static const QString s_OriSubFamilyKey
static const QString s_OriFamilyKey
static const QString s_OriFamilyNameKey
const double c_darkColorRatio
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
static void error(char *msg)
Definition: lsd.c:159
@ FACET
Definition: CVTypes.h:109
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