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);