ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
NeighborhoodFeature.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 "NeighborhoodFeature.h"
9 
10 // CCLib
12 #include <Jacobi.h>
13 #include <Neighbourhood.h>
14 
15 using namespace masc;
16 
17 bool NeighborhoodFeature::checkValidity(QString corePointRole,
18  QString& error) const {
19  if (!Feature::checkValidity(corePointRole, error)) {
20  return false;
21  }
22 
23  if (type == Invalid) {
24  assert(false);
25  error = "invalid feature type";
26  return false;
27  }
28 
29  if (stat != Feature::NO_STAT) {
30  error = "Neighborhood features shouldn't be associated to a STAT "
31  "measure";
32  return false;
33  }
34 
35  if (cloud2 && op == NO_OPERATION) {
36  error = "Feature has a second cloud associated but no MATH operation "
37  "is defined";
38  return false;
39  }
40 
41  if (std::isnan(scale)) {
42  error = "No scale defined";
43  return false;
44  }
45 
46  return true;
47 }
48 
50  const CorePoints& corePoints,
51  QString& error,
52  cloudViewer::GenericProgressCallback* progressCb /*=nullptr*/,
53  SFCollector* generatedScalarFields /*=nullptr*/) {
54  if (!cloud1 || !corePoints.cloud) {
55  // invalid input
56  assert(false);
57  error = "internal error (no input core points)";
58  return false;
59  }
60 
61  if (!checkValidity(corePoints.role, error)) {
62  assert(false);
63  return false;
64  }
65 
66  // build the final SF name
67  QString resultSFName = ToString(type) + "_" + cloud1Label;
68  if (cloud2) {
69  // include the math operation as well if necessary!
70  resultSFName += "_" + Feature::OpToString(op) + "_" + cloud2Label;
71  }
72  resultSFName += "@" + QString::number(scale);
73 
74  // and the scalar field
75  assert(!sf1);
77  CheckSFExistence(corePoints.cloud, qPrintable(resultSFName));
79  sf1 = PrepareSF(corePoints.cloud, qPrintable(resultSFName),
80  generatedScalarFields, SFCollector::ALWAYS_KEEP);
81  if (generatedScalarFields->scalarFields.contains(
82  sf1)) // i.e. the SF is existing but was not present at the
83  // startup of the plugin
84  generatedScalarFields->setBehavior(sf1, SFCollector::CAN_REMOVE);
85  } else {
86  sf1 = PrepareSF(corePoints.cloud, qPrintable(resultSFName),
87  generatedScalarFields, SFCollector::CAN_REMOVE);
88  }
89  if (!sf1) {
90  error = QString("Failed to prepare scalar %1 @ scale %2")
91  .arg(resultSFName)
92  .arg(scale);
93  return false;
94  }
95  source.name = sf1->getName();
96 
97  // sf2 is not needed if sf1 was already existing!
99  QString resultSFName2 = ToString(type) + "_" + cloud2Label + "@" +
100  QString::number(scale);
101 
102  assert(!sf2);
103 
105  CheckSFExistence(corePoints.cloud, qPrintable(resultSFName2));
106  sf2 = PrepareSF(corePoints.cloud, qPrintable(resultSFName2),
107  generatedScalarFields,
110 
111  if (!sf2) {
112  error = QString("Failed to prepare scalar field for %1 @ scale %2")
113  .arg(cloud2Label)
114  .arg(scale);
115  return false;
116  }
117  }
118 
119  return true;
120 }
121 
123  if (!corePoints.cloud) {
124  // invalid input
125  assert(false);
126  error = "internal error (no input core points)";
127  return false;
128  }
129 
130  bool success = true;
131 
132  if (sf1) {
134 
135  // update display
136  // if (corePoints.cloud->getDisplay())
137  {
138  int sfIndex1 =
139  corePoints.cloud->getScalarFieldIndexByName(sf1->getName());
140  corePoints.cloud->setCurrentDisplayedScalarField(sfIndex1);
141  // corePoints.cloud->getDisplay()->redraw();
142  // QCoreApplication::processEvents();
143  }
144  }
145 
146  if (sf2 && !sf1WasAlreadyExisting) {
147  // now perform the math operation
148  if (op != Feature::NO_OPERATION) {
149  if (!PerformMathOp(sf1, sf2, op)) {
150  error = "Failed to perform the MATH operation";
151  success = false;
152  }
153  }
154 
155  // if (keepSF2)
156  // {
157  // sf2->computeMinAndMax();
158  // }
159  // else
160  // {
161  // int sfIndex2 =
162  // corePoints.cloud->getScalarFieldIndexByName(sf2->getName());
163  // if (sfIndex2 >= 0)
164  // {
165  // corePoints.cloud->deleteScalarField(sfIndex2);
166  // }
167  // else
168  // {
169  // assert(false);
170  // sf2->release();
171  // }
172  // sf2 = nullptr;
173  // }
174  }
175 
176  return success;
177 }
178 
180  // use the default keyword + the scale
181  QString description = ToString(type) + "_SC" + QString::number(scale);
182 
183  description += "_" + cloud1Label;
184 
185  if (cloud2 && !cloud2Label.isEmpty()) {
186  description += "_" + cloud2Label;
187 
188  if (op != NO_OPERATION) {
189  description += "_" + OpToString(op);
190  }
191  }
192 
193  return description;
194 }
195 
197  cloudViewer::DgmOctree::NeighboursSet& pointsInNeighbourhood,
198  const CCVector3& queryPoint,
199  double& outputValue) const {
200  outputValue = std::numeric_limits<double>::quiet_NaN();
201 
202  size_t kNN = pointsInNeighbourhood.size();
203  if (kNN == 0) {
204  assert(false);
205  return false;
206  }
207 
208  switch (type) {
209  // features relying on the PCA
210  case PCA1:
211  case PCA2:
212  case PCA3:
213  case SPHER:
214  case LINEA:
215  case PLANA:
216  case VERT: {
218  switch (type) {
219  case PCA1:
221  break;
222  case PCA2:
224  break;
225  case PCA3:
227  break;
228  case SPHER:
230  break;
231  case LINEA:
233  break;
234  case PLANA:
236  break;
237  case VERT:
239  break;
240  default:
241  // impossible
242  assert(false);
243  return false;
244  }
245 
246  cloudViewer::DgmOctreeReferenceCloud neighboursCloud(
247  &pointsInNeighbourhood, static_cast<unsigned>(kNN));
248  cloudViewer::Neighbourhood Z(&neighboursCloud);
249  outputValue = Z.computeFeature(f);
250  } break;
251 
252  case FOM: {
253  cloudViewer::DgmOctreeReferenceCloud neighboursCloud(
254  &pointsInNeighbourhood, static_cast<unsigned>(kNN));
255  cloudViewer::Neighbourhood Z(&neighboursCloud);
256  outputValue = Z.computeMomentOrder1(queryPoint);
257  } break;
258 
259  case Dip:
260  case DipDir:
261  if (kNN >= 3) {
262  cloudViewer::DgmOctreeReferenceCloud neighboursCloud(
263  &pointsInNeighbourhood, static_cast<unsigned>(kNN));
264  cloudViewer::Neighbourhood Z(&neighboursCloud);
265  const CCVector3* N = Z.getLSPlaneNormal();
266  if (N) {
267  // force +Z
268  CCVector3 Np = (N->z < 0 ? -PC_ONE * *N : *N);
269  PointCoordinateType dip_deg, dipDir_deg;
271  dipDir_deg);
272  outputValue = (type == Dip ? dip_deg : dipDir_deg);
273  }
274  }
275  break;
276 
277  case NBPTS:
278  outputValue = static_cast<double>(kNN);
279  break;
280 
281  case ROUGH: {
282  cloudViewer::DgmOctreeReferenceCloud neighboursCloud(
283  &pointsInNeighbourhood, static_cast<unsigned>(kNN));
284  cloudViewer::Neighbourhood Z(&neighboursCloud);
285  outputValue = Z.computeRoughness(queryPoint);
286  } break;
287 
288  case CURV: {
289  cloudViewer::DgmOctreeReferenceCloud neighboursCloud(
290  &pointsInNeighbourhood, static_cast<unsigned>(kNN));
291  cloudViewer::Neighbourhood Z(&neighboursCloud);
292  outputValue = Z.computeCurvature(
293  queryPoint,
295  MEAN_CURV); // TODO: is it really the default one?
296  } break;
297 
298  case ZRANGE:
299  case Zmax:
300  case Zmin:
301  if (kNN >= 2) {
302  PointCoordinateType minZ, maxZ;
303  minZ = maxZ = pointsInNeighbourhood[0].point->z;
304  for (size_t i = 1; i < kNN; ++i) {
305  if (minZ > pointsInNeighbourhood[i].point->z)
306  minZ = pointsInNeighbourhood[i].point->z;
307  else if (maxZ < pointsInNeighbourhood[i].point->z)
308  maxZ = pointsInNeighbourhood[i].point->z;
309  }
310 
311  if (type == ZRANGE) {
312  outputValue = maxZ - minZ;
313  } else if (type == Zmax) {
314  outputValue = maxZ - queryPoint.z;
315  } else if (type == Zmin) {
316  outputValue = queryPoint.z - minZ;
317  } else {
318  // impossible
319  assert(false);
320  }
321  }
322  break;
323 
324  case ANISO:
325  if (kNN >= 3) {
326  cloudViewer::DgmOctreeReferenceCloud neighboursCloud(
327  &pointsInNeighbourhood, static_cast<unsigned>(kNN));
328  cloudViewer::Neighbourhood Z(&neighboursCloud);
329  const CCVector3* G = Z.getGravityCenter();
330  if (G) {
331  double r = sqrt(pointsInNeighbourhood.back().squareDistd);
332  if (r > std::numeric_limits<double>::epsilon()) {
333  double d = (queryPoint - *G).normd();
334  // Ratio of distance to center of mass and radius of
335  // sphere
336  outputValue = d / r;
337  }
338  }
339  }
340  break;
341 
342  // case LINEF:
343  // case ORIENF:
344  default: {
345  CVLog::Warning("Unhandled feature");
346  assert(false);
347  return false;
348  }
349  }
350 
351  return true;
352 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
SF collector.
bool setBehavior(cloudViewer::ScalarField *sf, Behavior behavior)
Type z
Definition: CVGeom.h:137
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
A kind of ReferenceCloud based on the DgmOctree::NeighboursSet structure.
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
ScalarType computeCurvature(const CCVector3 &P, CurvatureType cType)
Computes the curvature of a set of point (by fitting a 2.5D quadric)
ScalarType computeRoughness(const CCVector3 &P, const CCVector3 *roughnessUpDir=nullptr)
const CCVector3 * getGravityCenter()
Returns gravity center.
GeomFeature
Geometric feature computed from eigen values/vectors.
const CCVector3 * getLSPlaneNormal()
Returns best interpolating plane (Least-square) normal vector.
double computeFeature(GeomFeature feature)
Computes the given feature on a set of point.
ScalarType computeMomentOrder1(const CCVector3 &P)
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
static void error(char *msg)
Definition: lsd.c:159
3DMASC classifier
cloudViewer::GenericIndexedCloud * corePoints
Core points descriptor.
Definition: CorePoints.h:39
static bool CheckSFExistence(ccPointCloud *cloud, const char *resultSFName)
virtual bool checkValidity(QString corePointRole, QString &error) const
Checks the feature definition validity.
ccPointCloud * cloud1
ccPointCloud * cloud2
static ScalarType PerformMathOp(double s1, double s2, Operation op)
Performs a mathematical operation between two scalars.
static QString OpToString(Operation op)
double scale
Scale (diameter)
static cloudViewer::ScalarField * PrepareSF(ccPointCloud *cloud, const char *resultSFName, SFCollector *generatedScalarFields, SFCollector::Behavior behavior)
bool computeValue(cloudViewer::DgmOctree::NeighboursSet &pointsInNeighbourhood, const CCVector3 &queryPoint, double &outputValue) const
Compute the feature value on a set of points.
cloudViewer::ScalarField * sf1
Feature values.
virtual bool prepare(const CorePoints &corePoints, QString &error, cloudViewer::GenericProgressCallback *progressCb=nullptr, SFCollector *generatedScalarFields=nullptr) override
Prepares the feature (compute the scalar field, etc.)
virtual bool checkValidity(QString corePointRole, QString &error) const override
Checks the feature definition validity.
virtual bool finish(const CorePoints &corePoints, QString &error) override
Finishes the feature preparation (update the scalar field, etc.)
virtual QString toString() const override
Returns the formatted description.
NeighborhoodFeatureType type
Neighborhood feature type.
cloudViewer::ScalarField * sf2
static QString ToString(NeighborhoodFeatureType type)
Definition: lsd.c:149