Prevent the user from cancelling the training.

This commit is contained in:
Paul Leroy
2023-05-31 22:00:10 +02:00
parent 4f6cd5fe6d
commit 9bbf288b8f
7 changed files with 109 additions and 72 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ if (INSTALL_Q3DMASC_PLUGIN)
project( Q3DMASC_PLUGIN )
AddPlugin( NAME ${PROJECT_NAME} )
set(Q3DMASC_PLUGIN_VERSION "0.7+")
set(Q3DMASC_PLUGIN_VERSION "0.8")
include( CMakePolicies NO_POLICY_SCOPE )
+2 -2
View File
@@ -51,12 +51,12 @@ CCCoreLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resu
int sfIdx = cloud->getScalarFieldIndexByName(resultSFName);
if (sfIdx >= 0)
{
ccLog::Warning("Existing SF: " + QString(resultSFName) + ", do not store in generatedScalarFields");
// ccLog::Warning("Existing SF: " + QString(resultSFName) + ", do not store in generatedScalarFields");
resultSF = cloud->getScalarField(sfIdx);
}
else
{
ccLog::Warning("SF does not exist, create it: " + QString(resultSFName) + ", SFCollector::Behavior " + QString::number(behavior));
// ccLog::Warning("SF does not exist, create it: " + QString(resultSFName) + ", SFCollector::Behavior " + QString::number(behavior));
ccScalarField* newSF = new ccScalarField(resultSFName);
if (!newSF->resizeSafe(cloud->size()))
{
+8 -8
View File
@@ -29,10 +29,10 @@
void SFCollector::push(ccPointCloud* cloud, CCCoreLib::ScalarField* sf, Behavior behavior)
{
assert(!scalarFields.contains(sf));
if (scalarFields.contains(sf))
ccLog::Warning(QString("[SFCollector] scalar field '%1' HAS ALREADY BEEN COLLECTED").arg(sf->getName()) + ", behaviour " + QString::number(behavior));
else
ccLog::Warning(QString("[SFCollector] collect scalar field '%1'").arg(sf->getName()) + ", behaviour " + QString::number(behavior));
// if (scalarFields.contains(sf))
// ccLog::Warning(QString("[SFCollector] scalar field '%1' HAS ALREADY BEEN COLLECTED").arg(sf->getName()) + ", behaviour " + QString::number(behavior));
// else
// ccLog::Warning(QString("[SFCollector] collect scalar field '%1'").arg(sf->getName()) + ", behaviour " + QString::number(behavior));
SFDesc desc;
desc.behavior = behavior;
desc.cloud = cloud;
@@ -48,7 +48,7 @@ void SFCollector::releaseSFs(bool keepByDefault)
if (desc.behavior == ALWAYS_KEEP || (keepByDefault && desc.behavior == CAN_REMOVE))
{
ccLog::Warning(QString("[SFCollector] Keep scalar field '%1'").arg(sf->getName()));
// ccLog::Warning(QString("[SFCollector] Keep scalar field '%1'").arg(sf->getName()));
//keep this SF
continue;
}
@@ -56,7 +56,7 @@ void SFCollector::releaseSFs(bool keepByDefault)
int sfIdx = desc.cloud->getScalarFieldIndexByName(sf->getName());
if (sfIdx >= 0)
{
ccLog::Warning(QString("[SFCollector] Remove scalar field '%1'").arg(sf->getName()));
// ccLog::Warning(QString("[SFCollector] Remove scalar field '%1'").arg(sf->getName()));
desc.cloud->deleteScalarField(sfIdx);
}
else
@@ -72,9 +72,9 @@ bool SFCollector::setBehavior(CCCoreLib::ScalarField *sf, Behavior behavior)
{
if (scalarFields.contains(sf))
{
Behavior previousBehavior = scalarFields[sf].behavior;
// Behavior previousBehavior = scalarFields[sf].behavior;
scalarFields[sf].behavior = behavior;
ccLog::Warning("behavior of " + QString(sf->getName()) + " changed from " + QString::number(previousBehavior) + " to " + QString::number(behavior));
// ccLog::Warning("behavior of " + QString(sf->getName()) + " changed from " + QString::number(previousBehavior) + " to " + QString::number(behavior));
}
return true;
-3
View File
@@ -229,8 +229,6 @@ void ConfusionMatrix::compute(const std::vector<ScalarType>& actual, const std::
// FILL THE QTABLEWIDGET
// add the confusion matrix values
QBrush greenBrush(QColorConstants::Svg::palegreen);
QFont f( "Tahoma", 10, QFont::Bold );
for (int row = 0; row < nbClasses; row++)
for (int column = 0; column < nbClasses; column++)
{
@@ -239,7 +237,6 @@ void ConfusionMatrix::compute(const std::vector<ScalarType>& actual, const std::
if (row == column)
{
newItem->setBackground(getColor(val / vec_TP_FN.at<int>(row, 0), 0, 128, 255));
newItem->setFont(f);
}
else
newItem->setBackground(getColor(val / vec_TP_FN.at<int>(row, 0), 200, 50, 50));
+2 -2
View File
@@ -507,7 +507,7 @@ void q3DMASCPlugin::doTrainAction()
{
progressDlg.setAutoClose(false); //we don't want the progress dialog to 'pop' for each feature
QString error;
if (!masc::Tools::PrepareFeatures(corePoints, toPrepare, error, &progressDlg, &generatedScalarFields))
if (!masc::Tools::PrepareFeatures(corePoints, toPrepare, error, &progressDlg, &generatedScalarFields))
{
m_app->dispToConsole(error, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
generatedScalarFields.releaseSFs(false);
@@ -655,7 +655,7 @@ void q3DMASCPlugin::doTrainAction()
errorMessage,
trainDlg,
testCloud ? nullptr : testSubset.data(),
"Classification_pred",
testCloud ? "Classification_prediction" : "", // outputSFName, empty is the test cloud is not a separate cloud
m_app->getMainWindow()))
{
m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
+71 -37
View File
@@ -37,6 +37,7 @@
#include <QCoreApplication>
#include <QProgressDialog>
#include <QtConcurrent>
#include <QMessageBox>
#include "qTrain3DMASCDialog.h"
#include "confusionmatrix.h"
@@ -123,28 +124,30 @@ bool Classifier::classify( const Feature::Source::Set& featureSources,
return false;
}
//look for the classification field
CCCoreLib::ScalarField* classificationSF = Tools::GetClassificationSF(cloud);
// add a ccConfidence value if needed
int cvConfidenceIdx = cloud->getScalarFieldIndexByName("Classification_confidence");
if (cvConfidenceIdx > 0) // if the scalar field exists, delete it
if (cvConfidenceIdx >= 0) // if the scalar field exists, delete it
cloud->deleteScalarField(cvConfidenceIdx);
cvConfidenceIdx = cloud->addScalarField("Classification_confidence");
CCCoreLib::ScalarField* cvConfidenceSF = cloud->getScalarField(cvConfidenceIdx);
//look for the classification field
CCCoreLib::ScalarField* classificationSF = Tools::GetClassificationSF(cloud);
ccScalarField* classifSFBackup = nullptr;
if (classificationSF)
if (classificationSF) //save classification field (if any)
{
//save previous classification field (if any)
int sfIdx = cloud->getScalarFieldIndexByName("Classification_prev");
if (sfIdx > 0)
ccLog::Warning("Classification SF found: copy it in Classification_backup, a confusion matrix will be generated");
// delete Classification_backup field (if any)
int sfIdx = cloud->getScalarFieldIndexByName("Classification_backup");
if (sfIdx >= 0)
cloud->deleteScalarField(sfIdx);
// backup the classification field
try
{
classifSFBackup = new ccScalarField(*static_cast<ccScalarField*>(classificationSF));
classifSFBackup->setName("Classification_prev");
classifSFBackup = new ccScalarField(*static_cast<ccScalarField*>(classificationSF)); // copy constructor
classifSFBackup->setName("Classification_backup");
cloud->addScalarField(classifSFBackup);
}
catch (const std::bad_alloc)
@@ -237,7 +240,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources,
cv::Mat result;
m_rtrees->getVotes(test_data, result, cv::ml::DTrees::PREDICT_MAX_VOTE);
int classIndex = -1;
for (int col = 0; col < result.cols; col++)
for (int col = 0; col < result.cols; col++) // look for the index of the predicted class
if (predictedClass == result.at<int>(0, col))
{
classIndex = col;
@@ -245,8 +248,8 @@ bool Classifier::classify( const Feature::Source::Set& featureSources,
}
if (classIndex != -1)
{
float nbVotes = result.at<int>(1, classIndex);
cvConfidenceSF->setValue(i, static_cast<ScalarType>(nbVotes / numberOfTrees));
float nbVotes = result.at<int>(1, classIndex); // get the number of votes
cvConfidenceSF->setValue(i, static_cast<ScalarType>(nbVotes / numberOfTrees)); // compute the confidence
}
else
cvConfidenceSF->setValue(i, CCCoreLib::NAN_VALUE);
@@ -326,28 +329,31 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources,
return false;
}
CCCoreLib::ScalarField* outputSF = nullptr;
CCCoreLib::ScalarField* outSF = nullptr;
CCCoreLib::ScalarField* cvConfidenceSF = nullptr;
ccLog::Warning("[evaluate] TEST cloud " + testCloud->getName());
if (!outputSFName.isEmpty())
{
int outSFIndex = testCloud->getScalarFieldIndexByName(qPrintable(outputSFName));
if (outSFIndex < 0)
{
ccScalarField* _outputSF = new ccScalarField(qPrintable(outputSFName));
if (!_outputSF->resizeSafe(testCloud->size()))
{
errorMessage = QObject::tr("Not enough memory to create output scalar field");
_outputSF->release();
return false;
}
testCloud->addScalarField(_outputSF);
outputSF = _outputSF;
}
int outIdx = testCloud->getScalarFieldIndexByName(qPrintable(outputSFName));
if (outIdx >= 0)
testCloud->deleteScalarField(outIdx);
else
{
outputSF = testCloud->getScalarField(outSFIndex);
}
outputSF->fill(CCCoreLib::NAN_VALUE);
outputSF->computeMinAndMax();
ccLog::Warning("add " + outputSFName + " to the TEST cloud");
outIdx = testCloud->addScalarField(qPrintable(outputSFName));
outSF = testCloud->getScalarField(outIdx);
}
if (outSF) // add a Classification_confidence value to the test cloud if needed
{
int cvConfidenceIdx = testCloud->getScalarFieldIndexByName("Classification_confidence");
if (cvConfidenceIdx >= 0) // if the scalar field exists, delete it
testCloud->deleteScalarField(cvConfidenceIdx);
else
ccLog::Warning("add Classification_confidence to the TEST cloud");
cvConfidenceIdx = testCloud->addScalarField("Classification_confidence");
cvConfidenceSF = testCloud->getScalarField(cvConfidenceIdx);
}
unsigned testSampleCount = (testSubset ? testSubset->size() : testCloud->size());
@@ -397,6 +403,7 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources,
}
}
int numberOfTrees = m_rtrees->getRoots().size();
//estimate the efficiency of the classifier
std::vector<ScalarType> actualClass(testSampleCount);
@@ -424,9 +431,29 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources,
{
++metrics.goodGuess;
}
if (outputSF)
if (outSF)
{
outputSF->setValue(pointIndex, static_cast<ScalarType>(iPredictedClass));
outSF->setValue(pointIndex, static_cast<ScalarType>(iPredictedClass));
if (cvConfidenceSF)
{
// compute the confidence
cv::Mat result;
m_rtrees->getVotes(test_data.row(i), result, cv::ml::DTrees::PREDICT_MAX_VOTE);
int classIndex = -1;
for (int col = 0; col < result.cols; col++) // look for the index of the predicted class
if (iPredictedClass == result.at<int>(0, col))
{
classIndex = col;
break;
}
if (classIndex != -1)
{
float nbVotes = result.at<int>(1, classIndex); // get the number of votes
cvConfidenceSF->setValue(i, static_cast<ScalarType>(nbVotes / numberOfTrees)); // compute the confidence
}
else
cvConfidenceSF->setValue(i, CCCoreLib::NAN_VALUE);
}
}
if (pDlg && !nProgress.oneStep())
@@ -436,8 +463,10 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources,
}
}
if (outputSF)
outputSF->computeMinAndMax();
if (outSF)
outSF->computeMinAndMax();
if (cvConfidenceSF)
cvConfidenceSF->computeMinAndMax();
metrics.ratio = static_cast<float>(metrics.goodGuess) / metrics.sampleCount;
}
@@ -617,8 +646,13 @@ bool Classifier::train( const ccPointCloud* cloud,
{
if (pDlg->wasCanceled())
{
future.cancel();
break;
// future.cancel();
QMessageBox msgBox;
msgBox.setText("The training is still in progress, not possible to cancel.");
msgBox.exec();
// break;
pDlg->reset();
pDlg->show();
}
pDlg->setValue(pDlg->value() + 1);
}
+25 -19
View File
@@ -1060,9 +1060,8 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features
return false;
}
// if the feature already exists and if useExistingFeatures is checked, simply populate generatedScalarFields
//prepare the feature
if (!feature->prepare(corePoints, errorStr, progressCb, generatedScalarFields))
if (!feature->prepare(corePoints, errorStr, progressCb, generatedScalarFields))
{
//something failed (error should be up to date)
return false;
@@ -1089,18 +1088,21 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features
fas.scales.push_back(feature->scale);
}
}
//build the scaled feature list attached to the second cloud (if any)
if (feature->cloud2 && feature->cloud2 != feature->cloud1 && feature->op != Feature::NO_OPERATION
&& !static_cast<PointFeature*>(feature.data())->statSF1WasAlreadyExisting
&& !static_cast<PointFeature*>(feature.data())->statSF2WasAlreadyExisting) // nothing to compute if the scalar field was already there
if (feature->cloud2
&& feature->cloud2 != feature->cloud1
&& feature->op != Feature::NO_OPERATION
&& !static_cast<PointFeature*>(feature.data())->statSF1WasAlreadyExisting) // nothing to compute if the scalar field was already there
{
FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud2];
++fas.featureCount;
fas.pointFeaturesPerScale[feature->scale].push_back(qSharedPointerCast<PointFeature>(feature));
if (std::find(fas.scales.begin(), fas.scales.end(), feature->scale) == fas.scales.end())
if (!static_cast<PointFeature*>(feature.data())->statSF2WasAlreadyExisting)
{
fas.scales.push_back(feature->scale);
FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud2];
++fas.featureCount;
fas.pointFeaturesPerScale[feature->scale].push_back(qSharedPointerCast<PointFeature>(feature));
if (std::find(fas.scales.begin(), fas.scales.end(), feature->scale) == fas.scales.end())
{
fas.scales.push_back(feature->scale);
}
}
}
}
@@ -1123,16 +1125,20 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features
}
//build the scaled feature list attached to the second cloud (if any)
if (feature->cloud2 && feature->cloud2 != feature->cloud1 && feature->op != Feature::NO_OPERATION
&& !static_cast<NeighborhoodFeature*>(feature.data())->sf1WasAlreadyExisting
&& !static_cast<NeighborhoodFeature*>(feature.data())->sf2WasAlreadyExisting) // nothing to compute if the scalar field was already there
if (feature->cloud2
&& feature->cloud2 != feature->cloud1
&& feature->op != Feature::NO_OPERATION
&& !static_cast<NeighborhoodFeature*>(feature.data())->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there
{
FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud2];
fas.neighborhoodFeaturesPerScale[feature->scale].push_back(qSharedPointerCast<NeighborhoodFeature>(feature));
++fas.featureCount;
if (std::find(fas.scales.begin(), fas.scales.end(), feature->scale) == fas.scales.end())
if (!static_cast<NeighborhoodFeature*>(feature.data())->sf2WasAlreadyExisting)
{
fas.scales.push_back(feature->scale);
FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud2];
fas.neighborhoodFeaturesPerScale[feature->scale].push_back(qSharedPointerCast<NeighborhoodFeature>(feature));
++fas.featureCount;
if (std::find(fas.scales.begin(), fas.scales.end(), feature->scale) == fas.scales.end())
{
fas.scales.push_back(feature->scale);
}
}
}
}