From 4907e68ae24a20462b2aee620628f3492986f4dc Mon Sep 17 00:00:00 2001 From: Daniel Girardeau-Montaut Date: Sun, 24 Mar 2019 23:16:55 +0100 Subject: [PATCH] Option to evaluation a classifier with a point cloud --- q3DMASC.cpp | 45 +++++++++++++++++++++++++++++++++++++------ q3DMASCClassifier.cpp | 1 + q3DMASCCommands.h | 7 +++++++ q3DMASCTools.cpp | 13 +++++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/q3DMASC.cpp b/q3DMASC.cpp index f1a3a7b..fd81d7b 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -158,6 +158,13 @@ void q3DMASCPlugin::doClassifyAction() return; } + if (clouds.contains("TEST")) + { + //remove the test cloud (if any) + delete clouds["TEST"]; + clouds.remove("TEST"); + } + //the 'main cloud' is the cloud that should be classified masc::CorePoints corePoints; corePoints.origin = corePoints.cloud = clouds[mainCloudLabel]; @@ -317,6 +324,7 @@ void q3DMASCPlugin::doTrainAction() delete group; return; } + if (corePoints.cloud != corePoints.origin) { //auto-hide the other clouds @@ -342,6 +350,7 @@ void q3DMASCPlugin::doTrainAction() corePoints.cloud->setName(QString("Core points (%1)").arg(corePointsName)); group->addChild(corePoints.cloud); } + if (group->getChildrenNumber() != 0) { m_app->addToDB(group); @@ -353,10 +362,19 @@ void q3DMASCPlugin::doTrainAction() group = nullptr; } + //test role + ccPointCloud* testCloud = nullptr; + if (loadedClouds.contains("TEST")) + { + testCloud = loadedClouds["TEST"]; + loadedClouds.remove("TEST"); + } + + //train / test subsets QScopedPointer trainSubset(new CCLib::ReferenceCloud(corePoints.cloud)); QScopedPointer testSubset(new CCLib::ReferenceCloud(corePoints.cloud)); - float previousTrainSubsetRatio = -1.0f; + float previousTestSubsetRatio = -1.0f; SFCollector generatedScalarFields; @@ -418,15 +436,30 @@ void q3DMASCPlugin::doTrainAction() s_params.rt.maxTreeCount = trainDlg.maxTreeCountSpinBox->value(); s_params.rt.activeVarCount = trainDlg.activeVarCountSpinBox->value(); s_params.rt.minSampleCount = trainDlg.minSampleCountSpinBox->value(); - s_params.testDataRatio = trainDlg.testDataRatioSpinBox->value() / 100.0f; - if (s_params.testDataRatio < 0 || s_params.testDataRatio > 0.99f) + float testDataRatio = s_params.testDataRatio = trainDlg.testDataRatioSpinBox->value() / 100.0f; + QScopedPointer testSubset2; + if (testCloud) + { + m_app->dispToConsole("Test data cloud provided (ignoring test data ratio)", ccMainAppInterface::WRN_CONSOLE_MESSAGE); + testDataRatio = 0.0f; + testSubset2.reset(new CCLib::ReferenceCloud(testCloud)); + if (!testSubset2->reserve(testCloud->size())) + { + m_app->dispToConsole("Not enough memory to evaluate the classifier", ccMainAppInterface::ERR_CONSOLE_MESSAGE); + generatedScalarFields.releaseAllSFs(); + return; + } + testSubset2->addPointIndex(0, testCloud->size()); + } + + if (s_params.testDataRatio < 0.0f || s_params.testDataRatio > 0.99f) { assert(false); m_app->dispToConsole("Invalid test data ratio", ccMainAppInterface::ERR_CONSOLE_MESSAGE); } else { - if (previousTrainSubsetRatio != s_params.testDataRatio) + if (previousTestSubsetRatio != s_params.testDataRatio) { //randomly select the training points testSubset->clear(); @@ -437,7 +470,7 @@ void q3DMASCPlugin::doTrainAction() generatedScalarFields.releaseAllSFs(); return; } - previousTrainSubsetRatio = s_params.testDataRatio; + previousTestSubsetRatio = s_params.testDataRatio; } //train the classifier @@ -457,7 +490,7 @@ void q3DMASCPlugin::doTrainAction() { masc::Classifier::AccuracyMetrics metrics; QString errorMessage; - if (!classifier.evaluate(features, testSubset.data(), metrics, errorMessage, m_app->getMainWindow())) + if (!classifier.evaluate(features, testSubset2 ? testSubset2.data() : testSubset.data(), metrics, errorMessage, m_app->getMainWindow())) { m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE); generatedScalarFields.releaseAllSFs(); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index a9a1b5f..f4fe8ab 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -245,6 +245,7 @@ bool Classifier::evaluate(const Feature::Set& features, CCLib::ReferenceCloud* t } if (!testSubset) { + assert(false); errorMessage = QObject::tr("No test subset provided"); return false; } diff --git a/q3DMASCCommands.h b/q3DMASCCommands.h index 460aa5f..c627cdf 100644 --- a/q3DMASCCommands.h +++ b/q3DMASCCommands.h @@ -131,6 +131,13 @@ struct Command3DMASCClassif : public ccCommandLineInterface::Command return cmd.error("Classified cloud not loaded/defined?!"); } + //remove the test cloud (if any) + if (cloudPerRole.contains("TEST")) + { + delete cloudPerRole["TEST"]; + cloudPerRole.remove("TEST"); + } + //the 'main cloud' is the cloud that should be classified masc::CorePoints corePoints; corePoints.origin = corePoints.cloud = cloudPerRole[mainCloudRole]; diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 69a7fa5..4e1afff 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -694,6 +694,19 @@ static bool LoadFileCommon( const QString& filename, return false; } } + else if (upperLine.startsWith("TEST:")) //test cloud + { + if (cloudsAreProvided) + { + //no need to load the clouds in this case + continue; + } + QString command = line.mid(5); + if (!ReadCloud("TEST=" + command, clouds, fi.absoluteDir(), lineNumber)) //add the TEST keyword so that the cloud will be loaded as the TEST cloud + { + return false; + } + } else if (upperLine.startsWith("CORE_POINTS:")) //core points { if (!corePoints)