diff --git a/Classify3DMASCDialog.ui b/Classify3DMASCDialog.ui
index 941c11c..9a6a8a2 100644
--- a/Classify3DMASCDialog.ui
+++ b/Classify3DMASCDialog.ui
@@ -7,7 +7,7 @@
0
0
700
- 365
+ 393
@@ -185,6 +185,13 @@
+ -
+
+
+ Use existing attributes
+
+
+
-
diff --git a/Train3DMASCDialog.ui b/Train3DMASCDialog.ui
index 77afc53..d226cd3 100644
--- a/Train3DMASCDialog.ui
+++ b/Train3DMASCDialog.ui
@@ -13,7 +13,7 @@
Dialog
-
+
-
@@ -147,6 +147,33 @@
+ -
+
+
+ <html><head/><body><p>You the existing feature for the training. If not checked, all features will be recalculated and the existing features will be overwritten.</p></body></html>
+
+
+ Use existing attributes
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked : </p><p>* A directory is created near the parameter file, with name 3dmasc_yyyymmdd_HHhMM.</p><p>* A file 3dmasc_yyyymmdd_HHhMM.txt is created in this directory. </p><p>* Each time you train the classifier, the feature list and the classifier are stored and an entry is created in the trace file to save the overall accuracy.</p></body></html>
+
+
+ Keep traces
+
+
+
+ -
+
+
+ Keep attributes on completion
+
+
+
@@ -259,16 +286,6 @@
- -
-
-
- <html><head/><body><p>If checked, a directory is created near the parameters file. Each time you click on Run, the features used to train the random forest will be stored and an entry will be created in a specific file to save the metrics associated with the classifier.</p></body></html>
-
-
- Keep traces
-
-
-
diff --git a/confusionmatrix.cpp b/confusionmatrix.cpp
index b68d467..f6da8ae 100644
--- a/confusionmatrix.cpp
+++ b/confusionmatrix.cpp
@@ -8,6 +8,11 @@
#include
#include
+#include
+#include
+#include
+
+#include
ConfusionMatrix::ConfusionMatrix(std::vector &actual, std::vector &predicted, QWidget *parent) :
QWidget(parent),
@@ -15,7 +20,9 @@ ConfusionMatrix::ConfusionMatrix(std::vector &actual, std::vectorsetupUi(this);
this->setWindowFlag(Qt::WindowStaysOnTopHint);
+
compute(actual, predicted);
+
this->show();
this->setMinimumSize(this->ui->tableWidget->sizeHint());
}
@@ -119,9 +126,10 @@ void ConfusionMatrix::compute(std::vector& actual, std::vector classes(actual.begin(), actual.end());
- int nbClasses = classes.size();
- cv::Mat confusionMatrix(nbClasses, nbClasses, CV_32S, cv::Scalar(0));
- cv::Mat precisionRecallF1Score(nbClasses, 3, CV_32F, cv::Scalar(0));
+ nbClasses = classes.size();
+
+ confusionMatrix = cv::Mat(nbClasses, nbClasses, CV_32S, cv::Scalar(0));
+ precisionRecallF1Score = cv::Mat(nbClasses, 3, CV_32F, cv::Scalar(0));
// fill the confusion matrix
for (int i = 0; i < actual.size(); i++)
@@ -142,8 +150,7 @@ void ConfusionMatrix::compute(std::vector& actual, std::vector::iterator itB = classes.begin();
std::set::iterator itE = classes.end();
- std::vector vtr;
- vtr.assign(itB, itE);
+ class_numbers.assign(itB, itE);
// BUILD THE QTABLEWIDGET
@@ -184,9 +191,9 @@ void ConfusionMatrix::compute(std::vector& actual, std::vectorsetFont(font);
this->ui->tableWidget->setItem(1, 2 + nbClasses + F1_SCORE, newItem);
// add column names and row names
- for (int idx = 0; idx < vtr.size(); idx++)
+ for (int idx = 0; idx < class_numbers.size(); idx++)
{
- QString str = QString::number(vtr[idx]);
+ QString str = QString::number(class_numbers[idx]);
newItem = new QTableWidgetItem(str);
newItem->setFont(font);
this->ui->tableWidget->setItem(1, 2 + idx, newItem);
@@ -230,3 +237,42 @@ void ConfusionMatrix::setSessionRun(QString session, int run)
this->ui->label_sessionRun->setText(label);
}
+
+bool ConfusionMatrix::save(QString filePath)
+{
+ std::unique_ptr file(new QFile(filePath));
+ QTextStream stream;
+
+ if(!file->open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ ccLog::Error("impossible to open file: " + filePath);
+ return false;
+ }
+
+ if (file && file->isOpen())
+ {
+ stream.setDevice(file.get());
+ stream << "# columns: predicted classes\n# rows: actual classes\n";
+ stream << "# last three colums: precision / recall / F1-score\n";
+ for (auto class_number : class_numbers)
+ {
+ stream << class_number << " ";
+ }
+ stream << Qt::endl;
+ for (int row = 0; row < confusionMatrix.rows; row++)
+ {
+ stream << class_numbers.at(row) << " ";
+ for (int col = 0; col < confusionMatrix.cols; col++)
+ {
+ stream << confusionMatrix.at(row, col) << " ";
+ }
+ stream << precisionRecallF1Score.at(row, PRECISION) << " ";
+ stream << precisionRecallF1Score.at(row, RECALL) << " ";
+ stream << precisionRecallF1Score.at(row, F1_SCORE) << Qt::endl;
+ }
+ file->close();
+ return true;
+ }
+ else
+ return false;
+}
diff --git a/confusionmatrix.h b/confusionmatrix.h
index 5999664..7326d05 100644
--- a/confusionmatrix.h
+++ b/confusionmatrix.h
@@ -2,6 +2,7 @@
#define CONFUSIONMATRIX_H
#include
+#include
#include "CCTypes.h"
@@ -31,9 +32,15 @@ public:
void compute(std::vector& actual, std::vector& predicted);
void setSessionRun(QString session, int run);
float m_overallAccuracy;
+ bool save(QString filePath);
private:
+ std::set classes;
+ int nbClasses;
Ui::ConfusionMatrix *ui;
+ cv::Mat confusionMatrix;
+ cv::Mat precisionRecallF1Score;
+ std::vector class_numbers;
};
#endif // CONFUSIONMATRIX_H
diff --git a/q3DMASC.cpp b/q3DMASC.cpp
index dfbef64..88f201d 100644
--- a/q3DMASC.cpp
+++ b/q3DMASC.cpp
@@ -141,8 +141,6 @@ void q3DMASCPlugin::doClassifyAction()
classifDlg.setCloudRoles(cloudLabels, corePointsLabel);
classifDlg.label_trainOrClassify->setText(corePointsLabel + " will be classified");
classifDlg.classifierFileLineEdit->setText(inputFilename);
- static bool s_keepAttributes = false;
- classifDlg.keepAttributesCheckBox->setChecked(s_keepAttributes);
classifDlg.testCloudComboBox->hide();
classifDlg.testLabel->hide();
if (!classifDlg.exec())
@@ -150,8 +148,8 @@ void q3DMASCPlugin::doClassifyAction()
//process cancelled by the user
return;
}
-
- s_keepAttributes = classifDlg.keepAttributesCheckBox->isChecked();
+ bool useExistingScalarFields = classifDlg.checkBox_useExistingScalarFields->isChecked();
+ static bool s_keepAttributes = classifDlg.keepAttributesCheckBox->isChecked();
masc::Tools::NamedClouds clouds;
QString mainCloudLabel = corePointsLabel;
@@ -186,7 +184,7 @@ void q3DMASCPlugin::doClassifyAction()
progressDlg.setAutoClose(false); //we don't want the progress dialog to 'pop' for each feature
QString error;
SFCollector generatedScalarFields;
- if (!masc::Tools::PrepareFeatures(corePoints, features, error, &progressDlg, &generatedScalarFields))
+ if (!masc::Tools::PrepareFeatures(corePoints, features, error, &progressDlg, &generatedScalarFields, useExistingScalarFields))
{
m_app->dispToConsole(error, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
generatedScalarFields.releaseSFs(false);
@@ -275,7 +273,6 @@ void q3DMASCPlugin::doTrainAction()
return;
}
- static bool s_keepAttributes = false;
masc::Tools::NamedClouds loadedClouds;
masc::CorePoints corePoints;
@@ -296,13 +293,13 @@ void q3DMASCPlugin::doTrainAction()
classifDlg.setCloudRoles(cloudLabels, corePointsLabel);
classifDlg.label_trainOrClassify->setText("The classifier will be trained on " + corePointsLabel);
classifDlg.classifierFileLineEdit->setText(inputFilename);
- classifDlg.keepAttributesCheckBox->setChecked(s_keepAttributes);
+ classifDlg.keepAttributesCheckBox->hide(); // this parameter is set in the trainDlg dialog
+ classifDlg.checkBox_useExistingScalarFields->hide(); // this parameter is set in the trainDlg dialog
if (!classifDlg.exec())
{
//process cancelled by the user
return;
}
- s_keepAttributes = classifDlg.keepAttributesCheckBox->isChecked();
classifDlg.getClouds(loadedClouds);
m_app->dispToConsole("Training cloud: " + mainCloudLabel, ccMainAppInterface::STD_CONSOLE_MESSAGE);
@@ -391,6 +388,8 @@ void q3DMASCPlugin::doTrainAction()
trainDlg.testDataRatioSpinBox->setValue(static_cast(s_params.testDataRatio * 100));
trainDlg.testDataRatioSpinBox->setEnabled(testCloud == nullptr);
trainDlg.setInputFilePath(inputFilename);
+ static bool s_keepAttributes = trainDlg.checkBox_useExistingScalarFields->isChecked();
+ bool useExistingScalarFields = trainDlg.checkBox_useExistingScalarFields->isChecked();
//display the loaded features and let the user select the ones to use
trainDlg.setResultText("Select features and press 'Run'");
@@ -508,7 +507,7 @@ void q3DMASCPlugin::doTrainAction()
{
progressDlg.setAutoClose(false); //we don't want the progress dialog to 'pop' for each feature
QString error;
- if (!masc::Tools::PrepareFeatures(corePoints, toPrepare, error, &progressDlg, &generatedScalarFields))
+ if (!masc::Tools::PrepareFeatures(corePoints, toPrepare, error, &progressDlg, &generatedScalarFields, useExistingScalarFields))
{
m_app->dispToConsole(error, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
generatedScalarFields.releaseSFs(false);
@@ -590,7 +589,7 @@ void q3DMASCPlugin::doTrainAction()
return;
}
trainDlg.setFirstRunDone();
- trainDlg.shouldSaveClassifier();
+// trainDlg.shouldSaveClassifier(); // useless?
}
//test the trained classifier
@@ -626,7 +625,7 @@ void q3DMASCPlugin::doTrainAction()
masc::CorePoints corePointsTest;
corePointsTest.cloud = corePointsTest.origin = testCloud;
corePointsTest.role = mainCloudLabel;
- if (!masc::Tools::PrepareFeatures(corePointsTest, toPrepareTest, error, &progressDlg, &generatedScalarFieldsTest))
+ if (!masc::Tools::PrepareFeatures(corePointsTest, toPrepareTest, error, &progressDlg, &generatedScalarFieldsTest, useExistingScalarFields))
{
m_app->dispToConsole(error, ccMainAppInterface::ERR_CONSOLE_MESSAGE);
generatedScalarFields.releaseSFs(false);
diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp
index 3c77c7a..d793492 100644
--- a/q3DMASCClassifier.cpp
+++ b/q3DMASCClassifier.cpp
@@ -442,8 +442,7 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources,
metrics.ratio = static_cast(metrics.goodGuess) / metrics.sampleCount;
}
- std::unique_ptr confusionMatrix(new ConfusionMatrix(actualClass, predictectedClass));
- train3DMASCDialog.addConfusionMatrix(confusionMatrix);
+ train3DMASCDialog.addConfusionMatrixAndSaveTraces(new ConfusionMatrix(actualClass, predictectedClass));
return true;
}
diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp
index 43c0602..997d746 100644
--- a/q3DMASCTools.cpp
+++ b/q3DMASCTools.cpp
@@ -962,7 +962,8 @@ struct FeaturesAndScales
QMap > contextBasedFeaturesPerScale;
};
-bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& errorStr, CCCoreLib::GenericProgressCallback* progressCb/*=nullptr*/, SFCollector* generatedScalarFields/*=nullptr*/)
+bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& errorStr,
+ CCCoreLib::GenericProgressCallback* progressCb/*=nullptr*/, SFCollector* generatedScalarFields/*=nullptr*/, bool useExistingScalarFields/*=false*/)
{
if (features.empty() || !corePoints.origin)
{
@@ -986,8 +987,7 @@ bool Tools::PrepareFeatures(const CorePoints& corePoints, Feature::Set& features
// if the feature already exists and if useExistingFeatures is checked, simply populate generatedScalarFields
//prepare the feature
- bool useExistingScalarFileds = true;
- if (!feature->prepare(corePoints, errorStr, progressCb, generatedScalarFields, useExistingScalarFileds))
+ if (!feature->prepare(corePoints, errorStr, progressCb, generatedScalarFields, useExistingScalarFields))
{
//something failed (error should be up to date)
return false;
diff --git a/q3DMASCTools.h b/q3DMASCTools.h
index 840deea..40d9733 100644
--- a/q3DMASCTools.h
+++ b/q3DMASCTools.h
@@ -56,7 +56,8 @@ namespace masc
static bool SaveClassifier(QString filename, const Feature::Set& features, const QString corePointsRole, const masc::Classifier& classifier, QWidget* parent = nullptr);
- static bool PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& error, CCCoreLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr);
+ static bool PrepareFeatures(const CorePoints& corePoints, Feature::Set& features, QString& error,
+ CCCoreLib::GenericProgressCallback* progressCb = nullptr, SFCollector* generatedScalarFields = nullptr, bool useExistingScalarFields = false);
static bool RandomSubset(ccPointCloud* cloud, float ratio, CCCoreLib::ReferenceCloud* inRatioSubset, CCCoreLib::ReferenceCloud* outRatioSubset);
diff --git a/qClassify3DMASCDialog.cpp b/qClassify3DMASCDialog.cpp
index 2904b8c..5858027 100644
--- a/qClassify3DMASCDialog.cpp
+++ b/qClassify3DMASCDialog.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
//#include
//system
@@ -117,6 +118,24 @@ Classify3DMASCDialog::Classify3DMASCDialog(ccMainAppInterface* app, bool trainMo
onCloudChanged(0);
}
+void Classify3DMASCDialog::readSettings()
+{
+ QSettings settings;
+ settings.beginGroup("3DMASC");
+ bool keepAttributes = settings.value("keepAttributes", false).toBool();
+ this->keepAttributesCheckBox->setChecked(keepAttributes);
+ bool useExistingScalarFields = settings.value("useExistingScalarFields", false).toBool();
+ this->checkBox_useExistingScalarFields->setChecked(useExistingScalarFields);
+}
+
+void Classify3DMASCDialog::writeSettings()
+{
+ QSettings settings;
+ settings.beginGroup("3DMASC");
+ settings.setValue("keepAttributes", keepAttributesCheckBox->isChecked());
+ settings.setValue("useExistingScalarFields", checkBox_useExistingScalarFields->isChecked());
+}
+
void Classify3DMASCDialog::setCloudRoles(const QList& roles, QString corePointsLabel)
{
int index = 0;
diff --git a/qClassify3DMASCDialog.h b/qClassify3DMASCDialog.h
index 9c813ee..b250c7a 100644
--- a/qClassify3DMASCDialog.h
+++ b/qClassify3DMASCDialog.h
@@ -35,6 +35,11 @@ public:
//! Default constructor
Classify3DMASCDialog(ccMainAppInterface* app, bool trainMode = false);
+ //! read settings
+ void readSettings();
+ //! write settings
+ void writeSettings();
+
//! Sets the clouds roles
void setCloudRoles(const QList& roles, QString corePointsLabel);
diff --git a/qTrain3DMASCDialog.cpp b/qTrain3DMASCDialog.cpp
index b17e205..1a424e9 100644
--- a/qTrain3DMASCDialog.cpp
+++ b/qTrain3DMASCDialog.cpp
@@ -42,12 +42,12 @@ Train3DMASCDialog::Train3DMASCDialog(QWidget* parent/*=nullptr*/)
, saveRequested(false)
, traceFileConfigured(false)
, m_traceFile(nullptr)
+ , run(0)
{
setupUi(this);
QDateTime dateTime = QDateTime::currentDateTime();
m_baseName = "3dmasc_" + dateTime.toString("yyyyMMdd") + "_" + dateTime.toString("hh") + "h" + dateTime.toString("mm");
- run = 0;
readSettings();
@@ -60,12 +60,21 @@ Train3DMASCDialog::~Train3DMASCDialog()
{
writeSettings();
closeTraceFile();
+ for (auto m : toDeleteLater)
+ {
+ if (m != nullptr)
+ delete m;
+ }
}
void Train3DMASCDialog::readSettings()
{
QSettings settings;
settings.beginGroup("3DMASC");
+ bool keepAttributes = settings.value("keepAttributes", false).toBool();
+ this->keepAttributesCheckBox->setChecked(keepAttributes);
+ bool useExistingScalarFields = settings.value("useExistingScalarFields", false).toBool();
+ this->checkBox_useExistingScalarFields->setChecked(useExistingScalarFields);
bool saveTrace = settings.value("saveTrace", false).toBool();
setCheckBoxSaveTrace(saveTrace);
}
@@ -74,6 +83,8 @@ void Train3DMASCDialog::writeSettings()
{
QSettings settings;
settings.beginGroup("3DMASC");
+ settings.setValue("keepAttributes", keepAttributesCheckBox->isChecked());
+ settings.setValue("useExistingScalarFields", checkBox_useExistingScalarFields->isChecked());
settings.setValue("saveTrace", checkBox_keepTraces->isChecked());
}
@@ -220,12 +231,10 @@ void Train3DMASCDialog::onExportResults()
}
}
-void Train3DMASCDialog::addConfusionMatrix(std::unique_ptr& ptr)
+void Train3DMASCDialog::addConfusionMatrixAndSaveTraces(ConfusionMatrix* confusionMatrix)
{
- run++; // increment the run number
- ptr->setSessionRun(m_baseName, run);
- saveTraces(*ptr);
- m_confusionMatrices.push_back(std::move(ptr));
+ toDeleteLater.push_back(confusionMatrix);
+ saveTraces(confusionMatrix);
}
void Train3DMASCDialog::setInputFilePath(QString filePath)
@@ -305,8 +314,10 @@ bool Train3DMASCDialog::closeTraceFile()
return true;
}
-void Train3DMASCDialog::saveTraces(ConfusionMatrix &confusionMatrix)
+void Train3DMASCDialog::saveTraces(ConfusionMatrix *confusionMatrix)
{
+ run++; // increment the run number
+ confusionMatrix->setSessionRun(m_baseName, run);
if (checkBox_keepTraces->isChecked())
{
if (!traceFileConfigured) // if the trace file is not configured yet, do it
@@ -314,15 +325,13 @@ void Train3DMASCDialog::saveTraces(ConfusionMatrix &confusionMatrix)
if (!openTraceFile())
return;
}
- else // save the trace
- {
- // save the run number and the overall accuracy
- if (m_traceStream.device())
- m_traceStream << run << " " << confusionMatrix.m_overallAccuracy << Qt::endl;
- // save the confusion matrix
- // save the features
- // save the classifier
- }
+ // save the trace
+
+ // save the run number and the overall accuracy
+ if (m_traceStream.device())
+ m_traceStream << run << " " << confusionMatrix->m_overallAccuracy << Qt::endl;
+ confusionMatrix->save(m_tracePath + "/" + "run_" + QString::number(run) + "_confusion_matrix.txt");
+
}
}
diff --git a/qTrain3DMASCDialog.h b/qTrain3DMASCDialog.h
index 76b10c3..92b3948 100644
--- a/qTrain3DMASCDialog.h
+++ b/qTrain3DMASCDialog.h
@@ -59,12 +59,12 @@ public:
inline bool shouldSaveClassifier() const { return saveRequested; }
- void addConfusionMatrix(std::unique_ptr& ptr);
+ void addConfusionMatrixAndSaveTraces(ConfusionMatrix* ptr);
void setInputFilePath(QString filename);
void setCheckBoxSaveTrace(bool state);
bool openTraceFile();
bool closeTraceFile();
- void saveTraces(ConfusionMatrix &confusionMatrix);
+ void saveTraces(ConfusionMatrix *confusionMatrix);
bool getSaveTrace();
QString getTracePath();
int getRun();
@@ -79,7 +79,7 @@ protected: //members
bool classifierSaved;
bool saveRequested;
- std::vector> m_confusionMatrices;
+ std::vector toDeleteLater;
bool traceFileConfigured;
QFile *m_traceFile;
QString m_tracePath;