Training dialog (to set RT parameters mostly)

This commit is contained in:
Daniel Girardeau-Montaut
2019-01-19 21:40:38 +01:00
parent ecbdb4973e
commit 80a2ae5064
6 changed files with 358 additions and 49 deletions
+182
View File
@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Train3DMASCDialog</class>
<widget class="QDialog" name="Train3DMASCDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>397</width>
<height>476</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="rtGroupBox">
<property name="title">
<string>Random Trees</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>max depth</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="maxDepthSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>25</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>max tree count</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="maxTreeCountSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>active var count</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="activeVarCountSpinBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>min sample count</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="minSampleCountSpinBox">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="otherGroupBox">
<property name="title">
<string>Other</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Test data ratio</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="testDataRatioSpinBox">
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>99</number>
</property>
<property name="value">
<number>20</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Train3DMASCDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Train3DMASCDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+69 -45
View File
@@ -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<ccPointCloud*> 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<CCLib::ReferenceCloud> trainSubset(new CCLib::ReferenceCloud(corePoints.cloud));
QScopedPointer<CCLib::ReferenceCloud> 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<int>(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<CCLib::ReferenceCloud> trainSubset(new CCLib::ReferenceCloud(corePoints.cloud));
QScopedPointer<CCLib::ReferenceCloud> 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;
}
}
+46 -3
View File
@@ -609,6 +609,7 @@ static bool LoadFileCommon( const QString& filename,
std::vector<Feature::Shared>& 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<NamedClouds&>(clouds), true, rawFeatures, nullptr, &classifier, parent);
return LoadFileCommon(filename, const_cast<NamedClouds&>(clouds), true, rawFeatures, nullptr, &classifier, nullptr, parent);
}
bool Tools::LoadTrainingFile( QString filename,
Feature::Set& rawFeatures,
std::vector<ccPointCloud*>& 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, &parameters, nullptr))
{
try
{
+1 -1
View File
@@ -36,7 +36,7 @@ namespace masc
{
public:
static bool LoadTrainingFile(QString filename, Feature::Set& rawFeatures, std::vector<ccPointCloud*>& loadedClouds, CorePoints& corePoints);
static bool LoadTrainingFile(QString filename, Feature::Set& rawFeatures, std::vector<ccPointCloud*>& loadedClouds, CorePoints& corePoints, TrainParameters& parameters);
static bool SaveClassifier(QString filename, const Feature::Set& features, const masc::Classifier& classifier, QWidget* parent = nullptr);
+25
View File
@@ -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);
}
+35
View File
@@ -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 <QDialog>
#include <ui_Train3DMASCDialog.h>
//! 3DMASC plugin 'train' dialog
class Train3DMASCDialog : public QDialog, public Ui::Train3DMASCDialog
{
Q_OBJECT
public:
//! Default constructor
Train3DMASCDialog(QWidget* parent = nullptr);
};