ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qCanupoTools.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 "qCanupoTools.h"
9 
10 // Local
11 #include "trainer.h"
12 
13 // cloudViewer
15 #include <Neighbourhood.h>
16 #include <ParallelSort.h>
17 
18 // CV_DB_LIB
19 #include <ecvGenericPointCloud.h>
20 #include <ecvMainAppInterface.h>
21 #include <ecvOctree.h>
22 #include <ecvPointCloud.h>
23 #include <ecvProgressDialog.h>
24 #include <ecvScalarField.h>
25 
26 // Qt
27 #include <QApplication>
28 #include <QComboBox>
29 #include <QMainWindow>
30 #include <QProgressDialog>
31 #include <QtConcurrentMap>
32 
33 // ComputeCorePointsDescriptors parameters
34 static struct {
38  unsigned char octreeLevel;
41 
45 
46  ScaleParamsComputer* computer; // the per-scale parameters computer
47 
48  std::vector<ccScalarField*>* roughnessSFs; // for test
49 
51 
54 void ComputeCorePointDescriptor(unsigned index) {
55  if (s_computeCorePointsDescParams.processCanceled) return;
56 
57  const CCVector3* P =
58  s_computeCorePointsDescParams.corePoints->getPoint(index);
60 
61  // extract the neighbors (maximum radius)
62  float maxRadius =
63  s_computeCorePointsDescParams.descriptors->scales().front() / 2;
64  int n = s_computeCorePointsDescParams.octree
65  ->getPointsInSphericalNeighbourhood(
66  *P, maxRadius, neighbours,
67  s_computeCorePointsDescParams.octreeLevel);
68 
69  if (n != 0) {
70  size_t scaleCount =
71  s_computeCorePointsDescParams.descriptors->scales().size();
72 
73  // get reference on corresponding descriptor
74  assert(s_computeCorePointsDescParams.descriptors->size() > index);
75  CorePointDesc& desc =
76  s_computeCorePointsDescParams.descriptors->at(index);
77 
78  unsigned dimPerScale =
79  s_computeCorePointsDescParams.descriptors->dimPerScale();
80  assert(desc.params.size() == scaleCount * dimPerScale);
81 
82  // init the whole neighborhood subset (we will prune it each time)
84  s_computeCorePointsDescParams.sourceCloud);
85  {
86  if (!subset.reserve(n)) {
87  // not enough memory!
88  s_computeCorePointsDescParams.errorOccurred = true;
89  s_computeCorePointsDescParams.processCanceled =
90  true; // to make the loop stop!
91  return;
92  }
93 
94  // sort the neighbors by increasing distance
95  ParallelSort(neighbours.begin(), neighbours.end(),
97 
98  for (int j = 0; j < n; ++j) {
99  subset.addPointIndex(neighbours[j].pointIndex);
100  }
101  }
102 
103  s_computeCorePointsDescParams.computer->reset();
104 
105  for (size_t i = 0; i < scaleCount; ++i) {
106  const double radius =
107  s_computeCorePointsDescParams.descriptors->scales()[i] /
108  2; // we start from the biggest
109 
110  if (i != 0) {
111  // trim the points that don't fall in the current neighborhood
112  double squareRadius = radius * radius;
113  cloudViewer::DgmOctree::PointDescriptor fakeDesc(nullptr, 0,
114  squareRadius);
115  cloudViewer::DgmOctree::NeighboursSet::iterator up =
116  std::upper_bound(neighbours.begin(), neighbours.end(),
117  fakeDesc,
119  PointDescriptor::distComp);
120  if (up != neighbours.end()) {
121  size_t count = std::max<size_t>(1, up - neighbours.begin());
122 
123  neighbours.resize(count);
124  subset.resize(static_cast<unsigned>(count));
125  }
126  }
127 
128  // optional: compute per-level roughness
129  if (s_computeCorePointsDescParams.roughnessSFs) {
130  ScalarType roughness = NAN_VALUE;
131 
132  if (subset.size() >= 3) {
133  // to compute we take the nearest point to the query point
134  // as 'central' point warning: it should work in most of the
135  // cases, apart if the core points have nothing to do with
136  // the global cloud!!!
137  unsigned lastIndex = subset.size() - 1;
138  subset.swap(0, lastIndex);
139 
140  // temporarily remove the central point (now at the end)
141  unsigned globalIndex =
142  subset.getPointGlobalIndex(lastIndex);
143  subset.resize(lastIndex);
144 
145  cloudViewer::Neighbourhood Z(&subset);
146  const PointCoordinateType* lsPlane = Z.getLSPlane();
147  if (lsPlane) {
148  // distance to the LS plane fitted on the nearest
149  // neighbors
150  const CCVector3* centralPoint =
152  ->getPoint(globalIndex);
153  roughness =
155  computePoint2PlaneDistance(
156  centralPoint, lsPlane));
157  }
158 
159  // put back the point at its original place!
160  subset.addPointIndex(globalIndex);
161  subset.swap(0, lastIndex);
162  }
163 
164  assert(s_computeCorePointsDescParams.roughnessSFs->size() ==
165  scaleCount);
166  ccScalarField* sf =
167  s_computeCorePointsDescParams.roughnessSFs->at(i);
168  assert(sf && sf->currentSize() > index);
169  sf->setValue(index, roughness);
170  }
171 
172  bool invalidScale = false;
173  if (!s_computeCorePointsDescParams.computer->computeScaleParams(
174  subset, radius, &(desc.params[i * dimPerScale]),
175  invalidScale)) {
176  // an error occurred!
177  s_computeCorePointsDescParams.errorOccurred = true;
178  s_computeCorePointsDescParams.processCanceled =
179  true; // to make the loop stop!
180  return;
181  }
182 
183  if (invalidScale) {
184  s_computeCorePointsDescParams.invalidDescriptors = true;
185  // no need to compute the remaining scales!
186  for (size_t j = i + 1; j < scaleCount; ++j) {
187  // copy the same parameters for all scales (see CANUPO
188  // paper)
189  memcpy(&(desc.params[j * dimPerScale]),
190  &(desc.params[i * dimPerScale]),
191  sizeof(float) * dimPerScale);
192  }
193  // neighbours.clear();
194  // subset.clear(true);
195  break;
196  }
197  }
198  } else {
199  // if the widest neighborhood has less than 3 points, we can't compute a
200  // valid descriptor!
201  s_computeCorePointsDescParams.invalidDescriptors = true;
202  }
203 
204  // progress notification
205  if (s_computeCorePointsDescParams.nProgress &&
206  !s_computeCorePointsDescParams.nProgress->oneStep()) {
207  s_computeCorePointsDescParams.processCanceled = true;
208  }
209 }
210 
213  CorePointDescSet& corePointsDescriptors,
215  const std::vector<float>& sortedScales,
216  bool& invalidDescriptors,
217  QString& error, // if any
218  unsigned descriptorID /*=DESC_DIMENSIONALITY*/,
219  int maxThreadCount /*=0*/,
220  cloudViewer::GenericProgressCallback* progressCb /*=0*/,
221  cloudViewer::DgmOctree* inputOctree /*=0*/,
222  std::vector<ccScalarField*>* roughnessSFs /*=0*/) {
223  assert(corePoints && sourceCloud);
224  assert(!sortedScales.empty());
225 
226  invalidDescriptors = true;
227  error = QString();
228 
229  unsigned corePtsCount = corePoints->size();
230  if (corePtsCount == 0) {
231  error = "No core points?!";
232  return false;
233  }
234  size_t scaleCount = sortedScales.size();
235  if (scaleCount == 0) {
236  error = "No scales?!";
237  return false;
238  }
239 
240  // descriptor (computer)
242  ScaleParamsComputer::GetByID(descriptorID);
243  if (!s_computeCorePointsDescParams.computer) {
244  error = QString("Unhandled descriptor ID (%1)!").arg(descriptorID);
245  return false;
246  }
247  if (s_computeCorePointsDescParams.computer->needSF() &&
249  error = "Couldn't find a scalar field for core points!";
250  return false;
251  }
252 
253  corePointsDescriptors.setDescriptorID(descriptorID);
254  corePointsDescriptors.setDimPerScale(
255  s_computeCorePointsDescParams.computer->dimPerScale());
256 
257  cloudViewer::DgmOctree* theOctree = inputOctree;
258  if (!theOctree) {
259  theOctree = new cloudViewer::DgmOctree(sourceCloud);
260  if (theOctree->build(progressCb) == 0) {
261  error = "Failed to build the octree (not enough memory?)";
262  delete theOctree;
263  return false;
264  }
265  }
266 
267  cloudViewer::NormalizedProgress nProgress(progressCb, corePtsCount);
268  if (progressCb) {
269  if (progressCb->textCanBeEdited()) {
270  progressCb->setInfo(
271  qPrintable(QString("Core points: %1\nSource points: %2")
272  .arg(corePtsCount)
273  .arg(sourceCloud->size())));
274  progressCb->setMethodTitle("Computing descriptors");
275  }
276  progressCb->start();
277  QApplication::processEvents();
278  }
279 
280  // reserve memory for descriptors storage
281  bool success = true;
282  try {
283  corePointsDescriptors.resize(corePtsCount);
284  } catch (const std::bad_alloc&) {
285  success = false;
286  }
287 
288  if (success)
289  success = corePointsDescriptors.setScales(
290  sortedScales); // automatically resizes the 'params' structure
291  // for each core point
292  if (!success) {
293  error = "Not enough memory!";
294  if (!inputOctree) delete theOctree;
295  return false;
296  }
297 
298  PointCoordinateType biggestRadius =
299  sortedScales.front() / 2; // we extract the biggest neighborhood
300  unsigned char octreeLevel =
302  biggestRadius);
303 
305  s_computeCorePointsDescParams.descriptors = &corePointsDescriptors;
307  s_computeCorePointsDescParams.octree = theOctree;
309  s_computeCorePointsDescParams.nProgress = progressCb ? &nProgress : nullptr;
310  s_computeCorePointsDescParams.processCanceled = false;
311  s_computeCorePointsDescParams.errorOccurred = false;
312  s_computeCorePointsDescParams.invalidDescriptors = false;
314 
315  // we try the parallel way (if we have enough memory)
316  bool useParallelStrategy = true;
317 #ifdef _DEBUG
318  useParallelStrategy = false;
319 #endif
320 
321  std::vector<unsigned> corePointsIndexes;
322  if (useParallelStrategy) {
323  try {
324  corePointsIndexes.resize(corePtsCount);
325  } catch (const std::bad_alloc&) {
326  // not enough memory
327  useParallelStrategy = false;
328  }
329  }
330 
331  if (useParallelStrategy) {
332  for (unsigned i = 0; i < corePtsCount; ++i) {
333  corePointsIndexes[i] = i;
334  }
335 
336  if (maxThreadCount == 0) {
337  maxThreadCount = QThread::idealThreadCount();
338  }
339  assert(maxThreadCount <= QThread::idealThreadCount());
340  QThreadPool::globalInstance()->setMaxThreadCount(maxThreadCount);
341  QtConcurrent::blockingMap(corePointsIndexes,
343  } else {
344  // manually call the static per-point method!
345  for (unsigned i = 0; i < corePtsCount; ++i) {
347  }
348  }
349 
350  // output flags
351  bool wasCanceled = s_computeCorePointsDescParams.processCanceled;
352  bool errorOccurred = s_computeCorePointsDescParams.errorOccurred;
353  if (errorOccurred)
354  error = "An error occurred during descriptors computation!";
355  else if (wasCanceled)
356  error = "Process has been cancelled by the user";
358 
359  // reset static parameters (just to be clean ;)
360  s_computeCorePointsDescParams.corePoints = nullptr;
361  s_computeCorePointsDescParams.descriptors = nullptr;
362  s_computeCorePointsDescParams.sourceCloud = nullptr;
363  s_computeCorePointsDescParams.octree = nullptr;
364  s_computeCorePointsDescParams.octreeLevel = 0;
365  s_computeCorePointsDescParams.nProgress = nullptr;
366  s_computeCorePointsDescParams.processCanceled = false;
367  s_computeCorePointsDescParams.errorOccurred = false;
368  s_computeCorePointsDescParams.invalidDescriptors = false;
369  s_computeCorePointsDescParams.computer = nullptr;
370 
371  if (progressCb) {
372  progressCb->stop();
373  }
374 
375  if (!inputOctree) delete theOctree;
376 
377  return !errorOccurred && !wasCanceled;
378 }
379 
380 bool qCanupoTools::CompareVectors(const std::vector<float>& first,
381  const std::vector<float>& second) {
382  // check scales
383  size_t firstCount = first.size();
384  if (firstCount != second.size()) return false;
385 
386  for (size_t i = 0; i < firstCount; ++i)
387  if (!Fpeq<float>(first[i], second[i])) return false;
388 
389  return true;
390 }
391 
392 size_t qCanupoTools::TestVectorsOverlap(const std::vector<float>& first,
393  const std::vector<float>& second) {
394  size_t size1 = first.size();
395  size_t size2 = second.size();
396  size_t minCount = std::min(size1, size2);
397 
398  size_t i = 0;
399  for (i = 0; i < minCount; ++i)
400  if (!Fpeq<float>(first[size1 - 1 - i], second[size2 - 1 - i])) break;
401 
402  return i;
403 }
404 
406  if (!obj) {
407  assert(false);
408  return QString();
409  }
410 
411  QString name = obj->getName();
412  if (name.isEmpty()) name = "unnamed";
413  name += QString(" [ID %1]").arg(obj->getUniqueID());
414 
415  return name;
416 }
417 
419  ccHObject* dbRoot) {
420  assert(comboBox && dbRoot);
421  if (!comboBox || !dbRoot) {
422  assert(false);
423  return nullptr;
424  }
425 
426  // return the cloud currently selected in the combox box
427  int index = comboBox->currentIndex();
428  if (index < 0) {
429  assert(false);
430  return nullptr;
431  }
432  unsigned uniqueID = comboBox->itemData(index).toUInt();
433  ccHObject* item = dbRoot->find(uniqueID);
434  if (!item || !item->isA(CV_TYPES::POINT_CLOUD)) {
435  assert(false);
436  return nullptr;
437  }
438  return static_cast<ccPointCloud*>(item);
439 }
440 
442  const CorePointDescSet& descriptors1,
443  const CorePointDescSet& descriptors2,
444  const std::vector<float>& scales,
447 
448  if (descriptors1.empty() || descriptors2.empty()) {
449  // empty descriptors?
450  return false;
451  }
452 
453  // Evaluate on 1st class
454  {
455  size_t nsamples1 = descriptors1.size();
456  double sumd = 0;
457  double sumd2 = 0;
458  for (size_t i = 0; i < nsamples1; ++i) {
459  float d = classifier.classify(descriptors1[i]);
460  if (d > 0)
461  params.false1++;
462  else
463  params.true1++;
464 
465  sumd += static_cast<double>(d);
466  sumd2 += static_cast<double>(d * d);
467  }
468  params.mu1 = sumd / static_cast<double>(nsamples1);
469  params.var1 = sumd2 / static_cast<double>(nsamples1) -
470  params.mu1 * params.mu1;
471  }
472 
473  // Evaluate on 2nd class
474  {
475  size_t nsamples2 = descriptors2.size();
476  double sumd = 0;
477  double sumd2 = 0;
478  for (size_t i = 0; i < nsamples2; ++i) {
479  float d = classifier.classify(descriptors2[i]);
480  if (d < 0)
481  params.false2++;
482  else
483  params.true2++;
484 
485  sumd += static_cast<double>(d);
486  sumd2 += static_cast<double>(d * d);
487  }
488  params.mu2 = sumd / static_cast<double>(nsamples2);
489  params.var2 = sumd2 / static_cast<double>(nsamples2) -
490  params.mu2 * params.mu2;
491  }
492 
493  return true;
494 }
495 
497  Classifier& classifier,
498  const CorePointDescSet& descriptors1,
499  const CorePointDescSet& descriptors2,
500  const std::vector<float>& scales,
501  ccPointCloud* mscCloud,
502  const CorePointDescSet* evaluationDescriptors /*=0*/,
503  ecvMainAppInterface* app /*=0*/) {
504  // fuse both descriptor sets in a single 'dlib' structure
505  size_t nsamples1 = descriptors1.size();
506  size_t nsamples2 = descriptors2.size();
507 
508  if (nsamples1 == 0 || nsamples2 == 0) {
509  if (app)
510  app->dispToConsole("Invalid descriptors!",
512  return false;
513  }
514 
515  assert(descriptors1.descriptorID() == descriptors2.descriptorID());
516  classifier.descriptorID = descriptors1.descriptorID();
517 
518  unsigned dimPerScale = descriptors1.dimPerScale();
519  assert(dimPerScale == descriptors2.dimPerScale());
520  classifier.dimPerScale = dimPerScale;
521 
522  // we use the specified 'scales' (not necessarily all descriptors will be
523  // used!)
524  assert((descriptors1.front().params.size() % dimPerScale) == 0);
525  size_t paramsCount = descriptors1.front().params.size() / dimPerScale;
526  size_t scaleCount = scales.size();
527  assert(scaleCount <= paramsCount);
528  scaleCount = std::min(scaleCount, paramsCount);
529 
530  classifier.scales = scales;
531  // already set outside!
532  // classifier.class1 = 1;
533  // classifier.class2 = 2;
534 
535  size_t nsamples = nsamples1 + nsamples2;
536  size_t fdim = scaleCount * dimPerScale;
537 
538  std::vector<LDATrainer::sample_type> samples;
539  std::vector<float> labels;
540  try {
541  LDATrainer::sample_type nanSample;
542  nanSample.set_size(fdim, 1);
543  samples.resize(nsamples, nanSample);
544  labels.resize(
545  nsamples,
546  1); // labels for class#1 will be changed to -1 (see below)
547  } catch (const std::bad_alloc&) {
548  if (app)
549  app->dispToConsole("Not enough memory!",
551  return false;
552  }
553 
554  // add class #1 data
555  {
556  for (size_t i = 0; i < nsamples1; ++i) {
557  const CorePointDesc& desc = descriptors1[i];
558  LDATrainer::sample_type& sample = samples[i];
559  // assert(scaleCount <= paramsCount); //already tested above
560  size_t shift =
561  (paramsCount - scaleCount) *
562  dimPerScale; // if we use less scales than parameters
563  for (size_t j = 0; j < fdim; ++j) {
564  sample(j) = desc.params[shift + j];
565  }
566  // class #1 is labelled with '-1'
567  labels[i] = -1;
568  }
569  }
570  // add class #2 data
571  {
572  for (size_t i = 0; i < nsamples2; ++i) {
573  const CorePointDesc& desc = descriptors2[i];
574  LDATrainer::sample_type& sample = samples[nsamples1 + i];
575  // assert(scaleCount <= paramsCount); //already tested above
576  size_t shift =
577  (paramsCount - scaleCount) *
578  dimPerScale; // if we use less scales than parameters
579  for (size_t j = 0; j < fdim; ++j) {
580  sample(j) = desc.params[shift + j];
581  }
582  // class #2 is labelled with '1' (already done above)
583  // labels[nsamples1+i] = 1;
584  }
585  }
586 
587  // Computing the two best projection directions
588  QMainWindow* parentWindow = (app ? app->getMainWindow() : nullptr);
589  LDATrainer trainer;
590  {
591  QProgressDialog tempProgressDlg(
592  "LDA (step #1) in progress... please wait...", QString(), 0, 0,
593  parentWindow);
594  tempProgressDlg.show();
595  QApplication::processEvents();
596 
597  // shuffle before internal cross-validation to spread instances of each
598  // class
599  dlib::randomize_samples(samples, labels);
600  try {
601  trainer.train(10, samples, labels);
602  } catch (...) {
603  if (app)
604  app->dispToConsole(
605  "Oups, it seems the LDA classifier just crashed!",
607  return false;
608  }
609  }
610 
611  // get the projections of each sample on the first classifier direction
612  std::vector<float> proj1;
613  {
614  try {
615  proj1.resize(nsamples);
616  } catch (const std::bad_alloc&) {
617  if (app)
618  app->dispToConsole("Not enough memory!",
620  return false;
621  }
622 
623  try {
624  for (size_t i = 0; i < nsamples; ++i) {
625  proj1[i] = static_cast<float>(trainer.predict(samples[i]));
626  }
627  } catch (...) {
628  if (app)
629  app->dispToConsole(
630  "Oups, it seems the LDA classifier just crashed!",
632  return false;
633  }
634 
635  // std::pair<std::vector<float>::const_iterator,
636  // std::vector<float>::const_iterator> mm =
637  // std::minmax_element(proj1.begin(),proj1.end());
638  // m_app->dispToConsole(QString("Min/max(proj1) = (%1 ,
639  // %2)").arg(*mm.first).arg(*mm.second));
640  }
641 
642  dlib::matrix<LDATrainer::sample_type, 0, 1> basis;
643  {
644  basis.set_size(fdim);
645  for (size_t i = 0; i < fdim; ++i) {
646  basis(i).set_size(fdim);
647  for (size_t j = 0; j < fdim; ++j) basis(i)(j) = 0;
648  basis(i)(i) = 1;
649  }
650  }
652  {
653  w_vect.set_size(fdim);
654  for (size_t i = 0; i < fdim; ++i)
655  w_vect(i) = static_cast<float>(trainer.m_weights[i]);
656  }
657 
658  GramSchmidt(basis, w_vect);
659 
660  // Determining orthogonal direction
661  std::vector<LDATrainer::sample_type> samples_reduced;
662  {
663  try {
664  samples_reduced.resize(nsamples);
665  } catch (const std::bad_alloc&) {
666  if (app)
667  app->dispToConsole("Not enough memory!",
669  return false;
670  }
671 
672  for (size_t i = 0; i < nsamples; ++i)
673  samples_reduced[i].set_size(fdim - 1);
674  // project the data onto the hyperplane so as to get the second
675  // direction
676  for (size_t si = 0; si < nsamples; ++si)
677  for (size_t i = 1; i < fdim; ++i)
678  samples_reduced[si](i - 1) = dlib::dot(samples[si], basis(i));
679  }
680 
681  LDATrainer orthoTrainer;
682  try {
683  QProgressDialog tempProgressDlg(
684  "LDA (step #2) in progress... please wait...", QString(), 0, 0,
685  parentWindow);
686  tempProgressDlg.show();
687  QApplication::processEvents();
688 
689  orthoTrainer.train(10, samples_reduced, labels);
690  } catch (...) {
691  if (app)
692  app->dispToConsole(
693  "Oups, it seems the LDA classifier just crashed!",
695  return false;
696  }
697 
698  // convert back the classifier weights into the original space
699  {
700  try {
701  orthoTrainer.m_weights.resize(fdim + 1);
702  } catch (const std::bad_alloc&) {
703  if (app)
704  app->dispToConsole("Not enough memory!",
706  return false;
707  }
708  orthoTrainer.m_weights[fdim] = orthoTrainer.m_weights[fdim - 1];
709 
710  size_t i = 0;
711  for (i = 0; i < fdim; ++i) w_vect(i) = 0;
712  for (i = 1; i < fdim; ++i)
713  w_vect += orthoTrainer.m_weights[i - 1] * basis(i);
714  for (i = 0; i < fdim; ++i) orthoTrainer.m_weights[i] = w_vect(i);
715  }
716 
717  std::vector<float> proj2;
718  {
719  try {
720  proj2.resize(nsamples);
721  } catch (const std::bad_alloc&) {
722  if (app)
723  app->dispToConsole("Not enough memory!",
725  return false;
726  }
727 
728  try {
729  for (size_t i = 0; i < nsamples; ++i) {
730  proj2[i] = static_cast<float>(orthoTrainer.predict(samples[i]));
731  }
732  } catch (...) {
733  if (app)
734  app->dispToConsole(
735  "Oups, it seems the LDA classifier just crashed!",
737  return false;
738  }
739 
740  // std::pair<std::vector<float>::const_iterator,
741  // std::vector<float>::const_iterator> mm =
742  // std::minmax_element(proj2.begin(),proj2.end());
743  // m_app->dispToConsole(QString("Min/max(proj2) = (%1 ,
744  // %2)").arg(*mm.first).arg(*mm.second));
745  }
746 
747  // compute the reference points for orienting the classifier boundaries
748  // pathological cases are possible where an arbitrary point in the (>0,>0)
749  // quadrant is not in the +1 class for example
750  // here, just use the mean of the classes
751  ComputeReferencePoints(classifier.refPointPos, classifier.refPointNeg,
752  proj1, proj2, labels);
753 
754  classifier.weightsAxis1 = trainer.m_weights;
755  classifier.weightsAxis2 = orthoTrainer.m_weights;
756 
757  if (true) {
758  // Same as Brodu's code:
759  // Experimental: dilatation to highlight the internal data structure
760  if (!DilateClassifier(classifier, proj1, proj2, labels, samples,
761  trainer, orthoTrainer)) {
762  if (app)
763  app->dispToConsole("Not enough memory!",
765  return false;
766  }
767  }
768 
769  // proceed to boundary evaluation
770  {
771  Classifier::Point2D boundaryCenter(0, 0);
772  Classifier::Point2D boundaryDir(0, 1);
773 
774  assert(mscCloud);
775  if (!mscCloud) {
776  if (app)
777  app->dispToConsole("[Internal error] Invalid output MSC cloud!",
779  return false;
780  }
781  // if we have no 'evaluation' cloud, we'll add it to the sum of the two
782  // input clouds
783  size_t cloudSize = nsamples;
784  if (evaluationDescriptors) cloudSize += evaluationDescriptors->size();
785  mscCloud->clear();
786  if (!mscCloud->reserve(cloudSize)) {
787  if (app)
788  app->dispToConsole(
789  "Not enough memory to determine the classifier "
790  "behavior!",
792  return false;
793  }
794 
795  bool hasColors = true;
796  if (!mscCloud->reserveTheRGBTable()) {
797  if (app)
798  app->dispToConsole("Not enough memory to display colors!",
800  } else {
801  mscCloud->showColors(true);
802  }
803 
804  // generate the cloud of (colored) MSC "points"
805  for (size_t i = 0; i < cloudSize; ++i) {
807  sample.set_size(fdim);
808 
809  const CorePointDesc* desc = nullptr;
810  const ecvColor::Rgb* col = &ecvColor::lightGrey;
811 
812  if (i < nsamples1) {
813  desc = &descriptors1[i];
814  col = &ecvColor::blue;
815  } else if (i < nsamples) {
816  desc = &descriptors2[i - nsamples1];
817  col = &ecvColor::red;
818  } else if (evaluationDescriptors) {
819  desc = &evaluationDescriptors->at(i - nsamples);
820  // col = &ecvColor::lightGrey;
821  }
822 
823  assert(desc && col);
824  size_t shift =
825  (paramsCount - scaleCount) *
826  dimPerScale; // if we use less scales than parameters
827  for (size_t j = 0; j < fdim; ++j) {
828  sample(j) = desc->params[shift + j];
829  }
830 
831  double x = trainer.predict(sample);
832  double y = orthoTrainer.predict(sample);
833  mscCloud->addPoint(
834  CCVector3(static_cast<float>(x), static_cast<float>(y), 0));
835  if (hasColors && col) {
836  mscCloud->addRGBColor(*col);
837  }
838  }
839 
840 // DGM: we only use the evaluation cloud for representation now!
841 #if 0
842  if (evaluationDescriptors)
843  {
844  // radius from probabilistic SVM, diameter = 90% chance of correct classif
845  PointCoordinateType radius = static_cast<PointCoordinateType>(-log(1.0/0.9 - 1.0)/2.0);
846 
847  //we'll need the octree
848  ecvProgressDialog pDlg(true,parentWindow);
849  if (!mscCloud->computeOctree(&pDlg))
850  {
851  if (app)
852  app->dispToConsole("Not enough memory to determine the classifier boundary on evaluation cloud!",ecvMainAppInterface::ERR_CONSOLE_MESSAGE);
853  return false;
854  }
855  ccOctree* octree = mscCloud->getOctree();
856  assert(octree);
858 
859  int minNeighborCount = 0;
860  Classifier::Point2D bestDir(0,0);
861  Classifier::Point2D bestStartingPoint(0,0);
862 
863  const int c_searchTransSteps = 13; // 2*13=26 steps between the reference points
864  const int c_searchDirCount = 90; // from 0 to 180 each 2 degree (unoriented lines)
865 
866  CosSinTable<c_searchDirCount> tableCosSin;
867  float transStep = mscCloud->getOwnBB().getMaxBoxDim() / (c_searchTransSteps*2);
868 
869  //progress notification
870  pDlg.reset();
871  pDlg.setCancelButton(0);
872  pDlg.setWindowTitle("Determining boundary line");
873  pDlg.setRange(0,c_searchTransSteps*2);
874  pDlg.setInfo(qPrintable(QString("Search steps: %1").arg(c_searchTransSteps*2)));
875 
876  //we look for the line with the least number of neighbor points
877  for (int td=0; td<=c_searchTransSteps*2; ++td)
878  {
879  //strating point
880  Classifier::Point2D A = classifier.refPointNeg + (classifier.refPointPos - classifier.refPointNeg) * (static_cast<float>(td) / static_cast<float>(c_searchTransSteps*2));
881 
882  // now we swipe a decision boundary in each direction around the point
883  // and look for the lowest overall density along the boundary
884  int sumds[c_searchDirCount];
885 
886  // for each orientation
887  int minDirIndex = 0;
888  for (int sd=0; sd<c_searchDirCount; ++sd)
889  {
890  // unit vector in the direction of the line
891  Classifier::Point2D u( tableCosSin.cosines[sd],
892  tableCosSin.sines[sd] );
893  sumds[sd] = 0;
894  for (int sp = -c_searchTransSteps; sp <= c_searchTransSteps; ++sp)
895  {
896  float s = sp * transStep;
897 
898  // use the parametric P2D = A + u*s formulation of a line
899  Classifier::Point2D P2D = A + u * s;
900 
901  CCVector3 P(P2D.x,P2D.y,0);
904 
905  // count the number of neighbors
906  sumds[sd] += count;
907  }
908 
909  // keep track of the best direction for this starting point
910  if (sumds[sd] < sumds[minDirIndex])
911  minDirIndex = sd;
912  }
913 
914  if (td == 0 || sumds[minDirIndex] < minNeighborCount)
915  {
916  minNeighborCount = sumds[minDirIndex];
917  bestDir.x = tableCosSin.cosines[minDirIndex];
918  bestDir.y = tableCosSin.sines[minDirIndex];
919  bestStartingPoint = A;
920  }
921 
922  //progress notification
923  pDlg.setValue(td);
924  }
925 
926  pDlg.close();
927 
928  // decision boundary in this 2D space
929  boundaryCenter = bestStartingPoint;
930  boundaryDir = bestDir;
931  }
932  else
933 #endif
934  {
935  // evaluate the boundary simply with the two input "class" clouds
936  Classifier::Point2D c1(0, 0), c2(0, 0);
937  unsigned n1 = 0, n2 = 0;
938  ComputeReferencePoints(c2, c1, proj1, proj2, labels, &n2, &n1);
939 
940  Classifier::Point2D w_vect = c2 - c1;
941  w_vect.normalize();
942  Classifier::Point2D w_orth(-w_vect.y, w_vect.x);
943 
944  double cba2_max = 0;
945  const int c_searchSteps = 180;
946  CosSinTable<c_searchSteps> tableCosSin;
947 
948  // progress notification
949  ecvProgressDialog pDlg(false, parentWindow);
950  pDlg.setWindowTitle("Determining boundary line");
951  pDlg.setRange(0, c_searchSteps);
952  pDlg.setInfo(
953  qPrintable(QString("Search steps: %1").arg(c_searchSteps)));
954 
955  for (int sd = 1; sd < c_searchSteps; ++sd) {
956  Classifier::Point2D v(tableCosSin.cosines[sd],
957  tableCosSin.sines[sd]);
958 
959  dlib::matrix<double, 2, 2> basis;
960  Classifier::Point2D base_vec1 = w_vect;
961  Classifier::Point2D base_vec2 = w_vect * v.x + w_orth * v.y;
962  basis(0, 0) = base_vec1.x;
963  basis(0, 1) = base_vec2.x;
964  basis(1, 0) = base_vec1.y;
965  basis(1, 1) = base_vec2.y;
966  basis = inv(basis);
967 
968  double m1 = 0, m2 = 0;
969  std::vector<double> p1, p2;
970  p1.reserve(n1);
971  p2.reserve(n2);
972  for (size_t i = 0; i < nsamples; ++i) {
973  dlib::matrix<double, 2, 1> P;
974  P(0) = proj1[i];
975  P(1) = proj2[i];
976  P = basis * P;
977  const double& d = P(0); // projection on w_vect along the
978  // slanted direction
979  if (labels[i] < 0) {
980  p1.push_back(d);
981  m1 += d;
982  } else {
983  p2.push_back(d);
984  m2 += d;
985  }
986  }
987  m1 /= static_cast<double>(n1);
988  m2 /= static_cast<double>(n2);
989 
990  // search for optimal separation
991  bool reversed = false;
992  if (m1 > m2) {
993  reversed = true;
994  std::swap(m1, m2);
995  p1.swap(p2);
996  }
997  ParallelSort(p1.begin(), p1.end());
998  ParallelSort(p2.begin(), p2.end());
999 
1000  for (int i = 0; i <= 100; ++i) {
1001  double pos = m1 + i * (m2 - m1) / 100.0;
1002 
1003  size_t idx1 = std::lower_bound(p1.begin(), p1.end(), pos) -
1004  p1.begin();
1005  size_t idx2 = std::lower_bound(p2.begin(), p2.end(), pos) -
1006  p2.begin();
1007 
1008  double pr1 = idx1 / static_cast<double>(n1);
1009  double pr2 = 1.0 - idx2 / static_cast<double>(n2);
1010  double cba2 = fabs(pr1 + pr2);
1011  if (cba2 > cba2_max) {
1012  cba2_max = cba2;
1013  double r = (pos - m1) / (m2 - m1);
1014  if (reversed) r = 1.0 - r;
1015  Classifier::Point2D center =
1016  c1 + (c2 - c1) * static_cast<float>(r);
1017  boundaryCenter = center;
1018  boundaryDir = base_vec2;
1019  }
1020  }
1021 
1022  pDlg.setValue(sd);
1023  }
1024 
1025  pDlg.close();
1026  }
1027 
1028  // update classifier info (boundary)
1029  {
1030  PointCoordinateType l = mscCloud->getOwnBB().getDiagVec().y / 2;
1031  classifier.path.resize(2);
1032  classifier.path[0] = boundaryCenter + boundaryDir * l;
1033  classifier.path[1] = boundaryCenter - boundaryDir * l;
1034  }
1035  }
1036 
1037  return true;
1038 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
std::string name
int count
#define ParallelSort
Definition: ParallelSort.h:33
cmdLineReadable * params[]
Classifier.
Definition: classifier.h:23
std::vector< float > weightsAxis2
Definition: classifier.h:71
unsigned dimPerScale
Dimension (per-scale)
Definition: classifier.h:80
unsigned descriptorID
Associated descriptor ID (see ccPointDescriptor.h)
Definition: classifier.h:77
std::vector< float > weightsAxis1
Definition: classifier.h:71
std::vector< Point2D > path
Definition: classifier.h:72
std::vector< float > scales
Associated scales.
Definition: classifier.h:83
float classify(const CorePointDesc &mscdata) const
Classification in MSC space.
Definition: classifier.cpp:152
Point2D refPointPos
Definition: classifier.h:74
Point2D refPointNeg
Definition: classifier.h:74
Set of (core) point descriptors.
const unsigned descriptorID() const
Returns associated descriptor ID.
bool setScales(const std::vector< float > &scales)
Sets associated scales.
const unsigned dimPerScale() const
Returns the number of dimensions per scale.
void setDescriptorID(unsigned ID)
Sets associated descriptor ID.
void setDimPerScale(unsigned dim)
Sets associated descriptor ID.
double predict(const sample_type &data) const
Definition: trainer.h:202
std::vector< float > m_weights
Classifier weights.
Definition: trainer.h:211
trained_function_type train(const std::vector< sample_type > &samplesvec, const std::vector< float > &labels) const
Definition: trainer.h:24
dlib::matrix< float, 0, 1 > sample_type
Definition: trainer.h:19
Generic parameters 'computer' class (at a given scale)
static ScaleParamsComputer * GetByID(unsigned descID)
Vault: returns the computer corresponding to the given ID.
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:36
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:65
Type y
Definition: CVGeom.h:36
virtual void showColors(bool state)
Sets colors 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.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
ccHObject * find(unsigned uniqueID)
Finds an entity in this object hierarchy.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual unsigned getUniqueID() const
Returns object unique ID.
Definition: ecvObject.h:86
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
Octree structure.
Definition: ecvOctree.h:27
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
void clear() override
Clears the entity from all its points and features.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
A scalar field associated to display-related parameters.
Vector3Tpl< T > getDiagVec() const
Returns diagonal vector.
Definition: BoundingBox.h:169
T getMaxBoxDim() const
Returns maximal box dimension.
Definition: BoundingBox.h:185
The octree structure used throughout the library.
Definition: DgmOctree.h:39
unsigned char findBestLevelForAGivenNeighbourhoodSizeExtraction(PointCoordinateType radius) const
Definition: DgmOctree.cpp:2664
int getPointsInSphericalNeighbourhood(const CCVector3 &sphereCenter, PointCoordinateType radius, NeighboursSet &neighbours, unsigned char level) const
Returns the points falling inside a sphere.
Definition: DgmOctree.cpp:1846
int build(GenericProgressCallback *progressCb=nullptr)
Builds the structure.
Definition: DgmOctree.cpp:196
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
virtual bool enableScalarField()=0
Enables the scalar field associated to the cloud.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based point access.
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.
virtual bool textCanBeEdited() const
Returns whether the dialog title and info can be updated or not.
const PointCoordinateType * getLSPlane()
Returns best interpolating plane equation (Least-square)
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
A very simple point cloud (no point duplication)
virtual void swap(unsigned i, unsigned j)
Swaps two point references.
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
virtual bool resize(unsigned n)
Presets the size of the vector used to store point references.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
unsigned currentSize() const
Definition: ScalarField.h:100
RGB color structure.
Definition: ecvColorTypes.h:49
Main application interface (for plugins)
virtual QMainWindow * getMainWindow()=0
Returns main window.
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
Graphical progress indicator (thread-safe)
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
static ccPointCloud * GetCloudFromCombo(QComboBox *comboBox, ccHObject *dbRoot)
static bool EvaluateClassifier(const Classifier &classifier, const CorePointDescSet &descriptors1, const CorePointDescSet &descriptors2, const std::vector< float > &scales, EvalParameters &params)
Evaluates a classifier.
static size_t TestVectorsOverlap(const std::vector< float > &first, const std::vector< float > &second)
Tests whether a vector contains the values of another one (at the end!)
static bool CompareVectors(const std::vector< float > &first, const std::vector< float > &second)
Compares two vectors.
static bool ComputeCorePointsDescriptors(cloudViewer::GenericIndexedCloud *corePoints, CorePointDescSet &corePointsDescriptors, ccGenericPointCloud *sourceCloud, const std::vector< float > &sortedScales, bool &invalidDescriptors, QString &error, unsigned descriptorID=DESC_DIMENSIONALITY, int maxThreadCount=0, cloudViewer::GenericProgressCallback *progressCb=0, cloudViewer::DgmOctree *inputOctree=0, std::vector< ccScalarField * > *roughnessSFs=0)
Computes the 'descriptors' for various scales on core points only.
static bool TrainClassifier(Classifier &classifier, const CorePointDescSet &descriptors1, const CorePointDescSet &descriptors2, const std::vector< float > &scales, ccPointCloud *mscCloud, const CorePointDescSet *evaluationDescriptors=0, ecvMainAppInterface *app=0)
Trains a classifier.
static QString GetEntityName(ccHObject *obj)
Returns a long description of a given entity (name + [ID])
__host__ __device__ float dot(float2 a, float2 b)
Definition: cutil_math.h:1119
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
static void error(char *msg)
Definition: lsd.c:159
@ POINT_CLOUD
Definition: CVTypes.h:104
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb blue(0, 0, MAX)
constexpr Rgb lightGrey(static_cast< ColorCompType >(MAX *0.8), static_cast< ColorCompType >(MAX *0.8), static_cast< ColorCompType >(MAX *0.8))
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
bool errorOccurred
std::vector< ccScalarField * > * roughnessSFs
bool invalidDescriptors
cloudViewer::NormalizedProgress * nProgress
cloudViewer::DgmOctree * octree
cloudViewer::GenericIndexedCloud * corePoints
bool processCanceled
CorePointDescSet * descriptors
ScaleParamsComputer * computer
void ComputeCorePointDescriptor(unsigned index)
unsigned char octreeLevel
static struct @89 s_computeCorePointsDescParams
ccGenericPointCloud * sourceCloud
Set of descriptors.
std::vector< float > params
Helper: pre-computed cos and sin values between 0 and Pi.
Definition: qCanupoTools.h:33
float sines[Count]
Definition: qCanupoTools.h:35
float cosines[Count]
Definition: qCanupoTools.h:34
Structure used during nearest neighbour search.
Definition: DgmOctree.h:101
static bool distComp(const PointDescriptor &a, const PointDescriptor &b)
Comparison operator.
Definition: DgmOctree.h:126
Classifer evaluation parameters.
Definition: qCanupoTools.h:107
static void GramSchmidt(dlib::matrix< LDATrainer::sample_type, 0, 1 > &basis, LDATrainer::sample_type &newX)
Gram-Schmidt process to re-orthonormalise the basis.
Definition: trainer.h:215
static bool DilateClassifier(Classifier &classifier, std::vector< float > &proj1, std::vector< float > &proj2, const std::vector< float > &labels, const std::vector< LDATrainer::sample_type > &samples, LDATrainer &trainer, LDATrainer &orthoTrainer)
Definition: trainer.h:279
static void ComputeReferencePoints(Classifier::Point2D &refpt_pos, Classifier::Point2D &refpt_neg, const std::vector< float > &proj1, const std::vector< float > &proj2, const std::vector< float > &labels, unsigned *_npos=0, unsigned *_nneg=0)
Compute pos. and neg. reference points.
Definition: trainer.h:248