28 #include <QApplication>
29 #include <QMessageBox>
30 #include <QStringList>
33 #ifdef COMPILE_PRIVATE_CANUPO
34 static const char CANUPO_PER_LEVEL_ROUGHNESS_SF_NAME[] =
"CANUPO.roughness";
43 const float confidence,
44 float& distToBoundary,
50 unsigned char coreOctreeLevel,
53 const std::vector<int>& corePointClasses) {
70 std::vector<ScalarType> class1SFValues, class2SFValues;
71 for (
int j = 0; j < n; ++j) {
72 unsigned currentPointIndex = neighbors[j].pointIndex;
74 unsigned nearestCoreIndex = 0;
78 nearestCoreIndex = currentPointIndex;
80 double maxSquareDist = 0;
83 cloud->
getPoint(currentPointIndex), &Yk, 1,
84 coreOctreeLevel, maxSquareDist) == 1) {
92 int corePointClass = corePointClasses[nearestCoreIndex];
93 if (corePointClass != -1) {
94 const ScalarType& sfValue = sf->
getValue(currentPointIndex);
95 if (corePointClass == classifier.
class1)
96 class1SFValues.push_back(sfValue);
97 else if (corePointClass == classifier.
class2)
98 class2SFValues.push_back(sfValue);
105 size_t nsamples = class1SFValues.size() + class2SFValues.size();
112 if (class1SFValues.empty()) {
113 distToBoundary =
static_cast<float>(class2SFValues.size()) / n;
114 if (distToBoundary < 0.5f) {
118 }
else if (class2SFValues.empty()) {
119 distToBoundary = -
static_cast<float>(class1SFValues.size()) /
121 if (distToBoundary > -0.5f) {
126 std::sort(class1SFValues.begin(), class1SFValues.end());
127 std::sort(class2SFValues.begin(), class2SFValues.end());
129 std::vector<ScalarType>* smallestSet = &class1SFValues;
130 std::vector<ScalarType>* largestSet = &class2SFValues;
132 if (class1SFValues.size() >= class2SFValues.size()) {
136 std::vector<ScalarType> bestSplit;
137 std::vector<int> bestSplitDir;
138 float bestConfidence = -1.0f;
140 for (
size_t k = 0; k < smallestSet->size(); ++k) {
147 std::upper_bound(largestSet->begin(), largestSet->end(),
148 smallestSet->at(k)) -
151 nlabove = largestSet->size();
154 nlabove = largestSet->size() - dichomed;
160 float c1 =
static_cast<float>(nlabove) / largestSet->size() +
161 static_cast<float>(nsbelow) / smallestSet->size();
162 float c2 =
static_cast<float>(largestSet->size() - nlabove) /
164 static_cast<float>(smallestSet->size() - nsbelow) /
168 if (bestConfidence < conf) {
170 bestSplitDir.clear();
171 bestConfidence = conf;
174 if (qCanupoTools::Fpeq<float>(bestConfidence, conf)) {
176 (smallestSet->at(k) +
178 largestSet->size() - 1))) /
180 bestSplitDir.push_back(c1 <= c2 ? 1 : 0);
189 if ( confidence < bestConfidence) {
191 size_t bsi = bestSplit.size() / 2;
192 distToBoundary = sf->
getValue(coreIndex) - bestSplit[bsi];
194 if (bestSplitDir[bsi] == 1) distToBoundary = -distToBoundary;
196 if (class1SFValues.size() >= class2SFValues.size())
197 distToBoundary = -distToBoundary;
205 float newConfidence =
206 1.0f / (exp(-
fabs(distToBoundary)) + 1.0f);
207 newConfidence = 2 * (newConfidence - 0.5f);
209 return newConfidence;
210 }
catch (
const std::bad_alloc&) {
219 QString classifierFilename,
226 QWidget* parentWidget ,
235 std::vector<Classifier> classifiers;
236 std::vector<float> scales;
246 if (classifiers.empty()) {
255 for (
size_t i = 0; i < classifiers.size(); ++i) {
259 QString(
"Unhandled descriptor type! (ID = %1)")
260 .arg(classifiers[i].descriptorID),
265 classifiers[i].descriptorID != classifiers[0].descriptorID) {
268 QString(
"Can't handle mixed descriptor types!"),
273 descriptorID = classifiers[0].descriptorID;
280 QVariant mscMetaData;
281 bool useExistingMetaData =
false;
282 if (realCorePoints) {
284 if (mscMetaData.isValid()) {
285 bool validMetaData = (mscMetaData.type() == QVariant::ByteArray &&
287 mscMetaData.toByteArray()));
290 useExistingMetaData =
true;
294 "[qCanupo] Failed to read core point cloud "
295 "associated MSC meta data?!",
302 #ifdef COMPILE_PRIVATE_CANUPO
304 bool generateRoughnessSF = cDlg.generateRoughnessSFsCheckBox->isChecked();
305 std::vector<ccScalarField*> coreRoughnessSFs;
309 for (
int step = 0; step < 1; ++step)
312 bool computeDescriptors =
true;
313 if (!corePointsDescriptors.
scales().empty()) {
315 scales, corePointsDescriptors.
scales()) <
318 QMessageBox::question(
319 parentWidget,
"Scales mismatch",
320 "Available descriptors/scales data mismatch "
321 "with classifier's! Compute new descriptors or "
324 QMessageBox::Cancel) == QMessageBox::Cancel) {
335 if (!computeDescriptors &&
338 QMessageBox::question(
339 parentWidget,
"Descriptor type mismatch",
340 "Available descriptors have been computed with "
341 "another descriptor type! Compute new "
342 "descriptors or cancel?",
344 QMessageBox::Cancel) == QMessageBox::Cancel) {
349 computeDescriptors =
true;
361 "Failed to compute input cloud octree!",
369 #ifdef COMPILE_PRIVATE_CANUPO
370 computeDescriptors |= generateRoughnessSF;
374 if (computeDescriptors) {
377 assert(descriptorID != 0);
383 QString(
"Internal error: unhandled "
384 "descriptor ID (%1)!")
403 QString(
"To compute this type of "
404 "descriptor, the core points cloud "
405 "must have an active scalar "
412 #ifdef COMPILE_PRIVATE_CANUPO
414 if (generateRoughnessSF) {
415 size_t scaleCount = scales.size();
416 coreRoughnessSFs.resize(scaleCount, 0);
418 for (
size_t s = 0; s < scaleCount; ++s) {
420 QString(CANUPO_PER_LEVEL_ROUGHNESS_SF_NAME) +
421 QString(
" @ scale %1").arg(scales[s]);
423 coreRoughnessSFs[s] =
427 m_app->dispToConsole(
428 "Not enough memory to store per-level "
431 generateRoughnessSF =
false;
441 corePoints, corePointsDescriptors, cloud, scales,
444 #ifdef COMPILE_PRIVATE_CANUPO
446 generateRoughnessSF ? &coreRoughnessSFs : 0
451 QString(
"Failed to compute core points "
459 "[qCanupo] Some descriptors couldn't be "
460 "computed (min scale may be too small)!",
468 assert(!
params.useActiveSFForConfidence ||
474 corePointsOctree =
octree.data();
477 if (!corePointsOctree->
build(&pDlg)) {
480 "Failed to compute core points octree! "
481 "(not enough memory?)",
483 delete corePointsOctree;
484 corePointsOctree =
nullptr;
488 assert(corePointsOctree);
493 unsigned char coreOctreeLevel =
504 std::vector<int> corePointClasses;
506 std::vector<float> corePointConfidences;
512 size_t corePointCount = corePointsDescriptors.size();
514 corePointClasses.resize(corePointCount, -1);
515 corePointConfidences.resize(corePointCount, 0.0f);
518 std::vector<unsigned> pendingPoints(corePointCount);
520 for (
size_t i = 0; i < corePointCount; ++i)
521 pendingPoints[i] =
static_cast<unsigned>(i);
523 std::vector<unsigned> unreliablePointIndexes;
527 assert(!
params.useActiveSFForConfidence || sf);
530 while (!pendingPoints.empty()) {
533 pDlg.
setInfo(QObject::tr(
"Remaining points to classify: "
534 "%1\nSource points: %2")
535 .arg(pendingPoints.size())
536 .arg(cloud->
size()));
542 for (
size_t i = 0; i < pendingPoints.size(); ++i) {
543 unsigned coreIndex = pendingPoints[i];
545 corePointsDescriptors[coreIndex];
548 if (classifiers.size() == 1) {
549 const Classifier& classifier = classifiers.front();
550 float distToBoundary =
554 1.0f / (exp(-
fabs(distToBoundary)) +
557 2 * (confidence - 0.5f);
560 bool unreliable =
false;
561 if (confidence <
params.confidenceThreshold) {
563 if (
params.useActiveSFForConfidence) {
567 classifier, confidence,
568 distToBoundary, cloud,
571 coreOctreeLevel, coreIndex,
572 largestRadius, corePointClasses);
574 if (newConfidence < 0) {
577 "Internal error: failed to "
578 "refine the classification "
579 "on the boundary (not "
582 ERR_CONSOLE_MESSAGE);
584 }
else if (newConfidence > confidence) {
586 confidence = newConfidence;
592 int theClass = (distToBoundary >= 0
595 corePointClasses[i] = theClass;
596 corePointConfidences[i] = confidence;
597 }
else if (
params.useActiveSFForConfidence) {
599 unreliablePointIndexes.push_back(
600 static_cast<unsigned>(i));
604 std::map<int, int> votes;
605 std::map<int, float> minConfidences;
609 for (std::vector<Classifier>::const_iterator
610 classifierIt = classifiers.begin();
611 classifierIt != classifiers.end();
617 float distToBoundary = classifier.
classify(
633 1.0f / (exp(-
fabs(distToBoundary)) +
635 confidence = 2 * (confidence -
639 if (confidence <
params.confidenceThreshold) {
640 bool unreliable =
true;
641 if (
params.useActiveSFForConfidence) {
644 float newConfidence =
646 classifier, confidence,
647 distToBoundary, cloud,
656 if (newConfidence < 0) {
660 "failed to refine the "
661 "classification on the "
662 "boundary (not enough "
665 ERR_CONSOLE_MESSAGE);
667 }
else if (newConfidence > confidence) {
669 confidence = newConfidence;
679 int theClass = (distToBoundary >= 0
685 if (minConfidences.find(theClass) ==
686 minConfidences.end()) {
687 minConfidences[theClass] = confidence;
689 if (confidence < minConfidences[theClass])
690 minConfidences[theClass] = confidence;
694 if (!votes.empty()) {
696 std::vector<int> bestClasses;
697 int maxVoteCount = -1;
698 for (
auto& vote : votes) {
699 int voteCount = vote.second;
700 if (maxVoteCount < voteCount) {
702 bestClasses.push_back(vote.first);
703 maxVoteCount = voteCount;
704 }
else if (maxVoteCount == voteCount) {
705 bestClasses.push_back(vote.first);
711 int bestClassLabel = bestClasses.front();
712 if (bestClasses.size() > 1) {
713 for (
size_t j = 1; j < bestClasses.size();
715 if (minConfidences[bestClasses[j]] >
716 minConfidences[bestClassLabel])
717 bestClassLabel = bestClasses[j];
721 corePointClasses[i] = bestClassLabel;
722 corePointConfidences[i] =
723 minConfidences[bestClassLabel];
724 }
else if (
params.useActiveSFForConfidence) {
726 unreliablePointIndexes.push_back(
727 static_cast<unsigned>(i));
739 if (pendingPoints.size() == unreliablePointIndexes.size()) {
743 pendingPoints = unreliablePointIndexes;
744 unreliablePointIndexes.clear();
750 "[qCanupo] Process cancelled by user!",
759 int classLabelSFIdx = -1;
763 if (classLabelSFIdx < 0)
766 if (classLabelSFIdx >= 0) {
774 ERR_CONSOLE_MESSAGE);
780 int confidenceSFIdx = -1;
783 "CANUPO.confidence");
784 if (confidenceSFIdx < 0)
787 if (confidenceSFIdx >= 0) {
793 "[qCanupo] Not enough memory to store the "
794 "confidence values!",
800 std::vector<ccScalarField*> scaleSFs;
801 bool generateAdditionalSF =
params.generateAdditionalSF;
802 if (generateAdditionalSF &&
806 "[qCanupo] Per-level 'x-y' values can only "
807 "be extracted from descriptor with 2 "
808 "dimensions per scale!",
810 generateAdditionalSF =
false;
813 if (generateAdditionalSF) {
817 QStringList toDelete;
822 if (sfName.startsWith(
827 for (
int j = 0; j < toDelete.size(); ++j)
835 size_t scaleCount = scales.size();
836 scaleSFs.resize(scaleCount,
nullptr);
838 for (
size_t s = 0; s < scaleCount; ++s) {
841 QString(
" @ scale %1").arg(scales[s]);
846 qPrintable(sfName)) < 0);
849 if (!scaleSFs[s]->resizeSafe(cloud->
size(),
true,
853 "Not enough memory to store "
854 "per-level 'x-y' values!",
856 ERR_CONSOLE_MESSAGE);
857 while (!scaleSFs.empty()) {
858 scaleSFs.back()->release();
861 generateAdditionalSF =
false;
867 #ifdef COMPILE_PRIVATE_CANUPO
870 if (generateRoughnessSF) {
874 QStringList toDelete;
880 if (sfName.startsWith(
881 CANUPO_PER_LEVEL_ROUGHNESS_SF_NAME))
885 for (
int j = 0; j < toDelete.size(); ++j)
888 qPrintable(toDelete[j])));
895 for (
size_t s = 0; s < coreRoughnessSFs.size();
900 coreRoughnessSFs[s]->getName()) <
903 coreRoughnessSFs[s]->computeMinAndMax();
906 coreRoughnessSFs.clear();
908 generateRoughnessSF =
911 size_t scaleCount = scales.size();
913 assert(coreRoughnessSFs.size() ==
916 for (
size_t s = 0; s < scaleCount; ++s) {
919 coreRoughnessSFs[s]->getName());
930 "Not enough memory to store "
931 "per-level roughness!",
933 ERR_CONSOLE_MESSAGE);
938 generateRoughnessSF =
false;
949 QObject::tr(
"Core points: %1\nSource points: %2")
951 .arg(cloud->
size()));
959 for (
unsigned i = 0; i < cloud->
size(); ++i) {
963 unsigned nearestCorePointIndex = 0;
967 nearestCorePointIndex = i;
969 double maxSquareDist = 0;
972 assert(corePointsOctree);
974 P, &Yk, 1, coreOctreeLevel,
975 maxSquareDist) == 1) {
976 nearestCorePointIndex =
985 assert(classLabelSF);
986 ScalarType classVal =
static_cast<ScalarType
>(
987 corePointClasses[nearestCorePointIndex]);
988 if (classVal >= 0) classLabelSF->
setValue(i, classVal);
992 ScalarType confVal =
static_cast<ScalarType
>(
994 [nearestCorePointIndex]);
999 if (generateAdditionalSF) {
1000 unsigned dimPerScale =
1002 assert(dimPerScale == 2);
1004 [nearestCorePointIndex];
1005 assert(desc.
params.size() ==
1006 scaleSFs.size() * dimPerScale);
1007 for (
size_t s = 0; s < scaleSFs.size(); ++s) {
1009 (desc.
params[s * dimPerScale] -
1010 desc.
params[s * dimPerScale + 1]);
1011 scaleSFs[s]->setValue(i, val);
1015 #ifdef COMPILE_PRIVATE_CANUPO
1017 if (generateRoughnessSF) {
1019 [nearestCorePointIndex];
1020 assert(coreRoughnessSFs.size() ==
1023 const ScalarType& val =
1024 coreRoughnessSFs[s]->getValue(
1025 nearestCorePointIndex);
1041 "Internal error: failed to get nearest "
1045 if (confidenceSFIdx >= 0)
1047 while (!scaleSFs.empty()) {
1048 scaleSFs.back()->release();
1049 scaleSFs.pop_back();
1057 if (generateAdditionalSF) {
1058 for (
auto& scaleSF : scaleSFs) {
1059 scaleSF->computeMinAndMax();
1060 scaleSF->setSymmetricalScale(
true);
1065 #ifdef COMPILE_PRIVATE_CANUPO
1066 if (generateRoughnessSF) {
1079 if (corePointsOctree !=
octree) {
1080 delete corePointsOctree;
1081 corePointsOctree =
nullptr;
1085 if (realCorePoints && !useExistingMetaData) {
1086 bool proceed =
true;
1087 if (mscMetaData.isValid()) {
1088 proceed = (silent ||
1089 QMessageBox::question(
1091 "Overwrite MSC meta-data?",
1092 "Core points cloud already has "
1093 "associated MSC meta-data, should "
1094 "we overwrite them?",
1095 QMessageBox::Yes, QMessageBox::No) ==
1100 mscMetaData = corePointsDescriptors.
toByteArray();
1101 if (mscMetaData.isValid()) {
1106 QString(
"[qCanupo] MSC descriptors "
1107 "have been saved as meta-data "
1114 "[qCanupo] Failed to save MSC "
1115 "meta-dataa (not enough memory?)",
1117 WRN_CONSOLE_MESSAGE);
1125 }
catch (
const std::bad_alloc&) {
1132 #ifdef COMPILE_PRIVATE_CANUPO
1134 while (!coreRoughnessSFs.empty()) {
1135 coreRoughnessSFs.back()->release();
1136 coreRoughnessSFs.pop_back();
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
float PointCoordinateType
Type of the coordinates of a (N-D) point.
cmdLineReadable * params[]
static const unsigned DESC_DIMENSIONALITY
static bool Load(QString filename, std::vector< Classifier > &classifiers, std::vector< float > &scales, QString &error, FileHeader *header=0, bool headerOnly=false)
Loads a CANUPO's classifier file (.prm)
float classify(const CorePointDesc &mscdata) const
Classification in MSC space.
Set of (core) point descriptors.
const std::vector< float > & scales() const
Returns associated scales.
const unsigned descriptorID() const
Returns associated descriptor ID.
const unsigned dimPerScale() const
Returns the number of dimensions per scale.
QByteArray toByteArray() const
Converts structure to a byte array.
bool fromByteArray(const QByteArray &data)
Inits structure from a byte array.
Generic parameters 'computer' class (at a given scale)
static ScaleParamsComputer * GetByID(unsigned descID)
Vault: returns the computer corresponding to the given ID.
virtual bool needSF() const
Returns whether the computer requires a scalar field or not.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccOctreeProxy * getOctreeProxy() const
Returns the associated octree proxy (if any)
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
ccHObject * getParent() const
Returns parent object.
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.
QSharedPointer< ccOctree > Shared
Shared pointer.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
void deleteScalarField(int index) override
Deletes a specific scalar field.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
A scalar field associated to display-related parameters.
The octree structure used throughout the library.
unsigned findPointNeighbourhood(const CCVector3 *_queryPoint, ReferenceCloud *Yk, unsigned maxNumberOfNeighbors, unsigned char level, double &maxSquareDist, double maxSearchDist=0, int *finalNeighbourhoodSize=nullptr) const
Finds the nearest neighbours around a query point.
unsigned char findBestLevelForAGivenNeighbourhoodSizeExtraction(PointCoordinateType radius) const
int getPointsInSphericalNeighbourhood(const CCVector3 &sphereCenter, PointCoordinateType radius, NeighboursSet &neighbours, unsigned char level) const
Returns the points falling inside a sphere.
int build(GenericProgressCallback *progressCb=nullptr)
Builds the structure.
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
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.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
unsigned size() const override
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
virtual unsigned getPointGlobalIndex(unsigned localIndex) const
virtual void clear(bool releaseMemory=false)
Clears the cloud.
A simple scalar field (to be associated to a point cloud)
void fill(ScalarType fillValue=0)
Fills the array with a particular value.
virtual void computeMinAndMax()
Determines the min and max values.
ScalarType & getValue(std::size_t index)
void setValue(std::size_t index, ScalarType value)
const char * getName() const
Returns scalar field name.
Main application interface (for plugins)
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
Graphical progress indicator (thread-safe)
virtual void start() override
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
static bool Classify(QString classifierFilename, const ClassifyParams ¶ms, ccPointCloud *cloud, cloudViewer::GenericIndexedCloudPersist *corePoints, CorePointDescSet &corePointsDescriptors, ccPointCloud *realCorePoints=nullptr, ecvMainAppInterface *app=nullptr, QWidget *parentWidget=nullptr, bool silent=false)
Classify a point cloud.
__host__ __device__ float2 fabs(float2 v)
static void error(char *msg)
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
float RefinePointClassif(const Classifier &classifier, const float confidence, float &distToBoundary, ccPointCloud *cloud, ccOctree *octree, unsigned char octreeLevel, cloudViewer::GenericIndexedCloudPersist *corePoints, cloudViewer::DgmOctree *corePointsOctree, unsigned char coreOctreeLevel, unsigned coreIndex, PointCoordinateType largestRadius, const std::vector< int > &corePointClasses)
static const char s_canupoMSCMetaData[]
static const char CANUPO_PER_LEVEL_ADDITIONAL_SF_NAME[]
std::vector< float > params