Scalar fields can now handle large values (with an internal double offset). Their names can also be longer than 255 characters.

This commit is contained in:
Daniel Girardeau-Montaut
2024-11-10 17:32:37 +01:00
parent 8a9112cfd5
commit efab553b92
10 changed files with 68 additions and 61 deletions
+3 -3
View File
@@ -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
+10 -7
View File
@@ -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");
+2 -2
View File
@@ -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);
+6 -6
View File
@@ -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)
{
+18 -14
View File
@@ -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<ScalarType> 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;
+2 -2
View File
@@ -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:
+15 -16
View File
@@ -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<ScalarType> &actual, const std::vector<ScalarType> &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<float>(predictedIdx, PRECISION) = CCCoreLib::NAN_VALUE;
precisionRecallF1Score.at<float>(predictedIdx, PRECISION) = std::numeric_limits<float>::quiet_NaN();
else
precisionRecallF1Score.at<float>(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<float>(realIdx, RECALL) = CCCoreLib::NAN_VALUE;
precisionRecallF1Score.at<float>(realIdx, RECALL) = std::numeric_limits<float>::quiet_NaN();
else
precisionRecallF1Score.at<float>(realIdx, RECALL) = TP / TP_FN;
vec_TP_FN.at<int>(realIdx, 0) = TP_FN;
@@ -109,7 +109,7 @@ void ConfusionMatrix::computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& pr
float den = precisionRecallF1Score.at<float>(realIdx, PRECISION)
+ precisionRecallF1Score.at<float>(realIdx, RECALL);
if (den == 0)
precisionRecallF1Score.at<float>(realIdx, F1_SCORE) = CCCoreLib::NAN_VALUE;
precisionRecallF1Score.at<float>(realIdx, F1_SCORE) = std::numeric_limits<float>::quiet_NaN();
else
precisionRecallF1Score.at<float>(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<float>::quiet_NaN();
return m_overallAccuracy;
}
void ConfusionMatrix::compute(const std::vector<ScalarType>& actual, const std::vector<ScalarType>& 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<ScalarType> classes(actual.begin(), actual.end());
std::set<ScalarType> classes;
for (size_t i = 0; i < actual.size(); ++i)
{
classes.insert(actual.getValue(i));
}
int nbClasses = static_cast<int>(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<ScalarType>& 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<int>(actual.getValue(i));
int idxActual = std::distance(classes.begin(), classes.find(actualClass));
int predictedClass = static_cast<int>(predicted.getValue(i));
int idxPredicted = std::distance(classes.begin(), classes.find(predictedClass));
confusionMatrix.at<int>(idxActual, idxPredicted)++;
}
+3 -4
View File
@@ -3,7 +3,7 @@
#include <QWidget>
#include <set>
#include "CCTypes.h"
#include <GenericDistribution.h>
#include <ccMainAppInterface.h>
@@ -25,13 +25,12 @@ public:
F1_SCORE = 2
};
explicit ConfusionMatrix(const std::vector<ScalarType>& actual,
const std::vector<ScalarType>& 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<ScalarType> &actual, const std::vector<ScalarType> &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();
+7 -5
View File
@@ -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<ccScalarField*>(testCloud->getScalarField(outIdx));
}
@@ -481,7 +482,8 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources,
metrics.ratio = static_cast<float>(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)
{
+2 -2
View File
@@ -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<int>(i);
break;