From 80a2ae5064be6dab7f0dfa064cac406f8dc6f5b1 Mon Sep 17 00:00:00 2001 From: Daniel Girardeau-Montaut Date: Sat, 19 Jan 2019 21:40:38 +0100 Subject: [PATCH] Training dialog (to set RT parameters mostly) --- Train3DMASCDialog.ui | 182 +++++++++++++++++++++++++++++++++++++++++ q3DMASC.cpp | 114 ++++++++++++++++---------- q3DMASCTools.cpp | 49 ++++++++++- q3DMASCTools.h | 2 +- qTrain3DMASCDialog.cpp | 25 ++++++ qTrain3DMASCDialog.h | 35 ++++++++ 6 files changed, 358 insertions(+), 49 deletions(-) create mode 100644 Train3DMASCDialog.ui create mode 100644 qTrain3DMASCDialog.cpp create mode 100644 qTrain3DMASCDialog.h 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); + +};