From efab553b92bf95bf5c524c8b2295d73e18bbb444 Mon Sep 17 00:00:00 2001 From: Daniel Girardeau-Montaut Date: Sun, 10 Nov 2024 17:32:37 +0100 Subject: [PATCH] Scalar fields can now handle large values (with an internal double offset). Their names can also be longer than 255 characters. --- ContextBasedFeature.cpp | 6 +++--- FeaturesInterface.cpp | 17 ++++++++++------- FeaturesInterface.h | 4 ++-- NeighborhoodFeature.cpp | 12 ++++++------ PointFeature.cpp | 32 ++++++++++++++++++-------------- ScalarFieldWrappers.h | 4 ++-- confusionmatrix.cpp | 31 +++++++++++++++---------------- confusionmatrix.h | 7 +++---- q3DMASCClassifier.cpp | 12 +++++++----- q3DMASCTools.cpp | 4 ++-- 10 files changed, 68 insertions(+), 61 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index 6cd2762..0d3645e 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -123,14 +123,14 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, //and the scalar field assert(!sf); - sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSFName)); - sf = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields, SFCollector::CAN_REMOVE); + sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, resultSFName); + sf = PrepareSF(corePoints.cloud, resultSFName, generatedScalarFields, SFCollector::CAN_REMOVE); if (!sf) { errorMessage = QString("[ContextBasedFeature::prepare] Failed to prepare scalar %1 @ scale %2").arg(resultSFName).arg(scale); return false; } - source.name = sf->getName(); + source.name = QString::fromStdString(sf->getName()); // NOT NECESSARY IF THE VALUE IS ALREADY COMPUTED if (!scaled() && !sf1WasAlreadyExisting) //with 'kNN' neighbors, we can compute the values right away diff --git a/FeaturesInterface.cpp b/FeaturesInterface.cpp index cf78fc3..e3fbb28 100644 --- a/FeaturesInterface.cpp +++ b/FeaturesInterface.cpp @@ -25,21 +25,24 @@ using namespace masc; -bool Feature::CheckSFExistence(ccPointCloud* cloud, const char* resultSFName) +bool Feature::CheckSFExistence(ccPointCloud* cloud, const QString& resultSFName) { - if (!cloud || !resultSFName) + if (!cloud || resultSFName.isEmpty()) { assert(false); return false; } - int sfIdx = cloud->getScalarFieldIndexByName(resultSFName); + int sfIdx = cloud->getScalarFieldIndexByName(resultSFName.toStdString()); return (sfIdx >= 0); } -CCCoreLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resultSFName, SFCollector* generatedScalarFields/*=nullptr*/, SFCollector::Behavior behavior/*=SFCollector::CAN_REMOVE*/) +CCCoreLib::ScalarField* Feature::PrepareSF( ccPointCloud* cloud, + const QString& resultSFName, + SFCollector* generatedScalarFields/*=nullptr*/, + SFCollector::Behavior behavior/*=SFCollector::CAN_REMOVE*/ ) { - if (!cloud || !resultSFName) + if (!cloud || resultSFName.isEmpty()) { //invalid input parameters assert(false); @@ -47,7 +50,7 @@ CCCoreLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resu } CCCoreLib::ScalarField* resultSF = nullptr; - int sfIdx = cloud->getScalarFieldIndexByName(resultSFName); + int sfIdx = cloud->getScalarFieldIndexByName(resultSFName.toStdString()); if (sfIdx >= 0) { // ccLog::Warning("Existing SF: " + QString(resultSFName) + ", do not store in generatedScalarFields"); @@ -56,7 +59,7 @@ CCCoreLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resu else { // ccLog::Warning("SF does not exist, create it: " + QString(resultSFName) + ", SFCollector::Behavior " + QString::number(behavior)); - ccScalarField* newSF = new ccScalarField(resultSFName); + ccScalarField* newSF = new ccScalarField(resultSFName.toStdString()); if (!newSF->resizeSafe(cloud->size())) { ccLog::Warning("Not enough memory"); diff --git a/FeaturesInterface.h b/FeaturesInterface.h index 6ea2239..dd3c531 100644 --- a/FeaturesInterface.h +++ b/FeaturesInterface.h @@ -215,10 +215,10 @@ namespace masc public: //helpers //! Creates (or resets) a scalar field with the given name on the input core points cloud - static bool CheckSFExistence(ccPointCloud* cloud, const char* resultSFName); + static bool CheckSFExistence(ccPointCloud* cloud, const QString& resultSFName); //! Creates (or resets) a scalar field with the given name on the input core points cloud - static CCCoreLib::ScalarField* PrepareSF(ccPointCloud* cloud, const char* resultSFName, SFCollector* generatedScalarFields/*= nullptr*/, SFCollector::Behavior behavior); + static CCCoreLib::ScalarField* PrepareSF(ccPointCloud* cloud, const QString& resultSFName, SFCollector* generatedScalarFields/*= nullptr*/, SFCollector::Behavior behavior); //! Performs a mathematical operation between two scalars static ScalarType PerformMathOp(double s1, double s2, Operation op); diff --git a/NeighborhoodFeature.cpp b/NeighborhoodFeature.cpp index 15cd840..c458d72 100644 --- a/NeighborhoodFeature.cpp +++ b/NeighborhoodFeature.cpp @@ -89,23 +89,23 @@ bool NeighborhoodFeature::prepare( const CorePoints& corePoints, //and the scalar field assert(!sf1); - sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSFName)); + sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, resultSFName); if (sf1WasAlreadyExisting) { - sf1 = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields, SFCollector::ALWAYS_KEEP); + sf1 = PrepareSF(corePoints.cloud, resultSFName, generatedScalarFields, SFCollector::ALWAYS_KEEP); if (generatedScalarFields->scalarFields.contains(sf1)) // i.e. the SF is existing but was not present at the startup of the plugin generatedScalarFields->setBehavior(sf1, SFCollector::CAN_REMOVE); } else { - sf1 = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields, SFCollector::CAN_REMOVE); + sf1 = PrepareSF(corePoints.cloud, resultSFName, generatedScalarFields, SFCollector::CAN_REMOVE); } if (!sf1) { error = QString("Failed to prepare scalar %1 @ scale %2").arg(resultSFName).arg(scale); return false; } - source.name = sf1->getName(); + source.name = QString::fromStdString(sf1->getName()); // sf2 is not needed if sf1 was already existing! if (cloud2 && op != Feature::NO_OPERATION && !sf1WasAlreadyExisting) @@ -114,8 +114,8 @@ bool NeighborhoodFeature::prepare( const CorePoints& corePoints, assert(!sf2); - sf2WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSFName2)); - sf2 = PrepareSF(corePoints.cloud, qPrintable(resultSFName2), generatedScalarFields, sf2WasAlreadyExisting ? SFCollector::ALWAYS_KEEP : SFCollector::ALWAYS_REMOVE); + sf2WasAlreadyExisting = CheckSFExistence(corePoints.cloud, resultSFName2); + sf2 = PrepareSF(corePoints.cloud, resultSFName2, generatedScalarFields, sf2WasAlreadyExisting ? SFCollector::ALWAYS_KEEP : SFCollector::ALWAYS_REMOVE); if (!sf2) { diff --git a/PointFeature.cpp b/PointFeature.cpp index 248d4d8..2294dca 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -581,34 +581,34 @@ bool PointFeature::prepare( const CorePoints& corePoints, resultSF1Name += "@" + QString::number(scale); //prepare the corresponding scalar field - sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSF1Name)); + sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, resultSF1Name); if (sf1WasAlreadyExisting) { // if the SF exists, it is not added to generatedScalarFields - statSF1 = PrepareSF(corePoints.cloud, qPrintable(resultSF1Name), generatedScalarFields, SFCollector::ALWAYS_KEEP); + statSF1 = PrepareSF(corePoints.cloud, resultSF1Name, generatedScalarFields, SFCollector::ALWAYS_KEEP); if (generatedScalarFields->scalarFields.contains(statSF1)) // i.e. the SF is existing but was not present at the startup of the plugin generatedScalarFields->setBehavior(statSF1, SFCollector::CAN_REMOVE); } else - statSF1 = PrepareSF(corePoints.cloud, qPrintable(resultSF1Name), generatedScalarFields, SFCollector::CAN_REMOVE); + statSF1 = PrepareSF(corePoints.cloud, resultSF1Name, generatedScalarFields, SFCollector::CAN_REMOVE); if (!statSF1) { error = QString("Failed to prepare scalar field for field '%1' @ scale %2").arg(field1->getName()).arg(scale); return false; } - source.name = statSF1->getName(); + source.name = QString::fromStdString(statSF1->getName()); if (field2 && op != Feature::NO_OPERATION && !sf1WasAlreadyExisting) // nothing to do if statSF1 was already there { QString resultSF2Name = field2->getName() + QString("_") + cloud2Label + "_" + Feature::StatToString(stat) + "@" + QString::number(scale); - //keepStatSF2 = (corePoints.cloud->getScalarFieldIndexByName(qPrintable(resultSFName2)) >= 0); //we remember that the scalar field was already existing! + //keepStatSF2 = (corePoints.cloud->getScalarFieldIndexByName(resultSFName2) >= 0); //we remember that the scalar field was already existing! assert(!statSF2); - sf2WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSF2Name)); + sf2WasAlreadyExisting = CheckSFExistence(corePoints.cloud, resultSF2Name); if (sf2WasAlreadyExisting) - statSF2 = PrepareSF(corePoints.cloud, qPrintable(resultSF2Name), generatedScalarFields, SFCollector::ALWAYS_KEEP); + statSF2 = PrepareSF(corePoints.cloud, resultSF2Name, generatedScalarFields, SFCollector::ALWAYS_KEEP); else - statSF2 = PrepareSF(corePoints.cloud, qPrintable(resultSF2Name), generatedScalarFields, SFCollector::ALWAYS_REMOVE); + statSF2 = PrepareSF(corePoints.cloud, resultSF2Name, generatedScalarFields, SFCollector::ALWAYS_REMOVE); if (!statSF2) { error = QString("Failed to prepare scalar field for field '%1' @ scale %2").arg(field2->getName()).arg(scale); @@ -623,7 +623,7 @@ bool PointFeature::prepare( const CorePoints& corePoints, assert(cloud1 == corePoints.cloud || cloud1 == corePoints.origin); //retrieve/create a SF to host the result - int sfIdx = corePoints.cloud->getScalarFieldIndexByName(qPrintable(resultSF1Name)); + int sfIdx = corePoints.cloud->getScalarFieldIndexByName(resultSF1Name.toStdString()); CCCoreLib::ScalarField* resultSF = nullptr; if (sfIdx >= 0) @@ -634,7 +634,7 @@ bool PointFeature::prepare( const CorePoints& corePoints, else { //copy the SF1 field - resultSF = new ccScalarField(qPrintable(resultSF1Name)); + resultSF = new ccScalarField(resultSF1Name.toStdString()); if (!resultSF->resizeSafe(corePoints.cloud->size())) { error = "Not enough memory"; @@ -679,7 +679,7 @@ bool PointFeature::prepare( const CorePoints& corePoints, corePoints.cloud->setCurrentDisplayedScalarField(newSFIdx); } - source.name = resultSF->getName(); + source.name = QString::fromStdString(resultSF->getName()); return true; } @@ -738,7 +738,7 @@ bool PointFeature::computeStat(const CCCoreLib::DgmOctree::NeighboursSet& points double sum = 0.0; double sum2 = 0.0; - CCCoreLib::WeibullDistribution::ScalarContainer values; + std::vector values; if (storeValues) { try @@ -781,8 +781,10 @@ bool PointFeature::computeStat(const CCCoreLib::DgmOctree::NeighboursSet& points case Feature::MODE: { CCCoreLib::WeibullDistribution w; - if (w.computeParameters(values)) + if (w.computeParameters(CCCoreLib::WeibullDistribution::VectorAsScalarContainer(values))) + { outputValue = w.computeMode(); + } } break; @@ -810,8 +812,10 @@ bool PointFeature::computeStat(const CCCoreLib::DgmOctree::NeighboursSet& points case Feature::SKEW: { CCCoreLib::WeibullDistribution w; - if (w.computeParameters(values)) + if (w.computeParameters(CCCoreLib::WeibullDistribution::VectorAsScalarContainer(values))) + { outputValue = w.computeSkewness(); + } } break; diff --git a/ScalarFieldWrappers.h b/ScalarFieldWrappers.h index 4873169..72bbb8a 100644 --- a/ScalarFieldWrappers.h +++ b/ScalarFieldWrappers.h @@ -43,9 +43,9 @@ public: : m_sf(sf) {} - virtual inline double pointValue(unsigned index) const override { return m_sf->at(index); } + virtual inline double pointValue(unsigned index) const override { return m_sf->getValue(index); } virtual inline bool isValid() const { return m_sf != nullptr; } - virtual inline QString getName() const { return m_sf->getName(); } + virtual inline QString getName() const { return QString::fromStdString(m_sf->getName()); } virtual size_t size() const override { return m_sf->size(); } protected: diff --git a/confusionmatrix.cpp b/confusionmatrix.cpp index 40caeeb..12149a8 100644 --- a/confusionmatrix.cpp +++ b/confusionmatrix.cpp @@ -36,7 +36,7 @@ static QColor GetColor(double value, double r1, double g1, double b1) return QColor(r, g, b); } -ConfusionMatrix::ConfusionMatrix(const std::vector &actual, const std::vector &predicted) +ConfusionMatrix::ConfusionMatrix(const CCCoreLib::GenericDistribution::ScalarContainer& actual, const CCCoreLib::GenericDistribution::ScalarContainer& predicted) : nbClasses(0) , ui(new Ui::ConfusionMatrix) , m_overallAccuracy(0.0f) @@ -78,7 +78,7 @@ void ConfusionMatrix::computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& pr } float TP_FP = TP + FP; if (TP_FP == 0) - precisionRecallF1Score.at(predictedIdx, PRECISION) = CCCoreLib::NAN_VALUE; + precisionRecallF1Score.at(predictedIdx, PRECISION) = std::numeric_limits::quiet_NaN(); else precisionRecallF1Score.at(predictedIdx, PRECISION) = TP / TP_FP; } @@ -97,7 +97,7 @@ void ConfusionMatrix::computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& pr } float TP_FN = TP + FN; if (TP_FN == 0) - precisionRecallF1Score.at(realIdx, RECALL) = CCCoreLib::NAN_VALUE; + precisionRecallF1Score.at(realIdx, RECALL) = std::numeric_limits::quiet_NaN(); else precisionRecallF1Score.at(realIdx, RECALL) = TP / TP_FN; vec_TP_FN.at(realIdx, 0) = TP_FN; @@ -109,7 +109,7 @@ void ConfusionMatrix::computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& pr float den = precisionRecallF1Score.at(realIdx, PRECISION) + precisionRecallF1Score.at(realIdx, RECALL); if (den == 0) - precisionRecallF1Score.at(realIdx, F1_SCORE) = CCCoreLib::NAN_VALUE; + precisionRecallF1Score.at(realIdx, F1_SCORE) = std::numeric_limits::quiet_NaN(); else precisionRecallF1Score.at(realIdx, F1_SCORE) = 2 @@ -141,20 +141,19 @@ float ConfusionMatrix::computeOverallAccuracy(cv::Mat& matrix) if ((totalTrue + totalFalse) != 0) m_overallAccuracy = totalTrue / (totalTrue + totalFalse); else - m_overallAccuracy = CCCoreLib::NAN_VALUE; + m_overallAccuracy = std::numeric_limits::quiet_NaN(); return m_overallAccuracy; } -void ConfusionMatrix::compute(const std::vector& actual, const std::vector& predicted) +void ConfusionMatrix::compute(const CCCoreLib::GenericDistribution::ScalarContainer& actual, const CCCoreLib::GenericDistribution::ScalarContainer& predicted) { - int idxActual; - int idxPredicted; - int actualClass; - int predictedClass; - // get the set of classes with the contents of the actual classes - std::set classes(actual.begin(), actual.end()); + std::set classes; + for (size_t i = 0; i < actual.size(); ++i) + { + classes.insert(actual.getValue(i)); + } int nbClasses = static_cast(classes.size()); confusionMatrix = cv::Mat(nbClasses, nbClasses, CV_32S, cv::Scalar(0)); precisionRecallF1Score = cv::Mat(nbClasses, 3, CV_32F, cv::Scalar(0)); @@ -163,10 +162,10 @@ void ConfusionMatrix::compute(const std::vector& actual, const std:: // fill the confusion matrix for (int i = 0; i < actual.size(); i++) { - actualClass = actual.at(i); - idxActual = std::distance(classes.begin(), classes.find(actualClass)); - predictedClass = predicted.at(i); - idxPredicted = std::distance(classes.begin(), classes.find(predictedClass)); + int actualClass = static_cast(actual.getValue(i)); + int idxActual = std::distance(classes.begin(), classes.find(actualClass)); + int predictedClass = static_cast(predicted.getValue(i)); + int idxPredicted = std::distance(classes.begin(), classes.find(predictedClass)); confusionMatrix.at(idxActual, idxPredicted)++; } diff --git a/confusionmatrix.h b/confusionmatrix.h index bd4f363..c9996ba 100644 --- a/confusionmatrix.h +++ b/confusionmatrix.h @@ -3,7 +3,7 @@ #include #include -#include "CCTypes.h" +#include #include @@ -25,13 +25,12 @@ public: F1_SCORE = 2 }; - explicit ConfusionMatrix(const std::vector& actual, - const std::vector& predicted); + explicit ConfusionMatrix(const CCCoreLib::GenericDistribution::ScalarContainer& actual, const CCCoreLib::GenericDistribution::ScalarContainer& predicted); ~ConfusionMatrix() override; void computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& precisionRecallF1Score, cv::Mat &vec_TP_FN); float computeOverallAccuracy(cv::Mat& matrix); - void compute(const std::vector &actual, const std::vector &predicted); + void compute(const CCCoreLib::GenericDistribution::ScalarContainer& actual, const CCCoreLib::GenericDistribution::ScalarContainer& predicted); void setSessionRun(QString session, int run); bool save(QString filePath); float getOverallAccuracy(); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 28718b2..9b107fa 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -70,7 +70,7 @@ static IScalarFieldWrapper::Shared GetSource(const Feature::Source& fs, const cc case Feature::Source::ScalarField: { assert(!fs.name.isEmpty()); - int sfIdx = cloud->getScalarFieldIndexByName(qPrintable(fs.name)); + int sfIdx = cloud->getScalarFieldIndexByName(fs.name.toStdString()); if (sfIdx >= 0) { source.reset(new ScalarFieldWrapper(cloud->getScalarField(sfIdx))); @@ -288,7 +288,8 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, { if (app) { - ConfusionMatrix *confusionMatrix = new ConfusionMatrix(*classifSFBackup, *classificationSF); + ConfusionMatrix* confusionMatrix = new ConfusionMatrix(CCCoreLib::GenericDistribution::SFAsScalarContainer(*classifSFBackup), + CCCoreLib::GenericDistribution::SFAsScalarContainer(*classificationSF)); } } @@ -346,12 +347,12 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources, if (!outputSFName.isEmpty()) { - int outIdx = testCloud->getScalarFieldIndexByName(qPrintable(outputSFName)); + int outIdx = testCloud->getScalarFieldIndexByName(outputSFName.toStdString()); if (outIdx >= 0) testCloud->deleteScalarField(outIdx); else ccLog::Print("add " + outputSFName + " to the TEST cloud"); - outIdx = testCloud->addScalarField(qPrintable(outputSFName)); + outIdx = testCloud->addScalarField(outputSFName.toStdString()); outSF = static_cast(testCloud->getScalarField(outIdx)); } @@ -481,7 +482,8 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources, metrics.ratio = static_cast(metrics.goodGuess) / metrics.sampleCount; } - ConfusionMatrix* confusionMatrix = new ConfusionMatrix(actualClass, predictectedClass); + ConfusionMatrix* confusionMatrix = new ConfusionMatrix(CCCoreLib::GenericDistribution::VectorAsScalarContainer(actualClass), + CCCoreLib::GenericDistribution::VectorAsScalarContainer(predictectedClass)); train3DMASCDialog.addConfusionMatrixAndSaveTraces(confusionMatrix); if (app) { diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 24b4427..264263c 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1013,14 +1013,14 @@ CCCoreLib::ScalarField* Tools::RetrieveSF(const ccPointCloud* cloud, const QStri int sfIdx = -1; if (caseSensitive) { - sfIdx = cloud->getScalarFieldIndexByName(qPrintable(sfName)); + sfIdx = cloud->getScalarFieldIndexByName(sfName.toStdString()); } else { QString sfNameUpper = sfName.toUpper(); for (unsigned i = 0; i < cloud->getNumberOfScalarFields(); ++i) { - if (QString(cloud->getScalarField(i)->getName()).toUpper() == sfNameUpper) + if (QString::fromStdString(cloud->getScalarField(i)->getName()).toUpper() == sfNameUpper) { sfIdx = static_cast(i); break;