diff --git a/Train3DMASCDialog.ui b/Train3DMASCDialog.ui
new file mode 100644
index 0000000..eead0f6
--- /dev/null
+++ b/Train3DMASCDialog.ui
@@ -0,0 +1,182 @@
+
+
+ Train3DMASCDialog
+
+
+
+ 0
+ 0
+ 397
+ 476
+
+
+
+ Dialog
+
+
+ -
+
+
+ Random Trees
+
+
+
-
+
+
+ max depth
+
+
+
+ -
+
+
+ 1
+
+
+ 25
+
+
+
+ -
+
+
+ max tree count
+
+
+
+ -
+
+
+ 1
+
+
+ 10000
+
+
+ 10
+
+
+ 100
+
+
+
+ -
+
+
+ active var count
+
+
+
+ -
+
+
+ -
+
+
+ min sample count
+
+
+
+ -
+
+
+ 1
+
+
+
+
+
+
+ -
+
+
+ Other
+
+
+
-
+
+
+ Test data ratio
+
+
+
+ -
+
+
+ %
+
+
+ 0
+
+
+ 99
+
+
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Train3DMASCDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Train3DMASCDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/q3DMASC.cpp b/q3DMASC.cpp
index e0fb241..12c57af 100644
--- a/q3DMASC.cpp
+++ b/q3DMASC.cpp
@@ -22,6 +22,7 @@
#include "q3DMASCClassifier.h"
#include "q3DMASCTools.h"
#include "qClassify3DMASCDialog.h"
+#include "qTrain3DMASCDialog.h"
#include "q3DMASCCommands.h"
//qCC_db
@@ -198,13 +199,6 @@ void q3DMASCPlugin::doTrainAction()
if (!ShowTrainDisclaimer(m_app))
return;
- masc::TrainParameters params;
- if (params.testDataRatio < 0 || params.testDataRatio > 0.99f)
- {
- m_app->dispToConsole("Invalid test data ratio", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
- return;
- }
-
QString inputFilename;
{
QSettings settings;
@@ -220,10 +214,12 @@ void q3DMASCPlugin::doTrainAction()
settings.endGroup();
}
+ static masc::TrainParameters s_params;
+
std::vector loadedClouds;
masc::CorePoints corePoints;
masc::Feature::Set features;
- if (!masc::Tools::LoadTrainingFile(inputFilename, features, loadedClouds, corePoints))
+ if (!masc::Tools::LoadTrainingFile(inputFilename, features, loadedClouds, corePoints, s_params))
{
while (!loadedClouds.empty())
{
@@ -289,58 +285,86 @@ void q3DMASCPlugin::doTrainAction()
m_app->redrawAll();
- //randomly select the training points
- QScopedPointer trainSubset(new CCLib::ReferenceCloud(corePoints.cloud));
- QScopedPointer testSubset(new CCLib::ReferenceCloud(corePoints.cloud));
- if (!masc::Tools::RandomSubset(corePoints.cloud, params.testDataRatio, testSubset.data(), trainSubset.data()))
+ while (true)
{
- m_app->dispToConsole("Not enough memory", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
- return;
- }
-
- //train the classifier
- masc::Classifier classifier;
- {
- QString errorMessage;
- if (!classifier.train(corePoints.cloud, params.rt, features, errorMessage, trainSubset.data(), m_app->getMainWindow()))
+ Train3DMASCDialog trainDlg(m_app->getMainWindow());
+ trainDlg.maxDepthSpinBox->setValue(s_params.rt.maxDepth);
+ trainDlg.maxTreeCountSpinBox->setValue(s_params.rt.maxTreeCount);
+ trainDlg.activeVarCountSpinBox->setValue(s_params.rt.activeVarCount);
+ trainDlg.minSampleCountSpinBox->setValue(s_params.rt.minSampleCount);
+ trainDlg.testDataRatioSpinBox->setValue(static_cast(s_params.testDataRatio * 100));
+ if (!trainDlg.exec())
{
- m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
return;
}
- QString outputFilename;
+ s_params.rt.maxDepth = trainDlg.maxDepthSpinBox->value();
+ s_params.rt.maxTreeCount = trainDlg.maxTreeCountSpinBox->value();
+ s_params.rt.activeVarCount = trainDlg.activeVarCountSpinBox->value();
+ s_params.rt.minSampleCount = trainDlg.minSampleCountSpinBox->value();
+ s_params.testDataRatio = trainDlg.testDataRatioSpinBox->value() / 100.0f;
+ if (s_params.testDataRatio < 0 || s_params.testDataRatio > 0.99f)
{
- QSettings settings;
- settings.beginGroup("3DMASC");
- QString outputPath = settings.value("FilePath", QCoreApplication::applicationDirPath()).toString();
- outputFilename = QFileDialog::getSaveFileName(m_app->getMainWindow(), "Save 3DMASC classifier", outputPath, "*.txt");
- if (outputFilename.isNull())
+ assert(false);
+ m_app->dispToConsole("Invalid test data ratio", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
+ return;
+ }
+
+ //randomly select the training points
+ QScopedPointer trainSubset(new CCLib::ReferenceCloud(corePoints.cloud));
+ QScopedPointer testSubset(new CCLib::ReferenceCloud(corePoints.cloud));
+ if (!masc::Tools::RandomSubset(corePoints.cloud, s_params.testDataRatio, testSubset.data(), trainSubset.data()))
+ {
+ m_app->dispToConsole("Not enough memory", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
+ return;
+ }
+
+ //train the classifier
+ masc::Classifier classifier;
+ {
+ QString errorMessage;
+ if (!classifier.train(corePoints.cloud, s_params.rt, features, errorMessage, trainSubset.data(), m_app->getMainWindow()))
{
- //process cancelled by the user
+ m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
return;
}
- settings.setValue("FilePath", QFileInfo(outputFilename).absolutePath());
- settings.endGroup();
+
+ QString outputFilename;
+ {
+ QSettings settings;
+ settings.beginGroup("3DMASC");
+ QString outputPath = settings.value("FilePath", QCoreApplication::applicationDirPath()).toString();
+ outputFilename = QFileDialog::getSaveFileName(m_app->getMainWindow(), "Save 3DMASC classifier", outputPath, "*.txt");
+ if (outputFilename.isNull())
+ {
+ //process cancelled by the user
+ return;
+ }
+ settings.setValue("FilePath", QFileInfo(outputFilename).absolutePath());
+ settings.endGroup();
+ }
+
+ //save the classifier
+ if (masc::Tools::SaveClassifier(outputFilename, features, classifier, m_app->getMainWindow()))
+ {
+ m_app->dispToConsole("Classifier succesfully saved to " + outputFilename, ccMainAppInterface::STD_CONSOLE_MESSAGE);
+ }
}
- //save the classifier
- if (masc::Tools::SaveClassifier(outputFilename, features, classifier, m_app->getMainWindow()))
+ //test classifier
{
- m_app->dispToConsole("Classifier succesfully saved to " + outputFilename, ccMainAppInterface::STD_CONSOLE_MESSAGE);
- }
- }
+ masc::Classifier::AccuracyMetrics metrics;
+ QString errorMessage;
+ if (!classifier.evaluate(features, testSubset.data(), metrics, errorMessage, m_app->getMainWindow()))
+ {
+ m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
+ return;
+ }
- //test classifier
- {
- masc::Classifier::AccuracyMetrics metrics;
- QString errorMessage;
- if (!classifier.evaluate(features, testSubset.data(), metrics, errorMessage, m_app->getMainWindow()))
- {
- m_app->dispToConsole(errorMessage, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
- return;
+ m_app->dispToConsole(QString("Correct = %1 / %2 --> accuracy = %3").arg(metrics.goodGuess).arg(metrics.sampleCount).arg(metrics.ratio), ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
- m_app->dispToConsole(QString("Correct = %1 / %2 --> accuracy = %3").arg(metrics.goodGuess).arg(metrics.sampleCount).arg(metrics.ratio), ccMainAppInterface::STD_CONSOLE_MESSAGE);
+ break;
}
}
diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp
index 4ff9064..678c4ee 100644
--- a/q3DMASCTools.cpp
+++ b/q3DMASCTools.cpp
@@ -609,6 +609,7 @@ static bool LoadFileCommon( const QString& filename,
std::vector& rawFeatures,
masc::CorePoints* corePoints = nullptr,
masc::Classifier* classifier = nullptr,
+ TrainParameters* parameters = nullptr,
QWidget* parent = nullptr)
{
QFileInfo fi(filename);
@@ -730,6 +731,47 @@ static bool LoadFileCommon( const QString& filename,
return false;
}
}
+ else if (upperLine.startsWith("PARAM_")) //parameter
+ {
+ if (parameters) //no need to actually read the parameters if the caller didn't requested them
+ {
+ QStringList tokens = upperLine.split("=");
+ if (tokens.size() != 2)
+ {
+ ccLog::Warning(QString("Line #%1: malformed parameter command (expecting param_XXX=Y)").arg(lineNumber));
+ return false;
+ }
+ bool ok = false;
+ if (tokens[0] == "PARAM_MAX_DEPTH")
+ {
+ parameters->rt.maxDepth = tokens[1].toInt(&ok);
+ }
+ else if (tokens[0] == "PARAM_MAX_TREE_COUNT")
+ {
+ parameters->rt.maxTreeCount = tokens[1].toInt(&ok);
+ }
+ else if (tokens[0] == "PARAM_ACTIVE_VAR_COUNT")
+ {
+ parameters->rt.activeVarCount = tokens[1].toInt(&ok);
+ }
+ else if (tokens[0] == "PARAM_MIN_SAMPLE_COUNT")
+ {
+ parameters->rt.minSampleCount = tokens[1].toInt(&ok);
+ }
+ else if (tokens[0] == "PARAM_TEST_DATA_RATIO")
+ {
+ parameters->testDataRatio = tokens[1].toFloat(&ok);
+ }
+ else
+ {
+ ccLog::Warning(QString("Line #%1: unrecognized parameter: ").arg(lineNumber) + tokens[0]);
+ }
+ if (!ok)
+ {
+ ccLog::Warning(QString("Line #%1: invalid value for parameter ").arg(lineNumber) + tokens[0]);
+ }
+ }
+ }
else
{
ccLog::Warning(QString("Line #%1: unrecognized token/command: ").arg(lineNumber) + (line.length() < 10 ? line : line.left(10) + "..."));
@@ -750,16 +792,17 @@ static bool LoadFileCommon( const QString& filename,
bool Tools::LoadClassifier(QString filename, const NamedClouds& clouds, Feature::Set& rawFeatures, masc::Classifier& classifier, QWidget* parent/*=nullptr*/)
{
- return LoadFileCommon(filename, const_cast(clouds), true, rawFeatures, nullptr, &classifier, parent);
+ return LoadFileCommon(filename, const_cast(clouds), true, rawFeatures, nullptr, &classifier, nullptr, parent);
}
bool Tools::LoadTrainingFile( QString filename,
Feature::Set& rawFeatures,
std::vector& loadedClouds,
- CorePoints& corePoints)
+ CorePoints& corePoints,
+ TrainParameters& parameters)
{
NamedClouds clouds;
- if (LoadFileCommon(filename, clouds, false, rawFeatures, &corePoints, nullptr, nullptr))
+ if (LoadFileCommon(filename, clouds, false, rawFeatures, &corePoints, nullptr, ¶meters, nullptr))
{
try
{
diff --git a/q3DMASCTools.h b/q3DMASCTools.h
index d656e4e..aec5765 100644
--- a/q3DMASCTools.h
+++ b/q3DMASCTools.h
@@ -36,7 +36,7 @@ namespace masc
{
public:
- static bool LoadTrainingFile(QString filename, Feature::Set& rawFeatures, std::vector& loadedClouds, CorePoints& corePoints);
+ static bool LoadTrainingFile(QString filename, Feature::Set& rawFeatures, std::vector& loadedClouds, CorePoints& corePoints, TrainParameters& parameters);
static bool SaveClassifier(QString filename, const Feature::Set& features, const masc::Classifier& classifier, QWidget* parent = nullptr);
diff --git a/qTrain3DMASCDialog.cpp b/qTrain3DMASCDialog.cpp
new file mode 100644
index 0000000..4854296
--- /dev/null
+++ b/qTrain3DMASCDialog.cpp
@@ -0,0 +1,25 @@
+//##########################################################################
+//# #
+//# 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: UNIVERSITE EUROPEENNE DE BRETAGNE #
+//# #
+//##########################################################################
+
+#include "qTrain3DMASCDialog.h"
+
+Train3DMASCDialog::Train3DMASCDialog(QWidget* parent/*=nullptr*/)
+ : QDialog(parent)
+ , Ui::Train3DMASCDialog()
+{
+ setupUi(this);
+}
diff --git a/qTrain3DMASCDialog.h b/qTrain3DMASCDialog.h
new file mode 100644
index 0000000..31ea61e
--- /dev/null
+++ b/qTrain3DMASCDialog.h
@@ -0,0 +1,35 @@
+#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
+
+#include
+
+//! 3DMASC plugin 'train' dialog
+class Train3DMASCDialog : public QDialog, public Ui::Train3DMASCDialog
+{
+ Q_OBJECT
+
+public:
+
+ //! Default constructor
+ Train3DMASCDialog(QWidget* parent = nullptr);
+
+};