From 2fcee9db76f500f15d560a169ce4fa518f581b82 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Thu, 8 Feb 2024 17:11:38 +0100 Subject: [PATCH 01/14] Do not show confusion matrix when in command line mode --- confusionmatrix.cpp | 8 +++++--- confusionmatrix.h | 7 ++++++- q3DMASCClassifier.cpp | 10 ++++++++-- q3DMASCClassifier.h | 3 ++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/confusionmatrix.cpp b/confusionmatrix.cpp index 1e34cd2..cca7ac6 100644 --- a/confusionmatrix.cpp +++ b/confusionmatrix.cpp @@ -32,7 +32,7 @@ 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, QWidget *parent) : +ConfusionMatrix::ConfusionMatrix(const std::vector &actual, const std::vector &predicted, QWidget *parent, ccMainAppInterface *app) : QWidget(parent), ui(new Ui::ConfusionMatrix) { @@ -41,11 +41,13 @@ ConfusionMatrix::ConfusionMatrix(const std::vector &actual, const st compute(actual, predicted); - this->show(); + if (app) + { + this->show(); + } this->ui->tableWidget->resizeColumnsToContents(); this->ui->tableWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); QSize tableSize = this->ui->tableWidget->sizeHint(); - QSize labelSize = this->ui->label->sizeHint(); QSize widgetSize = QSize(tableSize.width() + 30, tableSize.height() + 50); this->setMinimumSize(widgetSize); } diff --git a/confusionmatrix.h b/confusionmatrix.h index 25bfc42..1675bea 100644 --- a/confusionmatrix.h +++ b/confusionmatrix.h @@ -5,6 +5,8 @@ #include "CCTypes.h" +#include + #include namespace Ui { @@ -23,7 +25,10 @@ public: F1_SCORE = 2 }; - explicit ConfusionMatrix(const std::vector& actual, const std::vector& predicted, QWidget *parent = nullptr); + explicit ConfusionMatrix( const std::vector& actual, + const std::vector& predicted, + QWidget *parent = nullptr, + ccMainAppInterface* app = nullptr); ~ConfusionMatrix() override; void computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& precisionRecallF1Score, cv::Mat &vec_TP_FN); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 0fa1c6f..02e3577 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -110,7 +110,8 @@ static IScalarFieldWrapper::Shared GetSource(const Feature::Source& fs, const cc bool Classifier::classify( const Feature::Source::Set& featureSources, ccPointCloud* cloud, QString& errorMessage, - QWidget* parentWidget/*=nullptr*/ + QWidget* parentWidget/*=nullptr*/, + ccMainAppInterface* app/*nullptr*/ ) { if (!cloud) @@ -278,7 +279,12 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, } if (classifSFBackup != nullptr) - ConfusionMatrix *confusionMatrix = new ConfusionMatrix(*classifSFBackup, *classificationSF); + { + if (app) + { + ConfusionMatrix *confusionMatrix = new ConfusionMatrix(*classifSFBackup, *classificationSF); + } + } return success; } diff --git a/q3DMASCClassifier.h b/q3DMASCClassifier.h index c24611a..e2b313b 100644 --- a/q3DMASCClassifier.h +++ b/q3DMASCClassifier.h @@ -76,7 +76,8 @@ namespace masc bool classify( const Feature::Source::Set& featureSources, ccPointCloud* cloud, QString& errorMessage, - QWidget* parentWidget = nullptr); + QWidget* parentWidget = nullptr, + ccMainAppInterface* app = nullptr); //! Returns whether the classifier is valid or not bool isValid() const; From a65cb83279c6527b665e77924a636f48f84b5e61 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Fri, 9 Feb 2024 14:38:54 +0100 Subject: [PATCH 02/14] prevent confusion matrix to pop up when in command line mode --- q3DMASC.cpp | 3 ++- q3DMASCClassifier.cpp | 5 +++-- q3DMASCClassifier.h | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/q3DMASC.cpp b/q3DMASC.cpp index 0931cc1..18391de 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -653,7 +653,8 @@ void q3DMASCPlugin::doTrainAction() trainDlg, testCloud ? nullptr : testSubset.data(), testCloud ? "Classification_prediction" : "", // outputSFName, empty is the test cloud is not a separate cloud - m_app->getMainWindow())) + m_app->getMainWindow(), + m_app)) { m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE); generatedScalarFields.releaseSFs(false); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 02e3577..60aa91d 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -296,7 +296,8 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources, Train3DMASCDialog& train3DMASCDialog, CCCoreLib::ReferenceCloud* testSubset/*=nullptr=*/, QString outputSFName/*=QString()*/, - QWidget* parentWidget/*=nullptr*/) + QWidget* parentWidget/*=nullptr*/, + ccMainAppInterface *app/*=nullptr*/) { if (!testCloud) { @@ -474,7 +475,7 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources, metrics.ratio = static_cast(metrics.goodGuess) / metrics.sampleCount; } - train3DMASCDialog.addConfusionMatrixAndSaveTraces(new ConfusionMatrix(actualClass, predictectedClass)); + train3DMASCDialog.addConfusionMatrixAndSaveTraces(new ConfusionMatrix(actualClass, predictectedClass, nullptr, app)); //show the Classification_prediction field by default if (outSF) diff --git a/q3DMASCClassifier.h b/q3DMASCClassifier.h index e2b313b..f615c56 100644 --- a/q3DMASCClassifier.h +++ b/q3DMASCClassifier.h @@ -70,7 +70,8 @@ namespace masc Train3DMASCDialog& train3DMASCDialog, CCCoreLib::ReferenceCloud* testSubset = nullptr, QString outputSFName = QString(), - QWidget* parentWidget = nullptr); + QWidget* parentWidget = nullptr, + ccMainAppInterface* app = nullptr); //! Applies the classifier bool classify( const Feature::Source::Set& featureSources, From be68cde7e3651a1d8fbab28337a219e53bb0309e Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Mon, 12 Feb 2024 22:37:48 +0100 Subject: [PATCH 03/14] use num_threads instead of omp_set_num_threads --- CMakeLists.txt | 2 +- ContextBasedFeature.cpp | 10 ++++++---- PointFeature.cpp | 3 +-- q3DMASC.cpp | 2 +- q3DMASCClassifier.cpp | 3 +-- q3DMASCTools.cpp | 19 ++++++++++--------- q3DMASCTools.h | 4 ++-- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a05e70d..1e718b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ if (PLUGIN_STANDARD_3DMASC) project( Q3DMASC_PLUGIN ) AddPlugin( NAME ${PROJECT_NAME} ) - set(Q3DMASC_PLUGIN_VERSION "0.12") + set(Q3DMASC_PLUGIN_VERSION "0.12+") include( CMakePolicies NO_POLICY_SCOPE ) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index ef9b066..9471118 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -136,7 +136,10 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, if (!scaled() && !sfWasAlreadyExisting) //with 'kNN' neighbors, we can compute the values right away { unsigned pointCount = corePoints.size(); - QString logMessage = QString("Computing %1 on cloud %2 with context cloud %3\n(core points: %4)").arg(typeStr).arg(corePoints.cloud->getName()).arg(cloud1Label).arg(pointCount); + QString logMessage = "Computing " + typeStr + + " on cloud " + corePoints.cloud->getName() + " (" + QString::number(pointCount) + " points)" + + " with context cloud " + cloud1Label + + " (class " + QString::number(ctxClassLabel) + ")"; //first: look for the number of points in the relevent class const ScalarType fClass = static_cast(ctxClassLabel); @@ -165,7 +168,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, } //compute the octree - ccLog::Print(QString("Computing octree of class %1 points (%2 points)").arg(ctxClassLabel).arg(classCount)); + ccLog::Print(QString("Computing octree of class %1 (%2 points)").arg(ctxClassLabel).arg(classCount)); ccOctree::Shared classOctree = classCloud.computeOctree(progressCb); if (!classOctree) { @@ -191,8 +194,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) - omp_set_num_threads(std::max(1, omp_get_max_threads() - 2)); -#pragma omp parallel for +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) diff --git a/PointFeature.cpp b/PointFeature.cpp index 5e2d56a..b73bd46 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -424,8 +424,7 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, error.clear(); #ifndef _DEBUG #if defined(_OPENMP) - omp_set_num_threads(std::max(1, omp_get_max_threads() - 2)); -#pragma omp parallel for +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) diff --git a/q3DMASC.cpp b/q3DMASC.cpp index cf3e2d4..d0b3caf 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -372,7 +372,7 @@ void q3DMASCPlugin::doTrainAction() masc::TrainParameters tempParams; if (!masc::Tools::LoadTrainingFile(inputFilename, featuresTest, scalesTest, loadedCloudsTest, tempParams)) { - m_app->dispToConsole("Failed to load the training file (for test)", ccMainAppInterface::ERR_CONSOLE_MESSAGE); + m_app->dispToConsole("Failed to load the training file (for TEST)", ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; } } diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 7a7464a..089f815 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -204,8 +204,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, int numberOfTrees = static_cast(m_rtrees->getRoots().size()); #ifndef _DEBUG #if defined(_OPENMP) - omp_set_num_threads(std::max(1, omp_get_max_threads() - 2)); -#pragma omp parallel for +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(cloud->size()); ++i) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 68eb972..d8deeec 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1223,20 +1223,21 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features unsigned char octreeLevel = octree->findBestLevelForAGivenNeighbourhoodSizeExtraction(largestRadius); unsigned pointCount = corePoints.size(); - QString logMessage = QString("Computing %1 features on cloud %2\n(core points: %3)").arg(fas.featureCount).arg(sourceCloud->getName()).arg(pointCount); + QString logMessage = QString("Computing %1 features on cloud %2 (%3 core points)").arg(fas.featureCount).arg(sourceCloud->getName()).arg(pointCount); if (progressCb) { progressCb->setMethodTitle("Compute features"); progressCb->setInfo(qPrintable(logMessage)); } - ccLog::Print(logMessage); + ccLog::Print(logMessage + " , nb threads " + QString::number(omp_get_max_threads() - 2) + ", nb points " + QString::number(pointCount)); CCCoreLib::NormalizedProgress nProgress(progressCb, pointCount); + nProgress.reset(); QMutex mutex; + #ifndef _DEBUG #if defined(_OPENMP) - omp_set_num_threads(std::max(1, omp_get_max_threads() - 2)); -#pragma omp parallel for +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) @@ -1372,10 +1373,10 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { break; } - } //for each scale + } //for each scale } - + if (progressCb) { mutex.lock(); @@ -1385,15 +1386,15 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { //process cancelled by the user ccLog::Warning("Process cancelled"); - errorStr = "Process cancelled"; + errorStr = "Process cancelled, iteration " + QString::number(i); success = false; break; } } - } //for each point - + } //for each cloud + } for (const Feature::Shared& feature : features) diff --git a/q3DMASCTools.h b/q3DMASCTools.h index 6105eb4..bb3eb6c 100644 --- a/q3DMASCTools.h +++ b/q3DMASCTools.h @@ -56,8 +56,8 @@ namespace masc static bool SaveClassifier(QString filename, const Feature::Set& features, const QString corePointsRole, const masc::Classifier& classifier, QWidget* parent = nullptr); - static bool PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& error, - CCCoreLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr); + static bool PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& error, + CCCoreLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr); static bool RandomSubset(ccPointCloud* cloud, float ratio, CCCoreLib::ReferenceCloud* inRatioSubset, CCCoreLib::ReferenceCloud* outRatioSubset); From 11ed7bd843867ed8a32473955d10728ee61e663a Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Tue, 13 Feb 2024 09:22:50 +0100 Subject: [PATCH 04/14] use ccQtHelpers::GetMaxThreadCount --- ContextBasedFeature.cpp | 5 ++++- PointFeature.cpp | 5 ++++- q3DMASCClassifier.cpp | 5 +++-- q3DMASCTools.cpp | 10 +++++++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index 9471118..eb158d5 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -23,6 +23,9 @@ //qCC_db #include +//CCPluginAPI +#include + //Qt #include @@ -194,7 +197,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) +#pragma omp parallel for num_threads(ccQtHelpers::GetMaxThreadCount(omp_get_max_threads())) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) diff --git a/PointFeature.cpp b/PointFeature.cpp index b73bd46..0017b69 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -89,6 +89,9 @@ constexpr const char* LAS_FIELD_NAMES[22] = {"X", //CCLib #include +//CCPluginAPI +#include + //system #include @@ -424,7 +427,7 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, error.clear(); #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) +#pragma omp parallel for num_threads(ccQtHelpers::GetMaxThreadCount(omp_get_max_threads())) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 8b09c47..ea3356e 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -30,8 +30,9 @@ //qPDALIO #include "../../../core/IO/qPDALIO/include/LASFields.h" -//qCC_plugins +//CCPluginAPI #include +#include //Qt #include @@ -209,7 +210,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, int numberOfTrees = static_cast(m_rtrees->getRoots().size()); #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) +#pragma omp parallel for num_threads(ccQtHelpers::GetMaxThreadCount(omp_get_max_threads())) #endif #endif for (int i = 0; i < static_cast(cloud->size()); ++i) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index d8deeec..32833dd 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -30,6 +30,9 @@ #include #include +//CCPluginAPI +#include + //qPDALIO #include "../../../core/IO/qPDALIO/include/LASFields.h" @@ -1229,15 +1232,16 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features progressCb->setMethodTitle("Compute features"); progressCb->setInfo(qPrintable(logMessage)); } - ccLog::Print(logMessage + " , nb threads " + QString::number(omp_get_max_threads() - 2) + ", nb points " + QString::number(pointCount)); + ccLog::Print(logMessage + ", nb points " + QString::number(pointCount)); CCCoreLib::NormalizedProgress nProgress(progressCb, pointCount); - nProgress.reset(); QMutex mutex; #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) + int num_threads = ccQtHelpers::GetMaxThreadCount(omp_get_max_threads()); + ccLog::Print("Using OpenMP with " + QString::number(num_threads) + " threads "); +#pragma omp parallel for num_threads(num_threads) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) From a01d4a81fd6375d867f77c18940d18ed9d7af11b Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Tue, 13 Feb 2024 14:35:17 +0100 Subject: [PATCH 05/14] remove breaks from omp parallel loops --- ContextBasedFeature.cpp | 9 ++++----- PointFeature.cpp | 12 ++++++------ q3DMASCClassifier.cpp | 13 +++++++++---- q3DMASCTools.cpp | 16 ++++++++-------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index eb158d5..acb6950 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -23,9 +23,6 @@ //qCC_db #include -//CCPluginAPI -#include - //Qt #include @@ -197,10 +194,12 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(ccQtHelpers::GetMaxThreadCount(omp_get_max_threads())) +#pragma omp parallel for num_threads(omp_get_max_threads()) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) + { + if (!cancelled) { const CCVector3* P = corePoints.cloud->getPoint(i); CCCoreLib::ReferenceCloud Yk(&classCloud); @@ -262,10 +261,10 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, { //process cancelled by the user errorMessage = "Process cancelled"; - break; } } } + } if (progressCb) { diff --git a/PointFeature.cpp b/PointFeature.cpp index 0017b69..a7d0312 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -89,9 +89,6 @@ constexpr const char* LAS_FIELD_NAMES[22] = {"X", //CCLib #include -//CCPluginAPI -#include - //system #include @@ -427,10 +424,13 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, error.clear(); #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(ccQtHelpers::GetMaxThreadCount(omp_get_max_threads())) + bool cancelled = false; +#pragma omp parallel for num_threads(omp_get_max_threads()) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) + { + if (!cancelled) { const CCVector3* P = corePoints.cloud->getPoint(i); CCCoreLib::ReferenceCloud Yk(&cloud2); @@ -474,16 +474,16 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, if (progressCb) { mutex.lock(); - bool cancelled = !nProgress.oneStep(); + cancelled = !nProgress.oneStep(); mutex.unlock(); if (cancelled) { //process cancelled by the user error = "Process cancelled"; - break; } } } + } outSF->computeMinAndMax(); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index ea3356e..2b141b7 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -32,7 +32,6 @@ //CCPluginAPI #include -#include //Qt #include @@ -210,10 +209,12 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, int numberOfTrees = static_cast(m_rtrees->getRoots().size()); #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(ccQtHelpers::GetMaxThreadCount(omp_get_max_threads())) + bool cancelled = false; +#pragma omp parallel for num_threads(omp_get_max_threads()) #endif #endif for (int i = 0; i < static_cast(cloud->size()); ++i) + { { //allocate the data matrix cv::Mat test_data; @@ -225,9 +226,11 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, { errorMessage = cvex.msg.c_str(); success = false; - break; + cancelled = true; } + if (!cancelled) + { for (int fIndex = 0; fIndex < attributesPerSample; ++fIndex) { double value = wrappers[fIndex]->pointValue(i); @@ -258,8 +261,10 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, { //process cancelled by the user success = false; - break; + cancelled = true; } + } + } } classificationSF->computeMinAndMax(); diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 32833dd..181625c 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -30,9 +30,6 @@ #include #include -//CCPluginAPI -#include - //qPDALIO #include "../../../core/IO/qPDALIO/include/LASFields.h" @@ -1226,25 +1223,28 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features unsigned char octreeLevel = octree->findBestLevelForAGivenNeighbourhoodSizeExtraction(largestRadius); unsigned pointCount = corePoints.size(); - QString logMessage = QString("Computing %1 features on cloud %2 (%3 core points)").arg(fas.featureCount).arg(sourceCloud->getName()).arg(pointCount); + QString logMessage = QString("Computing %1 features on cloud %2 at %3 core points").arg(fas.featureCount).arg(sourceCloud->getName()).arg(pointCount); if (progressCb) { progressCb->setMethodTitle("Compute features"); progressCb->setInfo(qPrintable(logMessage)); } - ccLog::Print(logMessage + ", nb points " + QString::number(pointCount)); + ccLog::Print(logMessage); CCCoreLib::NormalizedProgress nProgress(progressCb, pointCount); QMutex mutex; #ifndef _DEBUG #if defined(_OPENMP) - int num_threads = ccQtHelpers::GetMaxThreadCount(omp_get_max_threads()); + int num_threads = omp_get_max_threads(); ccLog::Print("Using OpenMP with " + QString::number(num_threads) + " threads "); + bool cancelled = false; #pragma omp parallel for num_threads(num_threads) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) + { + if (!cancelled) { //spherical neighborhood extraction structure CCCoreLib::DgmOctree::NearestNeighboursSearchStruct nNSS; @@ -1384,7 +1384,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (progressCb) { mutex.lock(); - bool cancelled = !nProgress.oneStep(); + cancelled = !nProgress.oneStep(); mutex.unlock(); if (cancelled) { @@ -1392,9 +1392,9 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features ccLog::Warning("Process cancelled"); errorStr = "Process cancelled, iteration " + QString::number(i); success = false; - break; } } + } } //for each point } //for each cloud From c3e1baf6fcf60aed10a39bc1289408fd12db3e09 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Tue, 13 Feb 2024 16:44:10 +0100 Subject: [PATCH 06/14] instability debuggin in progress --- ContextBasedFeature.cpp | 2 +- PointFeature.cpp | 4 ++-- q3DMASC.cpp | 4 ++-- q3DMASCClassifier.cpp | 2 +- q3DMASCTools.cpp | 30 ++++++++++++++++++++---------- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index acb6950..3486957 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -260,7 +260,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, if (cancelled) { //process cancelled by the user - errorMessage = "Process cancelled"; + errorMessage = "[ContextBasedFeature] Process cancelled"; } } } diff --git a/PointFeature.cpp b/PointFeature.cpp index a7d0312..f9c1e39 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -425,7 +425,7 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, #ifndef _DEBUG #if defined(_OPENMP) bool cancelled = false; -#pragma omp parallel for num_threads(omp_get_max_threads()) +#pragma omp parallel for #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) @@ -479,7 +479,7 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, if (cancelled) { //process cancelled by the user - error = "Process cancelled"; + error = "[Point feature] Process cancelled"; } } } diff --git a/q3DMASC.cpp b/q3DMASC.cpp index c551ff4..254e27b 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -618,11 +618,11 @@ void q3DMASCPlugin::doTrainAction() //prepare the features and the test cloud if (!toPrepareTest.empty()) { - progressDlg.show(); - QString error; masc::CorePoints corePointsTest; corePointsTest.cloud = corePointsTest.origin = testCloud; corePointsTest.role = mainCloudLabel; + progressDlg.show(); + QString error; if (!masc::Tools::PrepareFeatures(corePointsTest, toPrepareTest, error, &progressDlg, &generatedScalarFieldsTest)) { m_app->dispToConsole(error, ccMainAppInterface::ERR_CONSOLE_MESSAGE); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 2b141b7..91c9eab 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -210,7 +210,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, #ifndef _DEBUG #if defined(_OPENMP) bool cancelled = false; -#pragma omp parallel for num_threads(omp_get_max_threads()) +#pragma omp parallel for #endif #endif for (int i = 0; i < static_cast(cloud->size()); ++i) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 181625c..cfa22a5 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1236,16 +1236,16 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features #ifndef _DEBUG #if defined(_OPENMP) - int num_threads = omp_get_max_threads(); - ccLog::Print("Using OpenMP with " + QString::number(num_threads) + " threads "); bool cancelled = false; -#pragma omp parallel for num_threads(num_threads) +#pragma omp parallel for #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) { if (!cancelled) { + QString localErrorStr; + //spherical neighborhood extraction structure CCCoreLib::DgmOctree::NearestNeighboursSearchStruct nNSS; { @@ -1329,7 +1329,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (!feature->computeValue(nNSS.pointsInNeighbourhood, nNSS.queryPoint, outputValue)) { //an error occurred - errorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); + localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); success = false; break; } @@ -1345,7 +1345,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (!feature->computeValue(nNSS.pointsInNeighbourhood, nNSS.queryPoint, outputValue)) { //an error occurred - errorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud2->getName(); + localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud2->getName(); success = false; break; } @@ -1364,7 +1364,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (!feature->computeValue(nNSS.pointsInNeighbourhood, nNSS.queryPoint, outputValue)) { //an error occurred - errorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); + localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); success = false; break; } @@ -1375,24 +1375,34 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (!success) { - break; + ccLog::Warning(localErrorStr); + ccLog::Warning("(!success) with currentScale " + QString::number(currentScale) + " point " + QString::number(i)); } } //for each scale } + if (!success) + { + mutex.lock(); + cancelled = true; + errorStr = "Process cancelled (features computation not successful), point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; + ccLog::Warning(errorStr); + mutex.unlock(); + } + if (progressCb) { mutex.lock(); cancelled = !nProgress.oneStep(); - mutex.unlock(); if (cancelled) { //process cancelled by the user - ccLog::Warning("Process cancelled"); - errorStr = "Process cancelled, iteration " + QString::number(i); + errorStr = "Process cancelled, point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; + ccLog::Warning(errorStr); success = false; } + mutex.unlock(); } } } //for each point From 677e63d7534aef22090ca7080d5f91bdda8554fa Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Thu, 15 Feb 2024 07:28:28 +0100 Subject: [PATCH 07/14] warning messages modified --- ContextBasedFeature.cpp | 4 +-- q3DMASC.cpp | 9 +++++-- q3DMASCTools.cpp | 58 +++++++++++++++++++++++------------------ 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index 3486957..b34a8cf 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -127,7 +127,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, sf = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields, SFCollector::CAN_REMOVE); if (!sf) { - errorMessage = QString("Failed to prepare scalar %1 @ scale %2").arg(resultSFName).arg(scale); + errorMessage = QString("[ContextBasedFeature::prepare] Failed to prepare scalar %1 @ scale %2").arg(resultSFName).arg(scale); return false; } source.name = sf->getName(); @@ -172,7 +172,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, ccOctree::Shared classOctree = classCloud.computeOctree(progressCb); if (!classOctree) { - errorMessage = "Failed to compute octree (not enough memory?)"; + errorMessage = "[ContextBasedFeature::prepare] Failed to compute octree (not enough memory?)"; return false; } diff --git a/q3DMASC.cpp b/q3DMASC.cpp index 254e27b..9a909de 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -391,6 +391,7 @@ void q3DMASCPlugin::doTrainAction() //display the loaded features and let the user select the ones to use trainDlg.setResultText("Select features and press 'Run'"); + std::vector originalFeatures; originalFeatures.reserve(features.size()); for (const masc::Feature::Shared& f : features) @@ -398,6 +399,7 @@ void q3DMASCPlugin::doTrainAction() originalFeatures.push_back(FeatureSelection(f)); trainDlg.addFeature(f->toString(), originalFeatures.back().importance, originalFeatures.back().selected); } + for(double scale : scales) trainDlg.addScale(scale, true); trainDlg.connectScaleSelectionToFeatureSelection(); @@ -470,7 +472,8 @@ void q3DMASCPlugin::doTrainAction() //train / test subsets QSharedPointer trainSubset, testSubset; float previousTestSubsetRatio = -1.0f; - SFCollector generatedScalarFields, generatedScalarFieldsTest; + SFCollector generatedScalarFields; + SFCollector generatedScalarFieldsTest; //we will train + evaluate the classifier, then display the results //then let the user change parameters and (potentially) start again @@ -619,9 +622,11 @@ void q3DMASCPlugin::doTrainAction() if (!toPrepareTest.empty()) { masc::CorePoints corePointsTest; - corePointsTest.cloud = corePointsTest.origin = testCloud; + corePointsTest.cloud = testCloud; + corePointsTest.origin = testCloud; corePointsTest.role = mainCloudLabel; progressDlg.show(); + QCoreApplication::processEvents(); QString error; if (!masc::Tools::PrepareFeatures(corePointsTest, toPrepareTest, error, &progressDlg, &generatedScalarFieldsTest)) { diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index cfa22a5..2ad1c0c 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1212,7 +1212,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features octree = sourceCloud->computeOctree(progressCb); if (!octree) { - errorStr = "Failed to compute octree (not enough memory?)"; + errorStr = "[Tools::PrepareFeatures] Failed to compute octree (not enough memory?)"; return false; } } @@ -1245,6 +1245,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (!cancelled) { QString localErrorStr; + bool localSuccess = true; //spherical neighborhood extraction structure CCCoreLib::DgmOctree::NearestNeighboursSearchStruct nNSS; @@ -1290,13 +1291,14 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features //Point features for (PointFeature::Shared& feature : fas.pointFeaturesPerScale[currentScale]) { - if (feature->cloud1 == sourceCloud && feature->statSF1 && feature->field1) + if (feature->cloud1 == sourceCloud && feature->statSF1 && feature->field1 && localSuccess) { double outputValue = 0; if (!feature->computeStat(nNSS.pointsInNeighbourhood, feature->field1, outputValue)) { //an error occurred - success = false; + localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); + localSuccess = false; break; } @@ -1304,14 +1306,15 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features feature->statSF1->setValue(i, v1); } - if (feature->cloud2 == sourceCloud && feature->statSF2 && feature->field2) + if (feature->cloud2 == sourceCloud && feature->statSF2 && feature->field2 && localSuccess) { assert(feature->op != Feature::NO_OPERATION); double outputValue = 0; if (!feature->computeStat(nNSS.pointsInNeighbourhood, feature->field2, outputValue)) { //an error occurred - success = false; + localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud2->getName(); + localSuccess = false; break; } @@ -1323,14 +1326,14 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features //Neighborhood features for (NeighborhoodFeature::Shared& feature : fas.neighborhoodFeaturesPerScale[currentScale]) { - if (feature->cloud1 == sourceCloud && feature->sf1) + if (feature->cloud1 == sourceCloud && feature->sf1 && localSuccess) { double outputValue = 0; if (!feature->computeValue(nNSS.pointsInNeighbourhood, nNSS.queryPoint, outputValue)) { //an error occurred localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); - success = false; + localSuccess = false; break; } @@ -1338,7 +1341,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features feature->sf1->setValue(i, v1); } - if (feature->cloud2 == sourceCloud && feature->sf2) + if (feature->cloud2 == sourceCloud && feature->sf2 && localSuccess) { assert(feature->op != Feature::NO_OPERATION); double outputValue = 0; @@ -1346,7 +1349,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { //an error occurred localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud2->getName(); - success = false; + localSuccess = false; break; } @@ -1358,14 +1361,14 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features //Context-based features for (ContextBasedFeature::Shared& feature : fas.contextBasedFeaturesPerScale[currentScale]) { - if (feature->cloud1 == sourceCloud && feature->sf) + if (feature->cloud1 == sourceCloud && feature->sf && localSuccess) { ScalarType outputValue = 0; if (!feature->computeValue(nNSS.pointsInNeighbourhood, nNSS.queryPoint, outputValue)) { //an error occurred localErrorStr = "An error occurred during the computation of feature " + feature->toString() + "on cloud " + feature->cloud1->getName(); - success = false; + localSuccess = false; break; } @@ -1373,37 +1376,42 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features } } - if (!success) + if (!localSuccess) { - ccLog::Warning(localErrorStr); - ccLog::Warning("(!success) with currentScale " + QString::number(currentScale) + " point " + QString::number(i)); + localErrorStr = localErrorStr + " at scale " + QString::number(currentScale) + " on point " + QString::number(i); + break; } } //for each scale } - if (!success) + if (!localSuccess) { mutex.lock(); cancelled = true; - errorStr = "Process cancelled (features computation not successful), point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; - ccLog::Warning(errorStr); + success = false; + errorStr = "Feature computation failed for point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; + ccLog::Error(localErrorStr); mutex.unlock(); } + mutex.lock(); if (progressCb) { - mutex.lock(); - cancelled = !nProgress.oneStep(); - if (cancelled) + if (!cancelled) { - //process cancelled by the user - errorStr = "Process cancelled, point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; - ccLog::Warning(errorStr); - success = false; + cancelled = !nProgress.oneStep(); + if (cancelled) + { + //process cancelled by the user + errorStr = "Process cancelled at point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; + ccLog::Warning(errorStr); + ccLog::Error(localErrorStr); + success = false; + } } - mutex.unlock(); } + mutex.unlock(); } } //for each point From b542d0e1ffa51db97cf373276bf7c085bde586f2 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Fri, 16 Feb 2024 08:56:15 +0100 Subject: [PATCH 08/14] the use of progressDlg.close instead of progressDlg.hide led to an instability code cleaning --- ContextBasedFeature.cpp | 4 ++-- ContextBasedFeature.h | 4 ++-- FeaturesInterface.cpp | 4 ++-- FeaturesInterface.h | 5 +++++ NeighborhoodFeature.cpp | 1 - NeighborhoodFeature.h | 8 ++------ PointFeature.cpp | 14 +++++++------- PointFeature.h | 8 ++------ ScalarFieldCollector.cpp | 6 +++--- q3DMASC.cpp | 7 ++++--- q3DMASCClassifier.cpp | 3 ++- q3DMASCTools.cpp | 17 ++++++++--------- 12 files changed, 39 insertions(+), 42 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index b34a8cf..b8b4625 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -123,7 +123,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, //and the scalar field assert(!sf); - sfWasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSFName)); + sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSFName)); sf = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields, SFCollector::CAN_REMOVE); if (!sf) { @@ -133,7 +133,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, source.name = sf->getName(); // NOT NECESSARY IF THE VALUE IS ALREADY COMPUTED - if (!scaled() && !sfWasAlreadyExisting) //with 'kNN' neighbors, we can compute the values right away + if (!scaled() && !sf1WasAlreadyExisting) //with 'kNN' neighbors, we can compute the values right away { unsigned pointCount = corePoints.size(); QString logMessage = "Computing " + typeStr diff --git a/ContextBasedFeature.h b/ContextBasedFeature.h index e6575fa..622dfad 100644 --- a/ContextBasedFeature.h +++ b/ContextBasedFeature.h @@ -75,7 +75,7 @@ namespace masc , kNN(p_kNN) , ctxClassLabel(p_ctxClassLabel) , sf(nullptr) - , sfWasAlreadyExisting(false) + // , sfWasAlreadyExisting(false) { scale = p_scale; } @@ -105,6 +105,6 @@ namespace masc //! The computed scalar CCCoreLib::ScalarField* sf; //! Whether the SF pre-exists - bool sfWasAlreadyExisting; + // bool sfWasAlreadyExisting; }; } diff --git a/FeaturesInterface.cpp b/FeaturesInterface.cpp index d718ab2..cf78fc3 100644 --- a/FeaturesInterface.cpp +++ b/FeaturesInterface.cpp @@ -50,12 +50,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())) { diff --git a/FeaturesInterface.h b/FeaturesInterface.h index bce022f..6142f6f 100644 --- a/FeaturesInterface.h +++ b/FeaturesInterface.h @@ -162,6 +162,8 @@ namespace masc , source(p_source, p_sourceName) , stat(NO_STAT) , op(NO_OPERATION) + , sf1WasAlreadyExisting(false) + , sf2WasAlreadyExisting(false) {} //! Returns the type (must be reimplemented by child struct) @@ -236,5 +238,8 @@ namespace masc Stat stat; //only considered if a scale is defined Operation op; //only considered if 2 clouds are defined + + bool sf1WasAlreadyExisting; + bool sf2WasAlreadyExisting; // only considered if a second scalar field may be necessary (PointFeature, NeighborhoodFeature) }; } diff --git a/NeighborhoodFeature.cpp b/NeighborhoodFeature.cpp index 33321e9..ede9df4 100644 --- a/NeighborhoodFeature.cpp +++ b/NeighborhoodFeature.cpp @@ -111,7 +111,6 @@ bool NeighborhoodFeature::prepare( const CorePoints& corePoints, if (cloud2 && op != Feature::NO_OPERATION && !sf1WasAlreadyExisting) { QString resultSFName2 = ToString(type) + "_" + cloud2Label + "@" + QString::number(scale); - keepSF2 = (corePoints.cloud->getScalarFieldIndexByName(qPrintable(resultSFName2)) >= 0); //we remember that the scalar field was already existing! assert(!sf2); diff --git a/NeighborhoodFeature.h b/NeighborhoodFeature.h index e088de2..8518f65 100644 --- a/NeighborhoodFeature.h +++ b/NeighborhoodFeature.h @@ -151,9 +151,8 @@ namespace masc : type(p_type) , sf1(nullptr) , sf2(nullptr) - , keepSF2(false) - , sf1WasAlreadyExisting(false) - , sf2WasAlreadyExisting(false) + // , sf1WasAlreadyExisting(false) + // , sf2WasAlreadyExisting(false) { } @@ -177,8 +176,5 @@ namespace masc //! Feature values CCCoreLib::ScalarField *sf1, *sf2; - bool keepSF2; - bool sf1WasAlreadyExisting; - bool sf2WasAlreadyExisting; }; } diff --git a/PointFeature.cpp b/PointFeature.cpp index f9c1e39..7851a02 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -422,9 +422,9 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, double meanNeighborhoodSize = 0; int tenth = pointCount / 10; error.clear(); + bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) - bool cancelled = false; #pragma omp parallel for #endif #endif @@ -584,8 +584,8 @@ bool PointFeature::prepare( const CorePoints& corePoints, resultSF1Name += "@" + QString::number(scale); //prepare the corresponding scalar field - statSF1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSF1Name)); - if (statSF1WasAlreadyExisting) + sf1WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSF1Name)); + if (sf1WasAlreadyExisting) { // if the SF exists, it is not added to generatedScalarFields statSF1 = PrepareSF(corePoints.cloud, qPrintable(resultSF1Name), generatedScalarFields, SFCollector::ALWAYS_KEEP); @@ -601,14 +601,14 @@ bool PointFeature::prepare( const CorePoints& corePoints, } source.name = statSF1->getName(); - if (field2 && op != Feature::NO_OPERATION && !statSF1WasAlreadyExisting) // nothing to do if statSF1 was already there + 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! assert(!statSF2); - statSF2WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSF2Name)); - if (statSF2WasAlreadyExisting) + sf2WasAlreadyExisting = CheckSFExistence(corePoints.cloud, qPrintable(resultSF2Name)); + if (sf2WasAlreadyExisting) statSF2 = PrepareSF(corePoints.cloud, qPrintable(resultSF2Name), generatedScalarFields, SFCollector::ALWAYS_KEEP); else statSF2 = PrepareSF(corePoints.cloud, qPrintable(resultSF2Name), generatedScalarFields, SFCollector::ALWAYS_REMOVE); @@ -863,7 +863,7 @@ bool PointFeature::finish(const CorePoints& corePoints, QString& error) } } - if (statSF2 && !statSF1WasAlreadyExisting) + if (statSF2 && !sf1WasAlreadyExisting) { //now perform the math operation if (op != Feature::NO_OPERATION) diff --git a/PointFeature.h b/PointFeature.h index da75c7f..f8cc20b 100644 --- a/PointFeature.h +++ b/PointFeature.h @@ -148,8 +148,8 @@ namespace masc , field2(nullptr) , statSF1(nullptr) , statSF2(nullptr) - , statSF1WasAlreadyExisting(false) - , statSF2WasAlreadyExisting(false) + // , statSF1WasAlreadyExisting(false) + // , statSF2WasAlreadyExisting(false) //, keepStatSF2(false) { //auomatically set the right source for specific features @@ -213,9 +213,5 @@ namespace masc //! For scaled features CCCoreLib::ScalarField *statSF1, *statSF2; - - //bool keepStatSF2; - bool statSF1WasAlreadyExisting; - bool statSF2WasAlreadyExisting; }; } diff --git a/ScalarFieldCollector.cpp b/ScalarFieldCollector.cpp index 5b4b765..b3816f5 100644 --- a/ScalarFieldCollector.cpp +++ b/ScalarFieldCollector.cpp @@ -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' on cloud '%2'").arg(sf->getName()).arg(desc.cloud->getName())); //keep this SF continue; } @@ -56,12 +56,12 @@ 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' from '%2'").arg(sf->getName()).arg(desc.cloud->getName())); desc.cloud->deleteScalarField(sfIdx); } else { - ccLog::Warning(QString("[SFCollector] Scalar field '%1' can't be found anymore, impossible to remove it").arg(sf->getName())); + // ccLog::Warning(QString("[SFCollector] Scalar field '%1' can't be found anymore on cloud '%2', impossible to remove it").arg(sf->getName()).arg(desc.cloud->getName())); } } diff --git a/q3DMASC.cpp b/q3DMASC.cpp index 9a909de..bf91893 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -189,7 +189,7 @@ void q3DMASCPlugin::doClassifyAction() generatedScalarFields.releaseSFs(false); return; } - progressDlg.close(); + progressDlg.hide(); QCoreApplication::processEvents(); //apply classifier @@ -425,6 +425,7 @@ void q3DMASCPlugin::doTrainAction() //compute the core points (if necessary) ccProgressDialog progressDlg(true, m_app->getMainWindow()); progressDlg.setAutoClose(false); + ccLog::Error("Qt::WA_DeleteOnClose " + QString::number(progressDlg.testAttribute(Qt::WA_DeleteOnClose))); if (!corePoints.prepare(&progressDlg)) { m_app->dispToConsole("Failed to compute/prepare the core points!", ccMainAppInterface::ERR_CONSOLE_MESSAGE); @@ -517,7 +518,7 @@ void q3DMASCPlugin::doTrainAction() generatedScalarFieldsTest.releaseSFs(false); return; } - progressDlg.close(); + progressDlg.hide(); QCoreApplication::processEvents(); m_app->redrawAll(); @@ -635,7 +636,7 @@ void q3DMASCPlugin::doTrainAction() generatedScalarFieldsTest.releaseSFs(false); return; } - progressDlg.close(); + progressDlg.hide(); QCoreApplication::processEvents(); m_app->redrawAll(); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 91c9eab..3fe0041 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -207,9 +207,10 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, bool success = true; int numberOfTrees = static_cast(m_rtrees->getRoots().size()); + bool cancelled = false; + #ifndef _DEBUG #if defined(_OPENMP) - bool cancelled = false; #pragma omp parallel for #endif #endif diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 2ad1c0c..e38f1fa 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -43,7 +43,6 @@ //system #include -#include #if defined(_OPENMP) #include @@ -1085,7 +1084,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { //build the scaled feature list attached to the first cloud if (feature->cloud1 - && !static_cast(feature.data())->statSF1WasAlreadyExisting) // nothing to compute if the scalar field was already there + && !feature->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there { FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud1]; fas.pointFeaturesPerScale[feature->scale].push_back(qSharedPointerCast(feature)); @@ -1100,9 +1099,9 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features && feature->cloud2 != feature->cloud1 && feature->op != Feature::NO_OPERATION) { - if(!static_cast(feature.data())->statSF1WasAlreadyExisting) // nothing to compute if the scalar field was already there + if(!feature->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there { - if (!static_cast(feature.data())->statSF2WasAlreadyExisting) + if (!feature->sf2WasAlreadyExisting) { FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud2]; ++fas.featureCount; @@ -1122,7 +1121,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { //build the scaled feature list attached to the first cloud if (feature->cloud1 - && !static_cast(feature.data())->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there + && !feature->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there { FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud1]; fas.neighborhoodFeaturesPerScale[feature->scale].push_back(qSharedPointerCast(feature)); @@ -1138,9 +1137,9 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features && feature->cloud2 != feature->cloud1 && feature->op != Feature::NO_OPERATION) { - if (!static_cast(feature.data())->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there + if (!feature->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there { - if (!static_cast(feature.data())->sf2WasAlreadyExisting) + if (!feature->sf2WasAlreadyExisting) { FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud2]; fas.neighborhoodFeaturesPerScale[feature->scale].push_back(qSharedPointerCast(feature)); @@ -1160,7 +1159,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { //build the scaled feature list attached to the context cloud if (feature->cloud1 - && !static_cast(feature.data())->sfWasAlreadyExisting) // nothing to compute if the scalar field was already there + && !feature->sf1WasAlreadyExisting) // nothing to compute if the scalar field was already there { FeaturesAndScales& fas = cloudsWithScaledFeatures[feature->cloud1]; fas.contextBasedFeaturesPerScale[feature->scale].push_back(qSharedPointerCast(feature)); @@ -1233,10 +1232,10 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features CCCoreLib::NormalizedProgress nProgress(progressCb, pointCount); QMutex mutex; + bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) - bool cancelled = false; #pragma omp parallel for #endif #endif From 4b4227bf6a38c218aa0ed4bf2dc088d07c45387e Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Fri, 16 Feb 2024 09:53:04 +0100 Subject: [PATCH 09/14] version 1.0 --- CMakeLists.txt | 2 +- confusionmatrix.cpp | 7 +------ confusionmatrix.h | 6 ++---- q3DMASC.cpp | 1 - q3DMASCClassifier.cpp | 7 ++++++- q3DMASCTools.cpp | 6 ------ 6 files changed, 10 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e718b0..de33d8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ if (PLUGIN_STANDARD_3DMASC) project( Q3DMASC_PLUGIN ) AddPlugin( NAME ${PROJECT_NAME} ) - set(Q3DMASC_PLUGIN_VERSION "0.12+") + set(Q3DMASC_PLUGIN_VERSION "1.0") include( CMakePolicies NO_POLICY_SCOPE ) diff --git a/confusionmatrix.cpp b/confusionmatrix.cpp index cca7ac6..2f1b2f9 100644 --- a/confusionmatrix.cpp +++ b/confusionmatrix.cpp @@ -32,8 +32,7 @@ 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, QWidget *parent, ccMainAppInterface *app) : - QWidget(parent), +ConfusionMatrix::ConfusionMatrix(const std::vector &actual, const std::vector &predicted) : ui(new Ui::ConfusionMatrix) { ui->setupUi(this); @@ -41,10 +40,6 @@ ConfusionMatrix::ConfusionMatrix(const std::vector &actual, const st compute(actual, predicted); - if (app) - { - this->show(); - } this->ui->tableWidget->resizeColumnsToContents(); this->ui->tableWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); QSize tableSize = this->ui->tableWidget->sizeHint(); diff --git a/confusionmatrix.h b/confusionmatrix.h index 1675bea..bd4f363 100644 --- a/confusionmatrix.h +++ b/confusionmatrix.h @@ -25,10 +25,8 @@ public: F1_SCORE = 2 }; - explicit ConfusionMatrix( const std::vector& actual, - const std::vector& predicted, - QWidget *parent = nullptr, - ccMainAppInterface* app = nullptr); + explicit ConfusionMatrix(const std::vector& actual, + const std::vector& predicted); ~ConfusionMatrix() override; void computePrecisionRecallF1Score(cv::Mat& matrix, cv::Mat& precisionRecallF1Score, cv::Mat &vec_TP_FN); diff --git a/q3DMASC.cpp b/q3DMASC.cpp index bf91893..fb6252c 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -425,7 +425,6 @@ void q3DMASCPlugin::doTrainAction() //compute the core points (if necessary) ccProgressDialog progressDlg(true, m_app->getMainWindow()); progressDlg.setAutoClose(false); - ccLog::Error("Qt::WA_DeleteOnClose " + QString::number(progressDlg.testAttribute(Qt::WA_DeleteOnClose))); if (!corePoints.prepare(&progressDlg)) { m_app->dispToConsole("Failed to compute/prepare the core points!", ccMainAppInterface::ERR_CONSOLE_MESSAGE); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 3fe0041..092da90 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -481,7 +481,12 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources, metrics.ratio = static_cast(metrics.goodGuess) / metrics.sampleCount; } - train3DMASCDialog.addConfusionMatrixAndSaveTraces(new ConfusionMatrix(actualClass, predictectedClass, nullptr, app)); + ConfusionMatrix* confusionMatrix = new ConfusionMatrix(actualClass, predictectedClass); + train3DMASCDialog.addConfusionMatrixAndSaveTraces(confusionMatrix); + if (app) + { + confusionMatrix->show(); + } //show the Classification_prediction field by default if (outSF) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index e38f1fa..976bae1 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1231,7 +1231,6 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features ccLog::Print(logMessage); CCCoreLib::NormalizedProgress nProgress(progressCb, pointCount); - QMutex mutex; bool cancelled = false; #ifndef _DEBUG @@ -1386,15 +1385,12 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (!localSuccess) { - mutex.lock(); cancelled = true; success = false; errorStr = "Feature computation failed for point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; ccLog::Error(localErrorStr); - mutex.unlock(); } - mutex.lock(); if (progressCb) { if (!cancelled) @@ -1405,12 +1401,10 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features //process cancelled by the user errorStr = "Process cancelled at point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; ccLog::Warning(errorStr); - ccLog::Error(localErrorStr); success = false; } } } - mutex.unlock(); } } //for each point From 992f5d4c8c986cd9916ab43d6529d56b9500ef9d Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Fri, 16 Feb 2024 11:34:52 +0100 Subject: [PATCH 10/14] changes following PR #5 --- ContextBasedFeature.cpp | 2 +- ContextBasedFeature.h | 3 --- NeighborhoodFeature.h | 2 -- PointFeature.cpp | 5 +---- PointFeature.h | 3 --- confusionmatrix.cpp | 4 ++-- q3DMASCClassifier.cpp | 2 +- q3DMASCTools.cpp | 2 +- 8 files changed, 6 insertions(+), 17 deletions(-) diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index b8b4625..6cd2762 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -194,7 +194,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for num_threads(omp_get_max_threads()) +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) diff --git a/ContextBasedFeature.h b/ContextBasedFeature.h index 622dfad..2b46f8e 100644 --- a/ContextBasedFeature.h +++ b/ContextBasedFeature.h @@ -75,7 +75,6 @@ namespace masc , kNN(p_kNN) , ctxClassLabel(p_ctxClassLabel) , sf(nullptr) - // , sfWasAlreadyExisting(false) { scale = p_scale; } @@ -104,7 +103,5 @@ namespace masc int ctxClassLabel; //! The computed scalar CCCoreLib::ScalarField* sf; - //! Whether the SF pre-exists - // bool sfWasAlreadyExisting; }; } diff --git a/NeighborhoodFeature.h b/NeighborhoodFeature.h index 8518f65..cf3b517 100644 --- a/NeighborhoodFeature.h +++ b/NeighborhoodFeature.h @@ -151,8 +151,6 @@ namespace masc : type(p_type) , sf1(nullptr) , sf2(nullptr) - // , sf1WasAlreadyExisting(false) - // , sf2WasAlreadyExisting(false) { } diff --git a/PointFeature.cpp b/PointFeature.cpp index 7851a02..248d4d8 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -418,14 +418,13 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, ccLog::Print(logMessage); CCCoreLib::NormalizedProgress nProgress(progressCb, pointCount); - QMutex mutex; double meanNeighborhoodSize = 0; int tenth = pointCount / 10; error.clear(); bool cancelled = false; #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) @@ -473,9 +472,7 @@ static bool ComputeMathOpWithNearestNeighbor( const CorePoints& corePoints, if (progressCb) { - mutex.lock(); cancelled = !nProgress.oneStep(); - mutex.unlock(); if (cancelled) { //process cancelled by the user diff --git a/PointFeature.h b/PointFeature.h index f8cc20b..84fba0e 100644 --- a/PointFeature.h +++ b/PointFeature.h @@ -148,9 +148,6 @@ namespace masc , field2(nullptr) , statSF1(nullptr) , statSF2(nullptr) - // , statSF1WasAlreadyExisting(false) - // , statSF2WasAlreadyExisting(false) - //, keepStatSF2(false) { //auomatically set the right source for specific features switch (type) diff --git a/confusionmatrix.cpp b/confusionmatrix.cpp index 2f1b2f9..a6a3527 100644 --- a/confusionmatrix.cpp +++ b/confusionmatrix.cpp @@ -32,8 +32,8 @@ 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) : - ui(new Ui::ConfusionMatrix) +ConfusionMatrix::ConfusionMatrix(const std::vector &actual, const std::vector &predicted) + : ui(new Ui::ConfusionMatrix) { ui->setupUi(this); this->setWindowFlag(Qt::WindowStaysOnTopHint); diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 092da90..f52ad79 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -211,7 +211,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for +#pragma omp parallel for num_threads(omp_get_max_threads() - 2) #endif #endif for (int i = 0; i < static_cast(cloud->size()); ++i) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 976bae1..7ceb73f 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1235,7 +1235,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features #ifndef _DEBUG #if defined(_OPENMP) -#pragma omp parallel for +#pragma omp parallel for num_threads(std::max(1, omp_get_max_threads() - 2)) #endif #endif for (int i = 0; i < static_cast(pointCount); ++i) From 7087b899f4afe171e3fae04b586344772aa2ddae Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Fri, 16 Feb 2024 17:14:54 +0100 Subject: [PATCH 11/14] Try to fill in the qClassify3DMASCDialog if names are available in the parameter file and clouds exist in the database tree with the same name (case insensitive) Code cleaning --- CMakeLists.txt | 2 +- Classify3DMASCDialog.ui | 8 +-- q3DMASC.cpp | 10 +-- q3DMASCCommands.h | 3 +- q3DMASCTools.cpp | 4 +- q3DMASCTools.h | 2 +- qClassify3DMASCDialog.cpp | 136 ++++++++++++++++++++++---------------- qClassify3DMASCDialog.h | 5 +- 8 files changed, 100 insertions(+), 70 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de33d8d..68f5992 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ if (PLUGIN_STANDARD_3DMASC) project( Q3DMASC_PLUGIN ) AddPlugin( NAME ${PROJECT_NAME} ) - set(Q3DMASC_PLUGIN_VERSION "1.0") + set(Q3DMASC_PLUGIN_VERSION "1.0+") include( CMakePolicies NO_POLICY_SCOPE ) diff --git a/Classify3DMASCDialog.ui b/Classify3DMASCDialog.ui index 7b147d2..e2c8b25 100644 --- a/Classify3DMASCDialog.ui +++ b/Classify3DMASCDialog.ui @@ -65,14 +65,14 @@ - PC2 + - - CTX + - @@ -112,14 +112,14 @@ - PC1 + - - PCX + - diff --git a/q3DMASC.cpp b/q3DMASC.cpp index fb6252c..65506bf 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -120,7 +120,8 @@ void q3DMASCPlugin::doClassifyAction() QList cloudLabels; QString corePointsLabel; bool filenamesSpecified = false; - if (!masc::Tools::LoadClassifierCloudLabels(inputFilename, cloudLabels, corePointsLabel, filenamesSpecified)) + QMap labelMapName; + if (!masc::Tools::LoadClassifierCloudLabels(inputFilename, cloudLabels, corePointsLabel, filenamesSpecified, labelMapName)) { m_app->dispToConsole("Failed to read classifier file (see Console)", ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; @@ -138,7 +139,7 @@ void q3DMASCPlugin::doClassifyAction() //now show a dialog where the user will be able to set the cloud roles Classify3DMASCDialog classifDlg(m_app); - classifDlg.setCloudRoles(cloudLabels, corePointsLabel); + classifDlg.setCloudRoles(cloudLabels, corePointsLabel, labelMapName); classifDlg.label_trainOrClassify->setText(corePointsLabel + " will be classified"); classifDlg.classifierFileLineEdit->setText(inputFilename); classifDlg.testCloudComboBox->hide(); @@ -260,7 +261,8 @@ void q3DMASCPlugin::doTrainAction() QList cloudLabels; QString corePointsLabel; bool filenamesSpecified = false; - if (!masc::Tools::LoadClassifierCloudLabels(inputFilename, cloudLabels, corePointsLabel, filenamesSpecified)) + QMap rolesAndNames; + if (!masc::Tools::LoadClassifierCloudLabels(inputFilename, cloudLabels, corePointsLabel, filenamesSpecified, rolesAndNames)) { m_app->dispToConsole("Failed to read classifier file (see Console)", ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; @@ -288,7 +290,7 @@ void q3DMASCPlugin::doTrainAction() //now show a dialog where the user will be able to set the cloud roles Classify3DMASCDialog classifDlg(m_app, true); classifDlg.setWindowTitle("3DMASC Train"); - classifDlg.setCloudRoles(cloudLabels, corePointsLabel); + classifDlg.setCloudRoles(cloudLabels, corePointsLabel, rolesAndNames); classifDlg.label_trainOrClassify->setText("The classifier will be trained on " + corePointsLabel); classifDlg.classifierFileLineEdit->setText(inputFilename); classifDlg.keepAttributesCheckBox->hide(); // this parameter is set in the trainDlg dialog diff --git a/q3DMASCCommands.h b/q3DMASCCommands.h index 223530b..51b6398 100644 --- a/q3DMASCCommands.h +++ b/q3DMASCCommands.h @@ -165,7 +165,8 @@ struct Command3DMASCClassif : public ccCommandLineInterface::Command QList cloudLabels; QString corePointsLabel; bool filenamesSpecified = false; - if (!masc::Tools::LoadClassifierCloudLabels(classifierFilename, cloudLabels, corePointsLabel, filenamesSpecified)) + QMap labelsAndNames; + if (!masc::Tools::LoadClassifierCloudLabels(classifierFilename, cloudLabels, corePointsLabel, filenamesSpecified, labelsAndNames)) { return cmd.error("Failed to read classifier file"); } diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 7ceb73f..8b1f7b4 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -112,11 +112,12 @@ bool Tools::SaveClassifier( QString filename, return true; } -bool Tools::LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified) +bool Tools::LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified, QMap& labelsAndNames) { //just in case corePointsLabel.clear(); labels.clear(); + labelsAndNames.clear(); QFile file(filename); if (!file.open(QFile::Text | QFile::ReadOnly)) @@ -155,6 +156,7 @@ bool Tools::LoadClassifierCloudLabels(QString filename, QList& labels, return false; } labels.push_back(label); + labelsAndNames[label] = tokens.back(); if (tokens.size() > 1) ++filenameCount; diff --git a/q3DMASCTools.h b/q3DMASCTools.h index bb3eb6c..a3f142a 100644 --- a/q3DMASCTools.h +++ b/q3DMASCTools.h @@ -40,7 +40,7 @@ namespace masc static bool LoadTrainingFile(QString filename, Feature::Set& rawFeatures, std::vector& scales, NamedClouds& loadedClouds, TrainParameters& parameters, CorePoints* corePoints = nullptr, QWidget* parent = nullptr); - static bool LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified); + static bool LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified, QMap& labelsAndNames); static bool LoadClassifier(QString filename, NamedClouds& clouds, Feature::Set& rawFeatures, masc::Classifier& classifier, QWidget* parent = nullptr); diff --git a/qClassify3DMASCDialog.cpp b/qClassify3DMASCDialog.cpp index d2c825d..d9a6ba9 100644 --- a/qClassify3DMASCDialog.cpp +++ b/qClassify3DMASCDialog.cpp @@ -28,10 +28,7 @@ #include #include #include -//#include -//system -#include static ccPointCloud* GetCloudFromCombo(QComboBox* comboBox, ccHObject* dbRoot) { @@ -91,6 +88,8 @@ Classify3DMASCDialog::Classify3DMASCDialog(ccMainAppInterface* app, bool trainMo } } + testCloudComboBox->addItem("", 0); + //if 3 clouds are loaded, then there's chances that the first one is the global cloud! cloud1ComboBox->setCurrentIndex(/*cloudCount > 0 ? (cloudCount > 2 ? 1 : 0) : */-1); connect(cloud1ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onCloudChanged(int))); @@ -140,70 +139,109 @@ void Classify3DMASCDialog::writeSettings() settings.setValue("keepAttributes", keepAttributesCheckBox->isChecked()); } -void Classify3DMASCDialog::setCloudRoles(const QList& roles, QString corePointsLabel) +void Classify3DMASCDialog::setComboBoxIndex(QMap rolesAndNames, QLabel* label, QMap namesAndUniqueIds, QComboBox* comboBox) +{ + QString name; + + if (label != testLabel) + { + name = rolesAndNames[label->text()]; + } + else + { + name = rolesAndNames[QString("TEST")]; // this is because the label text of testLabel is 'TEST on (optional)' + } + QMap::iterator it = namesAndUniqueIds.find(name); + if (it != namesAndUniqueIds.end()) + { + int index = comboBox->findData(it.value()); + if (index != -1) + { + comboBox->setCurrentIndex(index); + } + } +} + +void Classify3DMASCDialog::setCloudRoles(const QList& roles, QString& corePointsLabel, const QMap& rolesAndNames) { int index = 0; for (const QString& role : roles) { - switch (index) + if (role != "TEST") { - case 0: - cloud1Label->setText(role); - if (corePointsLabel.isEmpty()) // if "core_points:" is not in the parameter file, the corePointsLabel is the first encountered role - corePointsLabel = role; -// cloud1RadioButton->setChecked(true); - break; - case 1: - cloud2Label->setText(role); - if (corePointsLabel == role) -// cloud2RadioButton->setChecked(true); - break; - case 2: - cloud3Label->setText(role); - if (corePointsLabel == role) -// cloud3RadioButton->setChecked(true); - break; - case 3: - cloud4Label->setText(role); - if (corePointsLabel == role) -// cloud4RadioButton->setChecked(true); - break; - default: - //this dialog can't handle more than 3 roles! - break; + switch (index) + { + case 0: + cloud1Label->setText(role); + if (corePointsLabel.isEmpty()) // if "core_points:" is not in the parameter file, the corePointsLabel is the first encountered role + { + corePointsLabel = role; + } + break; + case 1: + cloud2Label->setText(role); + break; + case 2: + cloud3Label->setText(role); + break; + case 3: + cloud4Label->setText(role); + break; + default: + //this dialog can't handle more than 3 roles! + break; + } + ++index; } - ++index; } if (index < 1) { -// cloud1RadioButton->setEnabled(false); cloud1ComboBox->setEnabled(false); } if (index < 2) { -// cloud2RadioButton->setEnabled(false); -// cloud2RadioButton->setVisible(false); cloud2ComboBox->setEnabled(false); cloud2ComboBox->setVisible(false); cloud2Label->setVisible(false); } if (index < 3) { -// cloud3RadioButton->setEnabled(false); -// cloud3RadioButton->setVisible(false); cloud3ComboBox->setEnabled(false); cloud3ComboBox->setVisible(false); cloud3Label->setVisible(false); } if (index < 4) { -// cloud4RadioButton->setEnabled(false); -// cloud4RadioButton->setVisible(false); cloud4ComboBox->setEnabled(false); cloud4ComboBox->setVisible(false); cloud4Label->setVisible(false); } + + // now we will try to preset the combo boxes depending on the names which are in the parameter file + QMap namesAndUniqueIds; + + // build a map 'name : uniqueId' of the available clouds in the database tree (duplicate names are not handled, simply keep the first occurrence) + ccHObject::Container clouds; + if (m_app->dbRootObject()) + { + m_app->dbRootObject()->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD); + } + for (size_t i = 0; i < clouds.size(); ++i) + { + if (clouds[i]->isA(CC_TYPES::POINT_CLOUD)) //as filterChildren only test 'isKindOf' + { + QVariant uniqueID(clouds[i]->getUniqueID()); + namesAndUniqueIds[clouds[i]->getName().toUpper()] = uniqueID; + } + } + + // preset the combo boxes if possible + setComboBoxIndex(rolesAndNames, cloud1Label, namesAndUniqueIds, cloud1ComboBox); + setComboBoxIndex(rolesAndNames, cloud2Label, namesAndUniqueIds, cloud2ComboBox); + setComboBoxIndex(rolesAndNames, cloud3Label, namesAndUniqueIds, cloud3ComboBox); + setComboBoxIndex(rolesAndNames, cloud4Label, namesAndUniqueIds, cloud4ComboBox); + setComboBoxIndex(rolesAndNames, testLabel, namesAndUniqueIds, testCloudComboBox); } void Classify3DMASCDialog::getClouds(QMap& clouds) const @@ -214,42 +252,26 @@ void Classify3DMASCDialog::getClouds(QMap& clouds) const return; } -// if (cloud1RadioButton->isEnabled()) if (cloud1ComboBox->isEnabled()) { clouds.insert(cloud1Label->text(), GetCloudFromCombo(cloud1ComboBox, m_app->dbRootObject())); -// if (cloud1RadioButton->isChecked()) -// { -// mainCloud = cloud1Label->text(); -// } } -// if (cloud2RadioButton->isEnabled()) + if (cloud2ComboBox->isEnabled()) { clouds.insert(cloud2Label->text(), GetCloudFromCombo(cloud2ComboBox, m_app->dbRootObject())); -// if (cloud2RadioButton->isChecked()) -// { -// mainCloud = cloud2Label->text(); -// } } -// if (cloud3RadioButton->isEnabled()) + if (cloud3ComboBox->isEnabled()) { clouds.insert(cloud3Label->text(), GetCloudFromCombo(cloud3ComboBox, m_app->dbRootObject())); -// if (cloud3RadioButton->isChecked()) -// { -// mainCloud = cloud3Label->text(); -// } } -// if (cloud4RadioButton->isEnabled()) + if (cloud4ComboBox->isEnabled()) { clouds.insert(cloud4Label->text(), GetCloudFromCombo(cloud4ComboBox, m_app->dbRootObject())); -// if (cloud4RadioButton->isChecked()) -// { -// mainCloud = cloud4Label->text(); -// } } + if (testCloudComboBox->currentIndex() >= 0) { clouds.insert("TEST", GetCloudFromCombo(testCloudComboBox, m_app->dbRootObject())); diff --git a/qClassify3DMASCDialog.h b/qClassify3DMASCDialog.h index a9599ec..1d6de90 100644 --- a/qClassify3DMASCDialog.h +++ b/qClassify3DMASCDialog.h @@ -42,7 +42,10 @@ public: void writeSettings(); //! Sets the clouds roles - void setCloudRoles(const QList& roles, QString corePointsLabel); + void setCloudRoles(const QList& roles, QString &corePointsLabel, const QMap &rolesAndNames); + + //! Preset the combo boxes if possible with the names specified in the parameter file + void setComboBoxIndex(QMap rolesAndNames, QLabel* label, QMap namesAndUniqueIds, QComboBox* comboBox); //! Returns the selected point clouds void getClouds(QMap& clouds) const; From 5b37d4f1c06099730239d7d0c1621c9fe968ddc7 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Mon, 19 Feb 2024 10:57:05 +0100 Subject: [PATCH 12/14] Update q3DMASC.cpp --- q3DMASC.cpp | 6 +++--- q3DMASCTools.cpp | 6 +++--- q3DMASCTools.h | 2 +- qClassify3DMASCDialog.cpp | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/q3DMASC.cpp b/q3DMASC.cpp index 65506bf..6675140 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -120,8 +120,8 @@ void q3DMASCPlugin::doClassifyAction() QList cloudLabels; QString corePointsLabel; bool filenamesSpecified = false; - QMap labelMapName; - if (!masc::Tools::LoadClassifierCloudLabels(inputFilename, cloudLabels, corePointsLabel, filenamesSpecified, labelMapName)) + QMap rolesAndNames; + if (!masc::Tools::LoadClassifierCloudLabels(inputFilename, cloudLabels, corePointsLabel, filenamesSpecified, rolesAndNames)) { m_app->dispToConsole("Failed to read classifier file (see Console)", ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; @@ -139,7 +139,7 @@ void q3DMASCPlugin::doClassifyAction() //now show a dialog where the user will be able to set the cloud roles Classify3DMASCDialog classifDlg(m_app); - classifDlg.setCloudRoles(cloudLabels, corePointsLabel, labelMapName); + classifDlg.setCloudRoles(cloudLabels, corePointsLabel, rolesAndNames); classifDlg.label_trainOrClassify->setText(corePointsLabel + " will be classified"); classifDlg.classifierFileLineEdit->setText(inputFilename); classifDlg.testCloudComboBox->hide(); diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 8b1f7b4..b391127 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -112,12 +112,12 @@ bool Tools::SaveClassifier( QString filename, return true; } -bool Tools::LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified, QMap& labelsAndNames) +bool Tools::LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified, QMap& rolesAndNames) { //just in case corePointsLabel.clear(); labels.clear(); - labelsAndNames.clear(); + rolesAndNames.clear(); QFile file(filename); if (!file.open(QFile::Text | QFile::ReadOnly)) @@ -156,7 +156,7 @@ bool Tools::LoadClassifierCloudLabels(QString filename, QList& labels, return false; } labels.push_back(label); - labelsAndNames[label] = tokens.back(); + rolesAndNames[label] = tokens.back(); if (tokens.size() > 1) ++filenameCount; diff --git a/q3DMASCTools.h b/q3DMASCTools.h index a3f142a..f424fa5 100644 --- a/q3DMASCTools.h +++ b/q3DMASCTools.h @@ -40,7 +40,7 @@ namespace masc static bool LoadTrainingFile(QString filename, Feature::Set& rawFeatures, std::vector& scales, NamedClouds& loadedClouds, TrainParameters& parameters, CorePoints* corePoints = nullptr, QWidget* parent = nullptr); - static bool LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified, QMap& labelsAndNames); + static bool LoadClassifierCloudLabels(QString filename, QList& labels, QString& corePointsLabel, bool& filenamesSpecified, QMap& rolesAndNames); static bool LoadClassifier(QString filename, NamedClouds& clouds, Feature::Set& rawFeatures, masc::Classifier& classifier, QWidget* parent = nullptr); diff --git a/qClassify3DMASCDialog.cpp b/qClassify3DMASCDialog.cpp index d9a6ba9..1cc4c95 100644 --- a/qClassify3DMASCDialog.cpp +++ b/qClassify3DMASCDialog.cpp @@ -90,7 +90,7 @@ Classify3DMASCDialog::Classify3DMASCDialog(ccMainAppInterface* app, bool trainMo testCloudComboBox->addItem("", 0); - //if 3 clouds are loaded, then there's chances that the first one is the global cloud! + //if 3 clouds are loaded, then there's chances that the first one is the global cloud! cloud1ComboBox->setCurrentIndex(/*cloudCount > 0 ? (cloudCount > 2 ? 1 : 0) : */-1); connect(cloud1ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onCloudChanged(int))); cloud2ComboBox->setCurrentIndex(/*cloudCount > 1 ? (cloudCount > 2 ? 2 : 1) : */-1); @@ -143,13 +143,13 @@ void Classify3DMASCDialog::setComboBoxIndex(QMap rolesAndNames { QString name; - if (label != testLabel) + if (label == testLabel) { - name = rolesAndNames[label->text()]; + name = rolesAndNames[QString("TEST")]; // testLabel is specific, the role is always TEST } else { - name = rolesAndNames[QString("TEST")]; // this is because the label text of testLabel is 'TEST on (optional)' + name = rolesAndNames[label->text()]; } QMap::iterator it = namesAndUniqueIds.find(name); if (it != namesAndUniqueIds.end()) From f302e5495558db1fc9d35d362ae9b46a93ee9ca5 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Tue, 20 Feb 2024 08:57:19 +0100 Subject: [PATCH 13/14] changes following PR#6 --- qClassify3DMASCDialog.cpp | 37 +++++++++++++------------------------ qClassify3DMASCDialog.h | 2 +- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/qClassify3DMASCDialog.cpp b/qClassify3DMASCDialog.cpp index 1cc4c95..24450cd 100644 --- a/qClassify3DMASCDialog.cpp +++ b/qClassify3DMASCDialog.cpp @@ -69,23 +69,20 @@ Classify3DMASCDialog::Classify3DMASCDialog(ccMainAppInterface* app, bool trainMo ccHObject::Container clouds; if (m_app->dbRootObject()) { - m_app->dbRootObject()->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD); + m_app->dbRootObject()->filterChildren(clouds, true, CC_TYPES::POINT_CLOUD, true); } unsigned cloudCount = 0; for (size_t i = 0; i < clouds.size(); ++i) { - if (clouds[i]->isA(CC_TYPES::POINT_CLOUD)) //as filterChildren only test 'isKindOf' - { - QString name = clouds[i]->getName() + QString(" [%1]").arg(clouds[i]->getUniqueID()); - QVariant uniqueID(clouds[i]->getUniqueID()); - cloud1ComboBox->addItem(name, uniqueID); - cloud2ComboBox->addItem(name, uniqueID); - cloud3ComboBox->addItem(name, uniqueID); - cloud4ComboBox->addItem(name, uniqueID); - testCloudComboBox->addItem(name, uniqueID); - ++cloudCount; - } + QString name = clouds[i]->getName() + QString(" [%1]").arg(clouds[i]->getUniqueID()); + QVariant uniqueID(clouds[i]->getUniqueID()); + cloud1ComboBox->addItem(name, uniqueID); + cloud2ComboBox->addItem(name, uniqueID); + cloud3ComboBox->addItem(name, uniqueID); + cloud4ComboBox->addItem(name, uniqueID); + testCloudComboBox->addItem(name, uniqueID); + ++cloudCount; } testCloudComboBox->addItem("", 0); @@ -139,19 +136,11 @@ void Classify3DMASCDialog::writeSettings() settings.setValue("keepAttributes", keepAttributesCheckBox->isChecked()); } -void Classify3DMASCDialog::setComboBoxIndex(QMap rolesAndNames, QLabel* label, QMap namesAndUniqueIds, QComboBox* comboBox) +void Classify3DMASCDialog::setComboBoxIndex(const QMap& rolesAndNames, QLabel* label, const QMap& namesAndUniqueIds, QComboBox* comboBox) { - QString name; + QString name = label == testLabel ? QString("TEST") : label->text(); // testLabel is specific, the role is always TEST - if (label == testLabel) - { - name = rolesAndNames[QString("TEST")]; // testLabel is specific, the role is always TEST - } - else - { - name = rolesAndNames[label->text()]; - } - QMap::iterator it = namesAndUniqueIds.find(name); + QMap::const_iterator it(namesAndUniqueIds.find(name)); if (it != namesAndUniqueIds.end()) { int index = comboBox->findData(it.value()); @@ -229,7 +218,7 @@ void Classify3DMASCDialog::setCloudRoles(const QList& roles, QString& c } for (size_t i = 0; i < clouds.size(); ++i) { - if (clouds[i]->isA(CC_TYPES::POINT_CLOUD)) //as filterChildren only test 'isKindOf' + if (clouds[i]->isA(CC_TYPES::POINT_CLOUD)) //as filterChildren only tests 'isKindOf' { QVariant uniqueID(clouds[i]->getUniqueID()); namesAndUniqueIds[clouds[i]->getName().toUpper()] = uniqueID; diff --git a/qClassify3DMASCDialog.h b/qClassify3DMASCDialog.h index 1d6de90..5fd29d6 100644 --- a/qClassify3DMASCDialog.h +++ b/qClassify3DMASCDialog.h @@ -45,7 +45,7 @@ public: void setCloudRoles(const QList& roles, QString &corePointsLabel, const QMap &rolesAndNames); //! Preset the combo boxes if possible with the names specified in the parameter file - void setComboBoxIndex(QMap rolesAndNames, QLabel* label, QMap namesAndUniqueIds, QComboBox* comboBox); + void setComboBoxIndex(const QMap& rolesAndNames, QLabel* label, const QMap &namesAndUniqueIds, QComboBox* comboBox); //! Returns the selected point clouds void getClouds(QMap& clouds) const; From 1e782f04b7672e343cef28fd803f55225036a091 Mon Sep 17 00:00:00 2001 From: Paul RASCLE Date: Mon, 4 Mar 2024 21:25:33 +0100 Subject: [PATCH 14/14] building plugin 3DMASC on macOS, without OpenMP --- q3DMASCTools.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 7ceb73f..e66d348 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -1387,7 +1387,11 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features { cancelled = true; success = false; +#if defined(_OPENMP) errorStr = "Feature computation failed for point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; +#else + errorStr = "Feature computation failed for point " + QString::number(i); +#endif ccLog::Error(localErrorStr); } @@ -1399,7 +1403,11 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features if (cancelled) { //process cancelled by the user +#if defined(_OPENMP) errorStr = "Process cancelled at point " + QString::number(i) + " (using OpenMP with " + QString::number(omp_get_num_threads()) + " threads)"; +#else + errorStr = "Process cancelled at point " + QString::number(i); +#endif ccLog::Warning(errorStr); success = false; }