mirror of
https://github.com/dgirardeau/q3DMASC.git
synced 2026-08-29 16:40:49 +08:00
Training dialog (to set RT parameters mostly)
This commit is contained in:
@@ -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
@@ -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
@@ -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, ¶meters, nullptr))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user