ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ContextBasedFeature.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 "ContextBasedFeature.h"
9 
10 // Local
11 #include "q3DMASCTools.h"
12 
13 // qCC_db
14 #include <ecvScalarField.h>
15 
16 // Qt
17 #include <QMutex>
18 
19 #if defined(_OPENMP)
20 #include <omp.h>
21 #endif
22 
23 using namespace masc;
24 
25 bool ContextBasedFeature::checkValidity(QString corePointRole,
26  QString& error) const {
27  if (!Feature::checkValidity(corePointRole, error)) {
28  return false;
29  }
30 
31  if (type == Invalid) {
32  assert(false);
33  error = "invalid feature type";
34  return false;
35  }
36 
37  if (!cloud1) {
38  error = "one cloud is required to compute context-based features";
39  return false;
40  }
41 
43  if (!classifSF) {
44  error = QString("Context cloud (%1) has no classification field")
45  .arg(cloud1Label);
46  return false;
47  }
48  if (classifSF->size() < cloud1->size()) {
49  error = QString("Context cloud (%1) has an invalid classification "
50  "field")
51  .arg(cloud1Label);
52  return false;
53  }
54 
55  if (!scaled() && kNN < 1) {
56  error = QString("invalid kNN value for a scale-less context-based "
57  "feature (%1)")
58  .arg(kNN);
59  return false;
60  }
61 
62  return true;
63 }
64 
66  const CorePoints& corePoints,
67  QString& errorMessage,
68  cloudViewer::GenericProgressCallback* progressCb /*=nullptr*/,
69  SFCollector* generatedScalarFields /*=nullptr*/) {
70  if (!corePoints.cloud) {
71  // invalid input
72  assert(false);
73  errorMessage = "internal error (no input core points)";
74  return false;
75  }
76 
77  if (!cloud1) {
78  // invalid input
79  assert(false);
80  errorMessage = "internal error (no contextual cloud)";
81  return false;
82  }
83 
84  if (!checkValidity(corePoints.role, errorMessage)) {
85  assert(false);
86  return false;
87  }
88 
90  if (!classifSF || classifSF->size() < cloud1->size()) {
91  assert(false);
92  // already checked by 'checkValidity'
93  return false;
94  }
97 
98  // build the final SF name
99  QString typeStr = ToString(type);
100  QString resultSFName =
101  typeStr + "_" + cloud1Label + "_" + QString::number(ctxClassLabel);
102  if (scaled()) {
103  resultSFName += "@" + QString::number(scale);
104  } else {
105  resultSFName += "@kNN=" + QString::number(kNN);
106  }
107 
108  // and the scalar field
109  assert(!sf);
111  CheckSFExistence(corePoints.cloud, qPrintable(resultSFName));
112  sf = PrepareSF(corePoints.cloud, qPrintable(resultSFName),
113  generatedScalarFields, SFCollector::CAN_REMOVE);
114  if (!sf) {
115  errorMessage = QString("[ContextBasedFeature::prepare] Failed to "
116  "prepare scalar %1 @ scale %2")
117  .arg(resultSFName)
118  .arg(scale);
119  return false;
120  }
121  source.name = sf->getName();
122 
123  // NOT NECESSARY IF THE VALUE IS ALREADY COMPUTED
124  if (!scaled() && !sf1WasAlreadyExisting) // with 'kNN' neighbors, we can
125  // compute the values right away
126  {
127  unsigned pointCount = corePoints.size();
128  QString logMessage = "Computing " + typeStr + " on cloud " +
129  corePoints.cloud->getName() + " (" +
130  QString::number(pointCount) + " points)" +
131  " with context cloud " + cloud1Label + " (class " +
132  QString::number(ctxClassLabel) + ")";
133 
134  // first: look for the number of points in the relevent class
135  const ScalarType fClass = static_cast<ScalarType>(ctxClassLabel);
136  unsigned classCount = 0;
137  for (unsigned i = 0; i < classifSF->size(); ++i) {
138  if (classifSF->getValue(i) == fClass) ++classCount;
139  }
140 
141  if (classCount >= static_cast<unsigned>(kNN)) {
142  ccPointCloud classCloud;
143  if (!classCloud.reserve(classCount)) {
144  errorMessage = "Not enough memory";
145  return false;
146  }
147 
148  for (unsigned i = 0; i < classifSF->size(); ++i) {
149  if (classifSF->getValue(i) == fClass) {
150  classCloud.addPoint(*cloud1->getPoint(i));
151  }
152  }
153 
154  // compute the octree
155  CVLog::Print(QString("Computing octree of class %1 (%2 points)")
156  .arg(ctxClassLabel)
157  .arg(classCount));
158  ccOctree::Shared classOctree = classCloud.computeOctree(progressCb);
159  if (!classOctree) {
160  errorMessage =
161  "[ContextBasedFeature::prepare] Failed to compute "
162  "octree (not enough memory?)";
163  return false;
164  }
165 
166  // now extract the neighborhoods
167  unsigned char octreeLevel =
168  classOctree->findBestLevelForAGivenPopulationPerCell(
169  static_cast<unsigned>(std::max(3, kNN)));
170  CVLog::Print(QString("[Initial octree level] level = %1")
171  .arg(octreeLevel));
172 
173  if (progressCb) {
174  progressCb->setMethodTitle(qPrintable("Compute " + typeStr));
175  progressCb->setInfo(qPrintable(logMessage));
176  }
177  CVLog::Print(logMessage);
178  cloudViewer::NormalizedProgress nProgress(progressCb, pointCount);
179 
180  QMutex mutex;
181  double meanNeighborhoodSize = 0;
182  int tenth = pointCount / 10;
183  bool cancelled = false;
184 #ifndef _DEBUG
185 #if defined(_OPENMP)
186 #pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2))
187 #endif
188 #endif
189  for (int i = 0; i < static_cast<int>(pointCount); ++i) {
190  if (!cancelled) {
191  const CCVector3* P = corePoints.cloud->getPoint(i);
192  cloudViewer::ReferenceCloud Yk(&classCloud);
193  double maxSquareDist = 0;
194 
195  ScalarType s = NAN_VALUE;
196 
197  int neighborhoodSize = 0;
198  if (classOctree->findPointNeighbourhood(
199  P, &Yk, static_cast<unsigned>(kNN), octreeLevel,
200  maxSquareDist, 0, &neighborhoodSize) >=
201  static_cast<unsigned>(kNN)) {
202  CCVector3d sumQ(0, 0, 0);
203  for (int k = 0; k < kNN; ++k) {
204  sumQ += CCVector3d::fromArray(Yk.getPoint(k)->u);
205  }
206 
207  switch (type) {
208  case DZ:
209  s = static_cast<ScalarType>(P->z -
210  sumQ.z / kNN);
211  break;
212  case DH:
213  s = static_cast<ScalarType>(
214  sqrt(pow(P->x - sumQ.x / kNN, 2.0) +
215  pow(P->y - sumQ.y / kNN, 2.0)));
216  break;
217  }
218  }
219 
220  if (i && (i % tenth) == 0) {
221  double density = meanNeighborhoodSize / tenth;
222  if (density < 1.1) {
223  if (octreeLevel + 1 <
225  ++octreeLevel;
226  } else
227  while (density > 2.9) {
228  if (octreeLevel <= 5) break;
229  --octreeLevel;
230  density /= 2.0;
231  }
232  CVLog::Print(QString("[Adaptative octree level] Mean "
233  "neighborhood size: %1 --> new "
234  "level = %2")
235  .arg(meanNeighborhoodSize / tenth)
236  .arg(octreeLevel));
237  meanNeighborhoodSize = 0;
238  } else {
239  meanNeighborhoodSize += neighborhoodSize;
240  }
241 
242  sf->setValue(i, s);
243 
244  if (progressCb) {
245  mutex.lock();
246  cancelled = !nProgress.oneStep();
247  mutex.unlock();
248  if (cancelled) {
249  // process cancelled by the user
250  errorMessage =
251  "[ContextBasedFeature] Process cancelled";
252  }
253  }
254  }
255  }
256 
257  if (progressCb) {
258  progressCb->stop();
259  }
260 
261  if (cancelled) {
262  sf->computeMinAndMax();
263  return false;
264  }
265 
266  } // classCount >= kNN
267  else // classCount < kNN
268  {
269  // specific case: not enough points of this class in the whole
270  // cloud! sf->fill(NAN_VALUE); //already the case
272  QString("Cloud %1 has less than %2 points of class %3")
273  .arg(cloud1Label)
274  .arg(classCount)
275  .arg(ctxClassLabel));
276  }
277 
278  sf->computeMinAndMax();
279  }
280 
281  return true;
282 }
283 
285  cloudViewer::DgmOctree::NeighboursSet& pointsInNeighbourhood,
286  const CCVector3& queryPoint,
287  ScalarType& outputValue) const {
288  const ScalarType fClass = static_cast<ScalarType>(ctxClassLabel);
289 
290  CCVector3d sumQ(0, 0, 0);
291  unsigned validCount = 0;
292  for (cloudViewer::DgmOctree::PointDescriptor& Pd : pointsInNeighbourhood) {
293  // we only consider points with the right class!!!
294  if (cloud1->getPointScalarValue(Pd.pointIndex) != fClass) continue;
295  sumQ += CCVector3d::fromArray(Pd.point->u);
296  ++validCount;
297  }
298 
299  if (validCount == 0) {
300  outputValue = NAN_VALUE;
301  return true;
302  }
303 
304  switch (type) {
305  case DZ:
306  outputValue =
307  static_cast<ScalarType>(queryPoint.z - sumQ.z / validCount);
308  break;
309  case DH:
310  outputValue = static_cast<ScalarType>(
311  sqrt(pow(queryPoint.x - sumQ.x / validCount, 2.0) +
312  pow(queryPoint.y - sumQ.y / validCount, 2.0)));
313  break;
314  default:
315  assert(false);
316  outputValue = NAN_VALUE;
317  return false;
318  }
319 
320  return true;
321 }
322 
324  if (!corePoints.cloud) {
325  // invalid input
326  assert(false);
327  error = "internal error (no input core points)";
328  return false;
329  }
330 
331  bool success = true;
332 
333  if (sf) {
334  sf->computeMinAndMax();
335 
336  // update display
337  // if (corePoints.cloud->getDisplay())
338  {
339  int sfIndex =
340  corePoints.cloud->getScalarFieldIndexByName(sf->getName());
341  corePoints.cloud->setCurrentDisplayedScalarField(sfIndex);
342  // corePoints.cloud->getDisplay()->redraw();
343  // QCoreApplication::processEvents();
344  }
345  }
346 
347  return success;
348 }
349 
351  // use the default keyword + number of neighbors + the scale + the context
352  // class
353  QString str = ToString(type);
354  if (!scaled()) {
355  if (kNN != 1) str += QString::number(kNN);
356  str += "_SC0";
357  } else {
358  str += "_SC" + QString::number(scale);
359  }
360 
361  str += "_" + cloud1Label + "_" + QString::number(ctxClassLabel);
362 
363  return str;
364 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h: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
SF collector.
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
Definition: DgmOctree.h:67
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
virtual unsigned size() const =0
Returns the number of points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
virtual void stop()=0
Notifies the fact that the process has ended.
virtual void setInfo(const char *infoStr)=0
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle)=0
Notifies the algorithm title.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
void setCurrentOutScalarField(int index)
Sets the OUTPUT scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
ScalarType getPointScalarValue(unsigned pointIndex) const override
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
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 & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
static cloudViewer::ScalarField * GetClassificationSF(const ccPointCloud *cloud)
Helper: returns the classification SF associated to a cloud (if any)
int max(int a, int b)
Definition: cutil_math.h:48
static void error(char *msg)
Definition: lsd.c:159
3DMASC classifier
cloudViewer::NormalizedProgress * nProgress
cloudViewer::GenericIndexedCloud * corePoints
unsigned char octreeLevel
Structure used during nearest neighbour search.
Definition: DgmOctree.h:101
virtual QString toString() const override
Returns the formatted description.
cloudViewer::ScalarField * sf
The computed scalar.
virtual bool prepare(const CorePoints &corePoints, QString &error, cloudViewer::GenericProgressCallback *progressCb=nullptr, SFCollector *generatedScalarFields=nullptr) override
Prepares the feature (compute the scalar field, etc.)
bool computeValue(cloudViewer::DgmOctree::NeighboursSet &pointsInNeighbourhood, const CCVector3 &queryPoint, ScalarType &outputValue) const
Compute the feature value on a set of points.
virtual bool finish(const CorePoints &corePoints, QString &error) override
Finishes the feature preparation (update the scalar field, etc.)
static QString ToString(ContextBasedFeatureType type)
ContextBasedFeatureType type
Neighborhood feature type.
int ctxClassLabel
Context class (label)
virtual bool checkValidity(QString corePointRole, QString &error) const override
Checks the feature definition validity.
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
bool scaled() const
Returns whether the feature has an associated scale.
double scale
Scale (diameter)
static cloudViewer::ScalarField * PrepareSF(ccPointCloud *cloud, const char *resultSFName, SFCollector *generatedScalarFields, SFCollector::Behavior behavior)