diff --git a/Classify3DMASCDialog.ui b/Classify3DMASCDialog.ui index 6a248af..ffdb9c0 100644 --- a/Classify3DMASCDialog.ui +++ b/Classify3DMASCDialog.ui @@ -38,13 +38,6 @@ Data - - - - Cloud - - - @@ -52,16 +45,6 @@ - - - - - 0 - 0 - - - - @@ -89,6 +72,13 @@ + + + + Cloud + + + @@ -103,19 +93,36 @@ + + + + + 0 + 0 + + + + + + + + color:red; + + + Assign each role to the right cloud, and select the role of the cloud to be classified + + + Qt::AlignCenter + + + - - - color:red; - + - Assign each role to the right cloud, and select the role of the cloud to be classified - - - Qt::AlignCenter + Keep attributes on completion diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index c2fe3f0..d4f8e82 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -24,7 +24,8 @@ using namespace masc; bool ContextBasedFeature::prepare( const CorePoints& corePoints, QString& error, - CCLib::GenericProgressCallback* progressCb/*=nullptr*/) + CCLib::GenericProgressCallback* progressCb/*=nullptr*/, + SFCollector* generatedScalarFields/*=nullptr*/) { if (!cloud1 || !corePoints.cloud) { @@ -62,7 +63,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, //and the scalar field assert(!sf); - sf = PrepareSF(corePoints.cloud, qPrintable(resultSFName)); + sf = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields); if (!sf) { error = QString("Failed to prepare scalar %1 @ scale %2").arg(resultSFName).arg(scale); @@ -117,7 +118,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, ScalarType s = NAN_VALUE; int neighborhoodSize = 0; - if (octree->findPointNeighbourhood(P, &Yk, static_cast(kNN), octreeLevel, maxSquareDist, 0, &neighborhoodSize) >= kNN) + if (octree->findPointNeighbourhood(P, &Yk, static_cast(kNN), octreeLevel, maxSquareDist, 0, &neighborhoodSize) >= static_cast(kNN)) { CCVector3d sumQ(0, 0, 0); for (int k = 0; k < kNN; ++k) diff --git a/ContextBasedFeature.h b/ContextBasedFeature.h index 67f9a3e..3f1b5a2 100644 --- a/ContextBasedFeature.h +++ b/ContextBasedFeature.h @@ -82,7 +82,7 @@ namespace masc //inherited from Feature virtual Type getType() const override { return Type::ContextBasedFeature; } virtual Feature::Shared clone() const override { return Feature::Shared(new ContextBasedFeature(*this)); } - virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr) override; + virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr) override; virtual bool finish(const CorePoints& corePoints, QString& error) override; virtual bool checkValidity(QString &error) const override; virtual QString toString() const override; diff --git a/DualCloudFeature.cpp b/DualCloudFeature.cpp index 93ab81d..62fdd12 100644 --- a/DualCloudFeature.cpp +++ b/DualCloudFeature.cpp @@ -21,7 +21,8 @@ using namespace masc; bool DualCloudFeature::prepare( const CorePoints& corePoints, QString& error, - CCLib::GenericProgressCallback* progressCb/*=nullptr*/) + CCLib::GenericProgressCallback* progressCb/*=nullptr*/, + SFCollector* generatedScalarFields/*=nullptr*/) { //TODO return false; diff --git a/DualCloudFeature.h b/DualCloudFeature.h index 3db66e6..d617810 100644 --- a/DualCloudFeature.h +++ b/DualCloudFeature.h @@ -67,7 +67,7 @@ namespace masc //inherited from Feature virtual Type getType() const override { return Type::DualCloudFeature; } virtual Feature::Shared clone() const override { return Feature::Shared(new DualCloudFeature(*this)); } - virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr) override; + virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr) override; virtual bool checkValidity(QString &error) const override; virtual QString toString() const override { diff --git a/FeaturesInterface.cpp b/FeaturesInterface.cpp index 3ded6c3..9e8b5cc 100644 --- a/FeaturesInterface.cpp +++ b/FeaturesInterface.cpp @@ -25,7 +25,7 @@ using namespace masc; -CCLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resultSFName) +CCLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resultSFName, SFCollector* generatedScalarFields/*=nullptr*/) { if (!cloud || !resultSFName) { @@ -50,6 +50,12 @@ CCLib::ScalarField* Feature::PrepareSF(ccPointCloud* cloud, const char* resultSF return nullptr; } cloud->addScalarField(newSF); + + if (generatedScalarFields) + { + //track the generated scalar-field + generatedScalarFields->push(cloud, newSF); + } resultSF = newSF; diff --git a/FeaturesInterface.h b/FeaturesInterface.h index 4cad8d3..788d90a 100644 --- a/FeaturesInterface.h +++ b/FeaturesInterface.h @@ -20,6 +20,7 @@ //Local #include "CorePoints.h" +#include "ScalarFieldCollector.h" //Qt #include @@ -141,7 +142,7 @@ namespace masc virtual Feature::Shared clone() const = 0; //! Prepares the feature (compute the scalar field, etc.) - virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr) = 0; + virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr) = 0; //! Finishes the feature preparation (update the scalar field, etc.) virtual bool finish(const CorePoints& corePoints, QString& error) { /* does nothing by default*/return true; } @@ -177,7 +178,7 @@ namespace masc public: //helpers //! Creates (or resets) a scalar field with the given name on the input core points cloud - static CCLib::ScalarField* PrepareSF(ccPointCloud* cloud, const char* resultSFName); + static CCLib::ScalarField* PrepareSF(ccPointCloud* cloud, const char* resultSFName, SFCollector* generatedScalarFields = nullptr); //! Performs a mathematical operation between two scalar fields (they must have the same size!) static bool PerformMathOp(CCLib::ScalarField* sf1, const CCLib::ScalarField* sf2, Operation op); diff --git a/NeighborhoodFeature.cpp b/NeighborhoodFeature.cpp index d68ca84..9494865 100644 --- a/NeighborhoodFeature.cpp +++ b/NeighborhoodFeature.cpp @@ -54,7 +54,8 @@ bool NeighborhoodFeature::checkValidity(QString &error) const bool NeighborhoodFeature::prepare( const CorePoints& corePoints, QString& error, - CCLib::GenericProgressCallback* progressCb/*=nullptr*/) + CCLib::GenericProgressCallback* progressCb/*=nullptr*/, + SFCollector* generatedScalarFields/*=nullptr*/) { if (!cloud1 || !corePoints.cloud) { diff --git a/NeighborhoodFeature.h b/NeighborhoodFeature.h index 3c6ab88..de724fe 100644 --- a/NeighborhoodFeature.h +++ b/NeighborhoodFeature.h @@ -153,7 +153,7 @@ namespace masc //inherited from Feature virtual Type getType() const override { return Type::NeighborhoodFeature; } virtual Feature::Shared clone() const override { return Feature::Shared(new NeighborhoodFeature(*this)); } - virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr) override; + virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr) override; virtual bool finish(const CorePoints& corePoints, QString& error) override; virtual bool checkValidity(QString &error) const override; virtual QString toString() const override; diff --git a/PointFeature.cpp b/PointFeature.cpp index 7cfdb0b..89ac09d 100644 --- a/PointFeature.cpp +++ b/PointFeature.cpp @@ -458,106 +458,10 @@ static bool ExtractStatFromSF( const CCVector3& queryPoint, return true; } -static CCLib::ScalarField* ExtractStat( const CorePoints& corePoints, - ccPointCloud* sourceCloud, - const IScalarFieldWrapper* sourceField, - double scale, - Feature::Stat stat, - const char* resultSFName, - CCLib::GenericProgressCallback* progressCb = nullptr) -{ - if (!corePoints.cloud || !sourceCloud || !sourceField || scale <= 0.0 || stat == Feature::NO_STAT || !resultSFName) - { - //invalid input parameters - assert(false); - return nullptr; - } - - ccOctree::Shared octree = sourceCloud->getOctree(); - if (!octree) - { - ccLog::Print(QString("Computing octree of cloud %1 (%2 points)").arg(sourceCloud->getName()).arg(sourceCloud->size())); - octree = sourceCloud->computeOctree(progressCb); - if (!octree) - { - ccLog::Warning("Failed to compute octree"); - return nullptr; - } - } - - CCLib::ScalarField* resultSF = nullptr; - int sfIdx = corePoints.cloud->getScalarFieldIndexByName(resultSFName); - if (sfIdx >= 0) - { - resultSF = corePoints.cloud->getScalarField(sfIdx); - } - else - { - resultSF = new ccScalarField(resultSFName); - if (!resultSF->resizeSafe(corePoints.cloud->size())) - { - ccLog::Warning("Not enough memory"); - resultSF->release(); - return nullptr; - } - } - resultSF->fill(NAN_VALUE); - - PointCoordinateType radius = static_cast(scale / 2); - unsigned char octreeLevel = octree->findBestLevelForAGivenNeighbourhoodSizeExtraction(radius); //scale is the diameter! - - unsigned pointCount = corePoints.size(); - if (progressCb) - { - progressCb->setInfo(qPrintable(QString("Computing field: %1\n(core points: %2)").arg(resultSFName).arg(pointCount))); - } - ccLog::Print(QString("Computing field: %1 (core points: %2)").arg(resultSFName).arg(pointCount)); - CCLib::NormalizedProgress nProgress(progressCb, pointCount); - - for (unsigned i = 0; i < pointCount; ++i) - { - double outputValue = 0; - if (!ExtractStatFromSF( *corePoints.cloud->getPoint(i), - octree.data(), - octreeLevel, - stat, - *sourceField, - radius, - outputValue)) - { - //unexpected error - resultSF->release(); - return nullptr; - } - - ScalarType v = static_cast(outputValue); - resultSF->setValue(i, v); - - if (progressCb && !nProgress.oneStep()) - { - //process cancelled by the user - ccLog::Warning("Process cancelled"); - resultSF->release(); - return nullptr; - } - } - - resultSF->computeMinAndMax(); - int newSFIdx = corePoints.cloud->addScalarField(static_cast(resultSF)); - //update display - //if (corePoints.cloud->getDisplay()) - { - corePoints.cloud->setCurrentDisplayedScalarField(newSFIdx); - //corePoints.cloud->getDisplay()->redraw(); - //QCoreApplication::processEvents(); - } - - return resultSF; -} - bool PointFeature::prepare( const CorePoints& corePoints, QString& error, - CCLib::GenericProgressCallback* progressCb/*=nullptr*/) + CCLib::GenericProgressCallback* progressCb/*=nullptr*/, + SFCollector* generatedScalarFields/*=nullptr*/) { if (!cloud1 || !corePoints.cloud) { @@ -695,6 +599,13 @@ bool PointFeature::prepare( const CorePoints& corePoints, } resultSF->computeMinAndMax(); int newSFIdx = corePoints.cloud->addScalarField(static_cast(resultSF)); + + if (generatedScalarFields) + { + //track the generated scalar-field + generatedScalarFields->push(corePoints.cloud, resultSF); + } + //update display //if (corePoints.cloud->getDisplay()) { diff --git a/PointFeature.h b/PointFeature.h index 5c9492d..aef1f49 100644 --- a/PointFeature.h +++ b/PointFeature.h @@ -187,7 +187,7 @@ namespace masc //inherited from Feature virtual Type getType() const override { return Type::PointFeature; } virtual Feature::Shared clone() const override { return Feature::Shared(new PointFeature(*this)); } - virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr) override; + virtual bool prepare(const CorePoints& corePoints, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr) override; virtual bool finish(const CorePoints& corePoints, QString& error) override; virtual bool checkValidity(QString &error) const override; virtual QString toString() const override; diff --git a/ScalarFieldCollector.cpp b/ScalarFieldCollector.cpp new file mode 100644 index 0000000..fb17271 --- /dev/null +++ b/ScalarFieldCollector.cpp @@ -0,0 +1,58 @@ +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: q3DMASC # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 or later of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# COPYRIGHT: Dimitri Lague / CNRS / UEB # +//# # +//########################################################################## + +#include "ScalarFieldCollector.h" + +//qCC_db +#include + +//CCLib +#include + +//system +#include + +void SFCollector::push(ccPointCloud* cloud, CCLib::ScalarField* sf) +{ + (*this)[cloud].insert(sf); +} + +void SFCollector::clear() +{ + for (QMap< ccPointCloud*, std::set >::iterator it = begin(); it != end(); ++it) + { + ccPointCloud* cloud = it.key(); + std::set& sfs = it.value(); + + for (CCLib::ScalarField* sf : sfs) + { + int sfIdx = cloud->getScalarFieldIndexByName(sf->getName()); + if (sfIdx >= 0) + { + cloud->deleteScalarField(sfIdx); + } + else + { + ccLog::Warning(QString("[SFCollector] Scalar field '%1' can't be found anymore").arg(sf->getName())); + } + } + + sfs.clear(); + } + + clear(); +} diff --git a/ScalarFieldCollector.h b/ScalarFieldCollector.h new file mode 100644 index 0000000..4c30745 --- /dev/null +++ b/ScalarFieldCollector.h @@ -0,0 +1,43 @@ +#pragma once + +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: q3DMASC # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 or later of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# COPYRIGHT: Dimitri Lague / CNRS / UEB # +//# # +//########################################################################## + +//Qt +#include + +//system +#include + +class ccPointCloud; + +namespace CCLib +{ + class ScalarField; +}; + +//! SF collector +/** For tracking the creation and removing a set of scalar fields +**/ +class SFCollector : QMap< ccPointCloud*, std::set > +{ + public: + + void push(ccPointCloud* cloud, CCLib::ScalarField* sf); + + void clear(); +}; diff --git a/classifyDisclaimerDlg.ui b/classifyDisclaimerDlg.ui index 35d360d..b3051f3 100644 --- a/classifyDisclaimerDlg.ui +++ b/classifyDisclaimerDlg.ui @@ -52,7 +52,7 @@ p, li { white-space: pre-wrap; } <p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; font-weight:600; color:#1f497d;">3D Multi-cloud, Multi-Attribute, multi-Scale, multi Class classification (3DMASC)</span></p> <p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; font-style:italic; color:#1f497d;">Le Guennec, A., Lague, D., Corpetti, Th., Lefevre, S.</span></p> <p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Calibri,sans-serif'; font-size:10pt; font-style:italic; color:#1f497d; background-color:#ffffff;"><br /></p> -<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;">Funded by Université Européenne de Bretagne, Centre National de la Recherche Scientifique and EEC Marie-Curie actions</span></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;">Funded by UEB (Université Européenne de Bretagne), CNRS (Centre National de la Recherche Scientifique) and EEC Marie-Curie actions</span></p> <p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;"><br /></p> <p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d;">Enjoy!</span></p></body></html> diff --git a/q3DMASC.cpp b/q3DMASC.cpp index 79a06ce..e0fb241 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -22,6 +22,7 @@ #include "q3DMASCClassifier.h" #include "q3DMASCTools.h" #include "qClassify3DMASCDialog.h" +#include "q3DMASCCommands.h" //qCC_db #include @@ -134,12 +135,16 @@ void q3DMASCPlugin::doClassifyAction() Classify3DMASCDialog classifDlg(m_app); classifDlg.setCloudRoles(cloudLabels); classifDlg.classifierFileLineEdit->setText(inputFilename); + static bool s_keepAttributes = false; + classifDlg.keepAttributesCheckBox->setChecked(s_keepAttributes); if (!classifDlg.exec()) { //process cancelled by the user return; } + s_keepAttributes = classifDlg.keepAttributesCheckBox->isChecked(); + masc::Tools::NamedClouds clouds; QString mainCloudLabel; classifDlg.getClouds(clouds, mainCloudLabel); @@ -159,9 +164,11 @@ void q3DMASCPlugin::doClassifyAction() ccProgressDialog pDlg(true, m_app->getMainWindow()); pDlg.setAutoClose(false); //we don't want the progress dialog to 'pop' for each feature QString error; - if (!masc::Tools::PrepareFeatures(corePoints, features, error, &pDlg)) + SFCollector generatedScalarFields; + if (!masc::Tools::PrepareFeatures(corePoints, features, error, &pDlg, &generatedScalarFields)) { m_app->dispToConsole(error, ccMainAppInterface::ERR_CONSOLE_MESSAGE); + generatedScalarFields.clear(); return; } pDlg.close(); @@ -174,8 +181,14 @@ void q3DMASCPlugin::doClassifyAction() if (!classifier.classify(features, corePoints.cloud, errorMessage, m_app->getMainWindow())) { m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE); + generatedScalarFields.clear(); return; } + + if (!s_keepAttributes) + { + generatedScalarFields.clear(); + } } } @@ -338,5 +351,6 @@ void q3DMASCPlugin::registerCommands(ccCommandLineInterface* cmd) assert(false); return; } - //cmd->registerCommand(ccCommandLineInterface::Command::Shared(new CommandCanupoClassif)); + + cmd->registerCommand(ccCommandLineInterface::Command::Shared(new Command3DMASCClassif)); } diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index 765d89c..bd3b6a0 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -202,7 +202,7 @@ bool Classifier::classify(const Feature::Set& features, ccPointCloud* cloud, QSt } float predictedClass = m_rtrees->predict(test_data.row(0)); - classificationSF->setValue(i, static_cast(predictedClass)); + classificationSF->setValue(i, static_cast(predictedClass)); if (pDlg && !nProgress.oneStep()) { diff --git a/q3DMASCCommands.h b/q3DMASCCommands.h new file mode 100644 index 0000000..3a62da6 --- /dev/null +++ b/q3DMASCCommands.h @@ -0,0 +1,188 @@ +#pragma once + +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: qCANUPO # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 or later of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# COPYRIGHT: UEB (UNIVERSITE EUROPEENNE DE BRETAGNE) / CNRS # +//# # +//########################################################################## + +//CloudCompare +#include "../../ccCommandLineInterface.h" + +//Local +#include "q3DMASCTools.h" + +//qCC_db +#include + +//Qt +#include + +static const char COMMAND_3DMASC_CLASSIFY[] = "3DMASC_CLASSIFY"; +static const char COMMAND_3DMASC_KEEP_ATTRIBS[] = "KEEP_ATTRIBUTES"; + +struct Command3DMASCClassif : public ccCommandLineInterface::Command +{ + Command3DMASCClassif() : ccCommandLineInterface::Command("3DMASC Classify", COMMAND_3DMASC_CLASSIFY) {} + + virtual bool process(ccCommandLineInterface& cmd) override + { + cmd.print("[3DMASC]"); + if (cmd.arguments().size() < 2) + { + return cmd.error(QString("Missing parameter(s): classifier filename (.txt) and cloud roles after \"-%1\"").arg(COMMAND_3DMASC_CLASSIFY)); + } + + QString argument = cmd.arguments().front(); + + bool keepAttributes = false; + if (ccCommandLineInterface::IsCommand(argument, COMMAND_3DMASC_KEEP_ATTRIBS)) + { + keepAttributes = true; + + //local option confirmed, we can move on + cmd.arguments().pop_front(); + } + + if (cmd.arguments().size() < 2) + { + return cmd.error(QString("Missing parameter(s): classifier filename (.txt) and cloud roles after \"-%1\"").arg(COMMAND_3DMASC_CLASSIFY)); + } + + QString classifierFilename = cmd.arguments().front(); + cmd.arguments().pop_front(); + + QString cloudRolesStr = cmd.arguments().front(); + cmd.arguments().pop_front(); + + //process the cloud roles description + QStringList tokens = cloudRolesStr.split(QRegExp("\\s+"), QString::SkipEmptyParts); + + masc::Tools::NamedClouds cloudPerRole; + QString mainCloudRole; + for (const QString& token : tokens) + { + QStringList subTokens = token.split("="); + if (subTokens.size() != 2) + { + return cmd.error("Malformed cloud roles description (expecting: \"PC1=1 PC2=3 CTX=2\" for instance)"); + } + QString role = subTokens[0].toUpper(); + bool ok = false; + unsigned cloudIndex = subTokens[1].toUInt(&ok); + if (!ok || cloudIndex == 0) + { + return cmd.error("Malformed cloud roles description (expecting the cloud index corresponding to each role - starting from 1)"); + } + if (cloudIndex > cmd.clouds().size()) + { + return cmd.error(QString("Cloud index %1 exceeds the number of loaded clouds (=%2)").arg(cloudIndex).arg(cmd.clouds().size())); + } + cloudPerRole.insert(role, cmd.clouds()[cloudIndex].pc); + + if (mainCloudRole.isEmpty()) + { + mainCloudRole = role; + cmd.print("The classified cloud role will be " + role); + } + } + + //try to load the clouds roles from the classifier file + QSet cloudLabels; + if (!masc::Tools::LoadClassifierCloudLabels(classifierFilename, cloudLabels)) + { + return cmd.error("Failed to read classifier file"); + } + + for (QString label : cloudLabels) + { + if (!cloudPerRole.contains(label.toUpper())) + { + return cmd.error(QString("Role %1 has not been defined").arg(label)); + } + } + + masc::Feature::Set features; + masc::Classifier classifier; + if (!masc::Tools::LoadClassifier(classifierFilename, cloudPerRole, features, classifier, cmd.widgetParent())) + { + return cmd.error("Failed to load the classifier"); + } + + //internal consistency check + if (!cloudPerRole.contains(mainCloudRole)) + { + return cmd.error("Classified cloud not loaded/defined?!"); + } + + //the 'main cloud' is the cloud that should be classified + masc::CorePoints corePoints; + corePoints.origin = corePoints.cloud = cloudPerRole[mainCloudRole]; + + //prepare the main cloud + QScopedPointer pDlg; + if (!cmd.silentMode()) + { + pDlg.reset(new ccProgressDialog(true, cmd.widgetParent())); + pDlg->setAutoClose(false); //we don't want the progress dialog to 'pop' for each feature + } + + QString errorMessage; + SFCollector generatedScalarFields; + if (!masc::Tools::PrepareFeatures(corePoints, features, errorMessage, pDlg.data(), &generatedScalarFields)) + { + generatedScalarFields.clear(); + return cmd.error(errorMessage); + } + + if (pDlg) + { + pDlg->close(); + QCoreApplication::processEvents(); + pDlg->setAutoClose(true); //restore the default behavior of the progress dialog + } + + //apply classifier + { + if (!classifier.classify(features, corePoints.cloud, errorMessage, cmd.widgetParent())) + { + generatedScalarFields.clear(); + return cmd.error(errorMessage); + } + + if (!keepAttributes) + { + generatedScalarFields.clear(); + } + } + + if (cmd.autoSaveMode()) + { + for (CLCloudDesc& desc : cmd.clouds()) + { + if (desc.pc == corePoints.origin) + { + QString errorStr = cmd.exportEntity(desc, "CLASSIFIED"); + if (!errorStr.isEmpty()) + { + return cmd.error(errorStr); + } + break; + } + } + } + + return true; + } +}; diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index 080ba40..4ff9064 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -834,7 +834,7 @@ struct FeaturesAndScales QMap > contextBasedFeaturesPerScale; }; -bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& errorStr, CCLib::GenericProgressCallback* progressCb/*=nullptr*/) +bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& errorStr, CCLib::GenericProgressCallback* progressCb/*=nullptr*/, SFCollector* generatedScalarFields/*=nullptr*/) { if (features.empty() || !corePoints.origin) { diff --git a/q3DMASCTools.h b/q3DMASCTools.h index f1a3c2e..d656e4e 100644 --- a/q3DMASCTools.h +++ b/q3DMASCTools.h @@ -46,7 +46,7 @@ namespace masc static bool LoadClassifier(QString filename, const NamedClouds& clouds, Feature::Set& rawFeatures, masc::Classifier& classifier, QWidget* parent = nullptr); - static bool PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr); + static bool PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& error, CCLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr); static bool RandomSubset(ccPointCloud* cloud, float ratio, CCLib::ReferenceCloud* inRatioSubset, CCLib::ReferenceCloud* outRatioSubset);